From ea736853e517237757255b1fd4c57e423e7b6037 Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Sat, 17 Aug 2019 18:21:13 +0900 Subject: [PATCH 1/7] support search related fields --- resources/META-INF/plugin.xml | 1 + .../koxudaxi/pydantic/PydanticBaseModel.kt | 11 +++ .../pydantic/PydanticFieldRenameFactory.kt | 5 - .../pydantic/PydanticFieldSearchExecutor.kt | 94 +++++++++++++++++++ 4 files changed, 106 insertions(+), 5 deletions(-) create mode 100644 src/com/koxudaxi/pydantic/PydanticBaseModel.kt create mode 100644 src/com/koxudaxi/pydantic/PydanticFieldSearchExecutor.kt diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml index d4fdab3c..1de244db 100644 --- a/resources/META-INF/plugin.xml +++ b/resources/META-INF/plugin.xml @@ -50,6 +50,7 @@ enabledByDefault="true" level="WARNING" implementationClass="com.koxudaxi.pydantic.PydanticInspection"/> + diff --git a/src/com/koxudaxi/pydantic/PydanticBaseModel.kt b/src/com/koxudaxi/pydantic/PydanticBaseModel.kt new file mode 100644 index 00000000..720d8da4 --- /dev/null +++ b/src/com/koxudaxi/pydantic/PydanticBaseModel.kt @@ -0,0 +1,11 @@ +package com.koxudaxi.pydantic + +import com.jetbrains.python.psi.PyCallExpression +import com.jetbrains.python.psi.PyClass +import com.jetbrains.python.psi.PyKeywordArgument + + +fun getPyClassByPyKeywordArgument(pyKeywordArgument: PyKeywordArgument) : PyClass? { + val pyCallExpression = pyKeywordArgument.parent?.parent as? PyCallExpression ?: return null + return pyCallExpression.callee?.reference?.resolve() as? PyClass ?: return null +} diff --git a/src/com/koxudaxi/pydantic/PydanticFieldRenameFactory.kt b/src/com/koxudaxi/pydantic/PydanticFieldRenameFactory.kt index 7d266061..87708162 100644 --- a/src/com/koxudaxi/pydantic/PydanticFieldRenameFactory.kt +++ b/src/com/koxudaxi/pydantic/PydanticFieldRenameFactory.kt @@ -121,8 +121,3 @@ class PydanticFieldRenameFactory : AutomaticRenamerFactory { } } - -private fun getPyClassByPyKeywordArgument(pyKeywordArgument: PyKeywordArgument) : PyClass? { - val pyCallExpression = pyKeywordArgument.parent?.parent as? PyCallExpression ?: return null - return pyCallExpression.callee?.reference?.resolve() as? PyClass ?: return null -} \ No newline at end of file diff --git a/src/com/koxudaxi/pydantic/PydanticFieldSearchExecutor.kt b/src/com/koxudaxi/pydantic/PydanticFieldSearchExecutor.kt new file mode 100644 index 00000000..71c9a2a8 --- /dev/null +++ b/src/com/koxudaxi/pydantic/PydanticFieldSearchExecutor.kt @@ -0,0 +1,94 @@ +package com.koxudaxi.pydantic + +import com.intellij.openapi.application.QueryExecutorBase +import com.intellij.openapi.application.ReadAction.run +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiReference +import com.intellij.psi.search.searches.ReferencesSearch +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.util.Processor +import com.jetbrains.python.psi.PyCallExpression +import com.jetbrains.python.psi.PyClass +import com.jetbrains.python.psi.PyKeywordArgument +import com.jetbrains.python.psi.PyTargetExpression +import com.jetbrains.python.psi.search.PyClassInheritorsSearch + +private fun searchField(pyClass: PyClass, elementName: String, consumer: Processor, single: Boolean = false): Boolean { + if (!pyClass.isSubclass("pydantic.main.BaseModel", null)) return false + pyClass.classAttributes.forEach { pyTargetExpression -> + if (pyTargetExpression.name == elementName) { + consumer.process(pyTargetExpression.reference) + if (single) return true + } + } + return false +} + +private fun searchKeywordArgument(pyClass: PyClass, elementName: String, consumer: Processor) { + if (!pyClass.isSubclass("pydantic.main.BaseModel", null)) return + ReferencesSearch.search(pyClass as PsiElement).forEach { psiReference -> + val callee = PsiTreeUtil.getParentOfType(psiReference.element, PyCallExpression::class.java) + callee?.arguments?.forEach { argument -> + if (argument is PyKeywordArgument && argument.name == elementName) { + consumer.process(argument.reference) + + } + } + } +} + +private fun searchDirectReferenceField(pyClass: PyClass, elementName: String, consumer: Processor): Boolean { + if (searchField(pyClass, elementName, consumer, true)) { + return true + } + pyClass.getAncestorClasses(null).forEach { ancestorClass -> + if (ancestorClass.qualifiedName != "pydantic.main.BaseModel") { + if (ancestorClass.isSubclass("pydantic.main.BaseModel", null)) { + if (searchDirectReferenceField(ancestorClass, elementName, consumer)) { + return true + } + } + } + } + return false + } + +private fun searchAllElementReference(pyClass: PyClass?, elementName: String, added: MutableSet, consumer: Processor) { + if (pyClass == null) return + added.add(pyClass) + searchField(pyClass, elementName, consumer) + searchKeywordArgument(pyClass, elementName, consumer) + pyClass.getAncestorClasses(null).forEach { ancestorClass -> + if (ancestorClass.qualifiedName != "pydantic.main.BaseModel") { + if (ancestorClass.isSubclass("pydantic.main.BaseModel", null) && + !added.contains(ancestorClass)) { + searchAllElementReference(ancestorClass, elementName, added, consumer) + } + } + } + PyClassInheritorsSearch.search(pyClass, true).forEach { inheritorsPyClass -> + if (inheritorsPyClass.qualifiedName != "pydantic.main.BaseModel" && ! added.contains(inheritorsPyClass)) { + searchAllElementReference(inheritorsPyClass, elementName, added, consumer) + } + } +} +class PydanticFieldSearchExecutor : QueryExecutorBase() { + override fun processQuery(queryParameters: ReferencesSearch.SearchParameters, consumer: Processor) { + val element = queryParameters.elementToSearch + + when (element) { + is PyKeywordArgument -> run { + val elementName = element.name ?: return@run + val pyClass = getPyClassByPyKeywordArgument(element) ?: return@run + if (!pyClass.isSubclass("pydantic.main.BaseModel", null)) return@run + searchDirectReferenceField(pyClass, elementName, consumer) + } + is PyTargetExpression -> run { + val elementName = element.name ?: return@run + val pyClass = element.containingClass ?: return@run + if (!pyClass.isSubclass("pydantic.main.BaseModel", null)) return@run + searchAllElementReference(pyClass, elementName, mutableSetOf(), consumer) + } + } + } + } From 08f40f85ba1974ab6921938e0d2019b78d7da2ef Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Sat, 17 Aug 2019 19:03:57 +0900 Subject: [PATCH 2/7] exclude super-class from search target --- src/com/koxudaxi/pydantic/PydanticFieldSearchExecutor.kt | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/com/koxudaxi/pydantic/PydanticFieldSearchExecutor.kt b/src/com/koxudaxi/pydantic/PydanticFieldSearchExecutor.kt index 71c9a2a8..8bca8a73 100644 --- a/src/com/koxudaxi/pydantic/PydanticFieldSearchExecutor.kt +++ b/src/com/koxudaxi/pydantic/PydanticFieldSearchExecutor.kt @@ -58,20 +58,13 @@ private fun searchAllElementReference(pyClass: PyClass?, elementName: String, ad added.add(pyClass) searchField(pyClass, elementName, consumer) searchKeywordArgument(pyClass, elementName, consumer) - pyClass.getAncestorClasses(null).forEach { ancestorClass -> - if (ancestorClass.qualifiedName != "pydantic.main.BaseModel") { - if (ancestorClass.isSubclass("pydantic.main.BaseModel", null) && - !added.contains(ancestorClass)) { - searchAllElementReference(ancestorClass, elementName, added, consumer) - } - } - } PyClassInheritorsSearch.search(pyClass, true).forEach { inheritorsPyClass -> if (inheritorsPyClass.qualifiedName != "pydantic.main.BaseModel" && ! added.contains(inheritorsPyClass)) { searchAllElementReference(inheritorsPyClass, elementName, added, consumer) } } } + class PydanticFieldSearchExecutor : QueryExecutorBase() { override fun processQuery(queryParameters: ReferencesSearch.SearchParameters, consumer: Processor) { val element = queryParameters.elementToSearch From 1da151ff1f28a08310cfcd46bd9f69a94b482f0a Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Sat, 17 Aug 2019 19:18:50 +0900 Subject: [PATCH 3/7] fix coding styles --- .../pydantic/PydanticFieldRenameFactory.kt | 16 ++++------ .../pydantic/PydanticFieldSearchExecutor.kt | 30 ++++++++----------- 2 files changed, 18 insertions(+), 28 deletions(-) diff --git a/src/com/koxudaxi/pydantic/PydanticFieldRenameFactory.kt b/src/com/koxudaxi/pydantic/PydanticFieldRenameFactory.kt index 87708162..26a2254e 100644 --- a/src/com/koxudaxi/pydantic/PydanticFieldRenameFactory.kt +++ b/src/com/koxudaxi/pydantic/PydanticFieldRenameFactory.kt @@ -6,14 +6,12 @@ import com.intellij.psi.util.PsiTreeUtil import com.intellij.refactoring.rename.naming.AutomaticRenamer import com.intellij.refactoring.rename.naming.AutomaticRenamerFactory import com.intellij.usageView.UsageInfo -import com.jetbrains.extensions.python.inherits import com.jetbrains.python.codeInsight.PyCodeInsightSettings import com.jetbrains.python.psi.PyCallExpression import com.jetbrains.python.psi.PyClass import com.jetbrains.python.psi.PyKeywordArgument import com.jetbrains.python.psi.PyTargetExpression import com.jetbrains.python.psi.search.PyClassInheritorsSearch -import com.jetbrains.python.psi.types.TypeEvalContext class PydanticFieldRenameFactory : AutomaticRenamerFactory { @@ -69,7 +67,7 @@ class PydanticFieldRenameFactory : AutomaticRenamerFactory { added.add(pyClass) addClassAttributes(pyClass, elementName) addKeywordArguments(pyClass, elementName) - pyClass.getAncestorClasses(null).forEach { ancestorClass -> + pyClass.getAncestorClasses(null).forEach { ancestorClass -> if (ancestorClass.qualifiedName != "pydantic.main.BaseModel") { if (ancestorClass.isSubclass("pydantic.main.BaseModel", null) && !added.contains(ancestorClass)) { @@ -78,18 +76,15 @@ class PydanticFieldRenameFactory : AutomaticRenamerFactory { } } PyClassInheritorsSearch.search(pyClass, true).forEach { inheritorsPyClass -> - if (inheritorsPyClass.qualifiedName != "pydantic.main.BaseModel" && ! added.contains(inheritorsPyClass)) { + if (inheritorsPyClass.qualifiedName != "pydantic.main.BaseModel" && !added.contains(inheritorsPyClass)) { addAllElement(inheritorsPyClass, elementName, added) } } } private fun addClassAttributes(pyClass: PyClass, elementName: String) { - pyClass.classAttributes.forEach { pyTargetExpression -> - if (pyTargetExpression.name == elementName) { - myElements.add(pyTargetExpression) - } - } + val pyTargetExpression = pyClass.findClassAttribute(elementName, false, null) ?: return + myElements.add(pyTargetExpression) } private fun addKeywordArguments(pyClass: PyClass, elementName: String) { @@ -97,12 +92,13 @@ class PydanticFieldRenameFactory : AutomaticRenamerFactory { val callee = PsiTreeUtil.getParentOfType(psiReference.element, PyCallExpression::class.java) callee?.arguments?.forEach { argument -> if (argument is PyKeywordArgument && argument.name == elementName) { - myElements.add(argument) + myElements.add(argument) } } } } + override fun getDialogTitle(): String { return "Rename Fields" } diff --git a/src/com/koxudaxi/pydantic/PydanticFieldSearchExecutor.kt b/src/com/koxudaxi/pydantic/PydanticFieldSearchExecutor.kt index 8bca8a73..7168b0e1 100644 --- a/src/com/koxudaxi/pydantic/PydanticFieldSearchExecutor.kt +++ b/src/com/koxudaxi/pydantic/PydanticFieldSearchExecutor.kt @@ -13,15 +13,11 @@ import com.jetbrains.python.psi.PyKeywordArgument import com.jetbrains.python.psi.PyTargetExpression import com.jetbrains.python.psi.search.PyClassInheritorsSearch -private fun searchField(pyClass: PyClass, elementName: String, consumer: Processor, single: Boolean = false): Boolean { +private fun searchField(pyClass: PyClass, elementName: String, consumer: Processor): Boolean { if (!pyClass.isSubclass("pydantic.main.BaseModel", null)) return false - pyClass.classAttributes.forEach { pyTargetExpression -> - if (pyTargetExpression.name == elementName) { - consumer.process(pyTargetExpression.reference) - if (single) return true - } - } - return false + val pyTargetExpression = pyClass.findClassAttribute(elementName, false, null) ?: return false + consumer.process(pyTargetExpression.reference) + return true } private fun searchKeywordArgument(pyClass: PyClass, elementName: String, consumer: Processor) { @@ -38,20 +34,19 @@ private fun searchKeywordArgument(pyClass: PyClass, elementName: String, consume } private fun searchDirectReferenceField(pyClass: PyClass, elementName: String, consumer: Processor): Boolean { - if (searchField(pyClass, elementName, consumer, true)) { - return true - } - pyClass.getAncestorClasses(null).forEach { ancestorClass -> + if (searchField(pyClass, elementName, consumer)) return true + + pyClass.getAncestorClasses(null).forEach { ancestorClass -> if (ancestorClass.qualifiedName != "pydantic.main.BaseModel") { if (ancestorClass.isSubclass("pydantic.main.BaseModel", null)) { if (searchDirectReferenceField(ancestorClass, elementName, consumer)) { return true } } - } } - return false } + return false +} private fun searchAllElementReference(pyClass: PyClass?, elementName: String, added: MutableSet, consumer: Processor) { if (pyClass == null) return @@ -59,7 +54,7 @@ private fun searchAllElementReference(pyClass: PyClass?, elementName: String, ad searchField(pyClass, elementName, consumer) searchKeywordArgument(pyClass, elementName, consumer) PyClassInheritorsSearch.search(pyClass, true).forEach { inheritorsPyClass -> - if (inheritorsPyClass.qualifiedName != "pydantic.main.BaseModel" && ! added.contains(inheritorsPyClass)) { + if (inheritorsPyClass.qualifiedName != "pydantic.main.BaseModel" && !added.contains(inheritorsPyClass)) { searchAllElementReference(inheritorsPyClass, elementName, added, consumer) } } @@ -67,9 +62,8 @@ private fun searchAllElementReference(pyClass: PyClass?, elementName: String, ad class PydanticFieldSearchExecutor : QueryExecutorBase() { override fun processQuery(queryParameters: ReferencesSearch.SearchParameters, consumer: Processor) { - val element = queryParameters.elementToSearch - when (element) { + when (val element = queryParameters.elementToSearch) { is PyKeywordArgument -> run { val elementName = element.name ?: return@run val pyClass = getPyClassByPyKeywordArgument(element) ?: return@run @@ -83,5 +77,5 @@ class PydanticFieldSearchExecutor : QueryExecutorBase Date: Sun, 18 Aug 2019 01:19:44 +0900 Subject: [PATCH 4/7] update target kotlin version --- build.gradle | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index f13d82ba..902bd8ba 100644 --- a/build.gradle +++ b/build.gradle @@ -36,16 +36,16 @@ allprojects { compileKotlin { kotlinOptions { jvmTarget = "1.8" - languageVersion = "1.2" - apiVersion = "1.2" + languageVersion = "1.3" + apiVersion = "1.3" } } compileTestKotlin { kotlinOptions { jvmTarget = "1.8" - languageVersion = "1.2" - apiVersion = "1.2" + languageVersion = "1.3" + apiVersion = "1.23" } } From 010d66edc914176c26a24bba6ca869854b974a33 Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Sun, 18 Aug 2019 01:20:12 +0900 Subject: [PATCH 5/7] add to search super-class attribute by crrent class attribute --- src/com/koxudaxi/pydantic/PydanticFieldSearchExecutor.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/com/koxudaxi/pydantic/PydanticFieldSearchExecutor.kt b/src/com/koxudaxi/pydantic/PydanticFieldSearchExecutor.kt index 7168b0e1..0dffbcf2 100644 --- a/src/com/koxudaxi/pydantic/PydanticFieldSearchExecutor.kt +++ b/src/com/koxudaxi/pydantic/PydanticFieldSearchExecutor.kt @@ -53,6 +53,11 @@ private fun searchAllElementReference(pyClass: PyClass?, elementName: String, ad added.add(pyClass) searchField(pyClass, elementName, consumer) searchKeywordArgument(pyClass, elementName, consumer) + pyClass.getAncestorClasses(null).forEach { ancestorClass -> + if (ancestorClass.qualifiedName != "pydantic.main.BaseModel" && !added.contains(ancestorClass)){ + searchField(pyClass, elementName, consumer) + } + } PyClassInheritorsSearch.search(pyClass, true).forEach { inheritorsPyClass -> if (inheritorsPyClass.qualifiedName != "pydantic.main.BaseModel" && !added.contains(inheritorsPyClass)) { searchAllElementReference(inheritorsPyClass, elementName, added, consumer) From d1f632b9657441fa8685e856b1a7053fe8ec5d9d Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Sun, 18 Aug 2019 01:21:14 +0900 Subject: [PATCH 6/7] update documents --- README.md | 2 +- resources/META-INF/plugin.xml | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5fe3e08b..6834aaec 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ * Model-specific `__init__`-arguments type-checking for subclasses of `pydantic.BaseModel` * Refactor support for renaming fields for subclasses of `BaseModel` * (If the field name is refactored from the model definition or `__init__` call keyword arguments, PyCharm will present a dialog offering the choice to automatically rename the keyword where it occurs in a model initialization call. - +* Search related-fields by class attributes and keyword arguments of __init__. with Ctrl+B and Cmd+B ## How to install: ### MarketPlace diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml index 1de244db..1c733fa8 100644 --- a/resources/META-INF/plugin.xml +++ b/resources/META-INF/plugin.xml @@ -1,9 +1,14 @@ com.koxudaxi.pydantic Pydantic - 0.0.13 + 0.0.14 Koudai Aono @koxudaxi version 0.0.14 +

Features

+
    +
  • Search related-fields by class attributes and keyword arguments of __init__. with Ctrl+B and Cmd+B [#42]
  • +

version 0.0.13

Features, BugFixes

    @@ -29,6 +34,7 @@
  • Model-specific __init__-arguments type-checking for subclasses of pydantic.BaseModel
  • Refactor support for renaming fields for subclasses of BaseModel
  • (If the field name is refactored from the model definition or __init__ call keyword arguments, PyCharm will present a dialog offering the choice to automatically rename the keyword where it occurs in a model initialization call.
  • +
  • Search related-fields by class attributes and keyword arguments of __init__. with Ctrl+B and Cmd+B
  • pydantic.dataclasses.dataclass From de63bcbb6e7852eabd55e0937b8aae2a43f04ed5 Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Sun, 18 Aug 2019 01:28:23 +0900 Subject: [PATCH 7/7] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6834aaec..87f8b348 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,8 @@ * Model-specific `__init__`-arguments type-checking for subclasses of `pydantic.BaseModel` * Refactor support for renaming fields for subclasses of `BaseModel` * (If the field name is refactored from the model definition or `__init__` call keyword arguments, PyCharm will present a dialog offering the choice to automatically rename the keyword where it occurs in a model initialization call. -* Search related-fields by class attributes and keyword arguments of __init__. with Ctrl+B and Cmd+B +* Search related-fields by class attributes and keyword arguments of `__init__` with `Ctrl+B` and `Cmd+B` + ## How to install: ### MarketPlace