From ebb55bd49c9f57ec72632a6cbbb092eb3e9db2d5 Mon Sep 17 00:00:00 2001 From: Artem Davletov Date: Fri, 21 Aug 2020 18:36:14 +0300 Subject: [PATCH] #250 Warning correct using methods (#299) Fixes: #250 --- CHANGES | 1 + gradle.properties | 6 +- .../snakecharm/codeInsight/SnakemakeAPI.kt | 23 +++++ .../SmkMisuseUsageIOFlagMethodsInspection.kt | 69 +++++++++++++++ .../snakecharm/lang/SnakemakeNames.kt | 11 +++ src/main/resources/META-INF/plugin.xml | 11 +++ src/main/resources/SnakemakeBundle.properties | 4 + ...SmkMisuseUsageIOFlagMethodsInspection.html | 5 ++ ...e_usage_io_flag_methods_inspection.feature | 88 +++++++++++++++++++ 9 files changed, 215 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkMisuseUsageIOFlagMethodsInspection.kt create mode 100644 src/main/resources/inspectionDescriptions/SmkMisuseUsageIOFlagMethodsInspection.html create mode 100644 src/test/resources/features/highlighting/inspections/misuse_usage_io_flag_methods_inspection.feature diff --git a/CHANGES b/CHANGES index 9cd7129af..6f85c3d46 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,7 @@ Features: - Inspection: Comma in the end of section arg list isn't needed (see #255) - Inspection: Warn user if rule from 'rulesorder' isn't available in current or included files (see #254) - Inspection: Warn about string arguments split on several lines (see #259) +- Inspection: Correct using methods ancient, protected, directory (see #250) - Inspection: Warn users if they have callable arguments in sections that does not expect it (see #198) - Inspection: Warn users if they have keyword arguments in sections that does not expect it (see #196) - Inspection: Warn about usage of rule/checkpoint/subworkflow objects as arguments instead of one of its fields (see #257) diff --git a/gradle.properties b/gradle.properties index c4dd921a1..4d253d22f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -28,9 +28,9 @@ downloadIdeSources=true # # ide version examples: IC-172.4343, IC-2018.1.3, IC-183-EAP-SNAPSHOT, IC-LATEST-EAP-SNAPSHOT # ide type examples: IC (IDEA Community), IU (IDEA Ultimate) -#ideVersion=IC-2019.3 -#ideVersion=IC-193-EAP-SNAPSHOT -#pythonPlugin=PythonCore:193.5233.84 +#ideVersion=IC-202-EAP-SNAPSHOT +## OK with IC-202.6948.36 it is 2020.2.1 RC +#pythonPlugin=PythonCore:202.6948.52 # --------- # PyCharm # --------- diff --git a/src/main/kotlin/com/jetbrains/snakecharm/codeInsight/SnakemakeAPI.kt b/src/main/kotlin/com/jetbrains/snakecharm/codeInsight/SnakemakeAPI.kt index 968ecbfbd..1f93def45 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/codeInsight/SnakemakeAPI.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/codeInsight/SnakemakeAPI.kt @@ -23,6 +23,16 @@ import com.jetbrains.snakecharm.lang.SnakemakeNames.SECTION_THREADS import com.jetbrains.snakecharm.lang.SnakemakeNames.SECTION_VERSION import com.jetbrains.snakecharm.lang.SnakemakeNames.SECTION_WILDCARD_CONSTRAINTS import com.jetbrains.snakecharm.lang.SnakemakeNames.SECTION_WRAPPER +import com.jetbrains.snakecharm.lang.SnakemakeNames.SNAKEMAKE_IO_METHOD_ANCIENT +import com.jetbrains.snakecharm.lang.SnakemakeNames.SNAKEMAKE_IO_METHOD_DIRECTORY +import com.jetbrains.snakecharm.lang.SnakemakeNames.SNAKEMAKE_IO_METHOD_DYNAMIC +import com.jetbrains.snakecharm.lang.SnakemakeNames.SNAKEMAKE_IO_METHOD_PIPE +import com.jetbrains.snakecharm.lang.SnakemakeNames.SNAKEMAKE_IO_METHOD_PROTECTED +import com.jetbrains.snakecharm.lang.SnakemakeNames.SNAKEMAKE_IO_METHOD_REPEAT +import com.jetbrains.snakecharm.lang.SnakemakeNames.SNAKEMAKE_IO_METHOD_REPORT +import com.jetbrains.snakecharm.lang.SnakemakeNames.SNAKEMAKE_IO_METHOD_TEMP +import com.jetbrains.snakecharm.lang.SnakemakeNames.SNAKEMAKE_IO_METHOD_TOUCH +import com.jetbrains.snakecharm.lang.SnakemakeNames.SNAKEMAKE_IO_METHOD_UNPACK /** * Also see [ImplicitPySymbolsProvider] class @@ -191,4 +201,17 @@ object SnakemakeAPI { SECTION_PRIORITY, SECTION_GROUP, SECTION_SHADOW, SECTION_CONDA, SECTION_SCRIPT, SECTION_WRAPPER, SECTION_CWL, SECTION_NOTEBOOK, SECTION_CACHE, SECTION_CONTAINER ) + + val IO_FLAG_2_SUPPORTED_SECTION: HashMap> = hashMapOf( + SNAKEMAKE_IO_METHOD_ANCIENT to listOf(SECTION_INPUT), + SNAKEMAKE_IO_METHOD_PROTECTED to listOf(SECTION_OUTPUT, SECTION_LOG, SECTION_BENCHMARK), + SNAKEMAKE_IO_METHOD_DIRECTORY to listOf(SECTION_OUTPUT), + SNAKEMAKE_IO_METHOD_REPORT to listOf(SECTION_OUTPUT), + SNAKEMAKE_IO_METHOD_TEMP to listOf(SECTION_INPUT, SECTION_OUTPUT), + SNAKEMAKE_IO_METHOD_TOUCH to listOf(SECTION_OUTPUT), + SNAKEMAKE_IO_METHOD_PIPE to listOf(SECTION_OUTPUT), + SNAKEMAKE_IO_METHOD_REPEAT to listOf(SECTION_BENCHMARK), + SNAKEMAKE_IO_METHOD_UNPACK to listOf(SECTION_INPUT), + SNAKEMAKE_IO_METHOD_DYNAMIC to listOf(SECTION_OUTPUT) + ) } \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkMisuseUsageIOFlagMethodsInspection.kt b/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkMisuseUsageIOFlagMethodsInspection.kt new file mode 100644 index 000000000..4cded0fae --- /dev/null +++ b/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkMisuseUsageIOFlagMethodsInspection.kt @@ -0,0 +1,69 @@ +package com.jetbrains.snakecharm.inspections + +import com.intellij.codeInspection.LocalInspectionToolSession +import com.intellij.codeInspection.ProblemsHolder +import com.jetbrains.python.psi.PyCallExpression +import com.jetbrains.python.psi.PyReferenceExpression +import com.jetbrains.snakecharm.SnakemakeBundle +import com.jetbrains.snakecharm.codeInsight.SnakemakeAPI.IO_FLAG_2_SUPPORTED_SECTION +import com.jetbrains.snakecharm.lang.psi.SmkFile +import com.jetbrains.snakecharm.lang.psi.SmkRuleOrCheckpointArgsSection + +class SmkMisuseUsageIOFlagMethodsInspection : SnakemakeInspection() { + override fun buildVisitor( + holder: ProblemsHolder, + isOnTheFly: Boolean, + session: LocalInspectionToolSession + ) = object : SnakemakeInspectionVisitor(holder, session) { + + private fun getSupportedSectionIfMisuse( + flagCallName: String, + section: SmkRuleOrCheckpointArgsSection + ): List { + val supportedSections = IO_FLAG_2_SUPPORTED_SECTION[flagCallName] + if (supportedSections != null) { + if (section.sectionKeyword !in supportedSections) { + return supportedSections + } + } + // check N/A here + return emptyList() + } + + override fun visitSmkRuleOrCheckpointArgsSection(st: SmkRuleOrCheckpointArgsSection) { + + if (st.containingFile !is SmkFile) { + return + } + + val argList = st.argumentList ?: return + argList.arguments + .filterIsInstance() + .forEach { callExpr -> + val callee = callExpr.callee + if (callee is PyReferenceExpression) { + // We don't need qualified refs here (e.g. like `foo.boo.ancient`) + val callName = when (callee.qualifier) { + null -> callee.referencedName + else -> null + } + + if (callName != null) { + val supportedSectionIfMisuse = getSupportedSectionIfMisuse(callName, st) + if (supportedSectionIfMisuse.isNotEmpty()) { + holder.registerProblem( + callExpr, + SnakemakeBundle.message( + "INSP.NAME.misuse.usage.io.flag.methods.warning.message", + callName, + st.sectionKeyword!!, + supportedSectionIfMisuse.sorted().joinToString { "'$it'"} + ) + ) + } + } + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/SnakemakeNames.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/SnakemakeNames.kt index b8eff32e4..3ee7f309d 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/lang/SnakemakeNames.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/SnakemakeNames.kt @@ -57,4 +57,15 @@ object SnakemakeNames { const val RUN_SECTION_VARIABLE_RULE = "rule" const val RUN_SECTION_VARIABLE_JOBID = "jobid" + + const val SNAKEMAKE_IO_METHOD_ANCIENT = "ancient" + const val SNAKEMAKE_IO_METHOD_PROTECTED = "protected" + const val SNAKEMAKE_IO_METHOD_DIRECTORY = "directory" + const val SNAKEMAKE_IO_METHOD_TEMP = "temp" + const val SNAKEMAKE_IO_METHOD_REPORT = "report" + const val SNAKEMAKE_IO_METHOD_TOUCH = "touch" + const val SNAKEMAKE_IO_METHOD_PIPE = "pipe" + const val SNAKEMAKE_IO_METHOD_REPEAT = "repeat" + const val SNAKEMAKE_IO_METHOD_UNPACK = "unpack" + const val SNAKEMAKE_IO_METHOD_DYNAMIC = "dynamic" } \ No newline at end of file diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 15c61d929..d63b417f8 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -449,6 +449,17 @@ implementationClass="com.jetbrains.snakecharm.inspections.SmkRedundantCommaInspection" /> + + + +Input/Output flags (e.g. ancient(..), protected(..), directory(..), etc) not supported by all rule sections. This inspection warns about such flags misuse. + + \ No newline at end of file diff --git a/src/test/resources/features/highlighting/inspections/misuse_usage_io_flag_methods_inspection.feature b/src/test/resources/features/highlighting/inspections/misuse_usage_io_flag_methods_inspection.feature new file mode 100644 index 000000000..c6604db24 --- /dev/null +++ b/src/test/resources/features/highlighting/inspections/misuse_usage_io_flag_methods_inspection.feature @@ -0,0 +1,88 @@ +Feature: Inspection for methods from snakemake library + + Scenario Outline: Incorrect using ancient/protected/directory methods + Given a snakemake project + Given I open a file "foo.smk" with text + """ + NAME: +
: + """ + And SmkMisuseUsageIOFlagMethodsInspection inspection is enabled + Then I expect inspection warning on <> in <
: > with message + """ + '' isn't supported in '
' section, expected in sections: . + """ + When I check highlighting warnings + Examples: + | rule_like | section | method | arg_list | expected | + | rule | input | directory | ('') | 'output' | + | rule | input | pipe | ('') | 'output' | + | rule | input | protected | ('') | 'benchmark', 'log', 'output' | + | rule | log | temp | ('') | 'input', 'output' | + | rule | input | dynamic | ('') | 'output' | + | rule | input | touch | ('') | 'output' | + | rule | input | repeat | ('') | 'benchmark' | + | rule | input | report | ('') | 'output' | + | rule | output | ancient | ('') | 'input' | + | rule | output | unpack | ('') | 'input' | + | checkpoint | input | directory | ('') | 'output' | + | checkpoint | input | pipe | ('') | 'output' | + | checkpoint | input | protected | ('') | 'benchmark', 'log', 'output' | + | checkpoint | log | temp | ('') | 'input', 'output' | + | checkpoint | input | dynamic | ('') | 'output' | + | checkpoint | input | touch | ('') | 'output' | + | checkpoint | input | repeat | ('') | 'benchmark' | + | checkpoint | input | report | ('') | 'output' | + | checkpoint | output | ancient | ('') | 'input' | + | checkpoint | output | unpack | ('') | 'input' | + + Scenario Outline: Correct using ancient/protected/directory methods + Given a snakemake project + Given I open a file "foo.smk" with text + """ + NAME: +
: + """ + And SmkMisuseUsageIOFlagMethodsInspection inspection is enabled + Then I expect no inspection warnings + When I check highlighting warnings + Examples: + | rule_like | section | method | + | rule | input | ancient('') | + | rule | input | temp('') | + | rule | input | unpack('') | + | rule | output | directory('') | + | rule | output | pipe('') | + | rule | output | protected('') | + | rule | output | dynamic('') | + | rule | output | touch('') | + | rule | output | report('') | + | rule | benchmark | repeat('') | + | rule | benchmark | protected('') | + | checkpoint | input | ancient('') | + | checkpoint | input | temp('') | + | checkpoint | input | unpack('') | + | checkpoint | output | directory('') | + | checkpoint | output | pipe('') | + | checkpoint | output | protected('') | + | checkpoint | output | dynamic('') | + | checkpoint | output | touch('') | + | checkpoint | output | report('') | + | checkpoint | benchmark | repeat('') | + | checkpoint | benchmark | protected('') | + + + Scenario Outline: Complex cases not be confused + Given a snakemake project + Given I open a file "foo.smk" with text + """ + NAME: +
: + """ + And SmkMisuseUsageIOFlagMethodsInspection inspection is enabled + Then I expect no inspection warnings + When I check highlighting warnings + Examples: + | rule_like | section | method | + | rule | output | foo.ancient('') | + | rule | output | ancient.foo('') | \ No newline at end of file