diff --git a/src/main/kotlin/com/jetbrains/snakecharm/codeInsight/SnakemakeAPI.kt b/src/main/kotlin/com/jetbrains/snakecharm/codeInsight/SnakemakeAPI.kt index eeaa5d301..1c7f222ba 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/codeInsight/SnakemakeAPI.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/codeInsight/SnakemakeAPI.kt @@ -37,6 +37,9 @@ 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 +import com.jetbrains.snakecharm.lang.SnakemakeNames.WORKFLOW_CONTAINERIZED_KEYWORD +import com.jetbrains.snakecharm.lang.SnakemakeNames.WORKFLOW_CONTAINER_KEYWORD +import com.jetbrains.snakecharm.lang.SnakemakeNames.WORKFLOW_SINGULARITY_KEYWORD /** * Also see [ImplicitPySymbolsProvider] class @@ -59,7 +62,7 @@ object SnakemakeAPI { ) val FUNCTIONS_BANNED_FOR_WILDCARDS = listOf( - SMK_FUN_EXPAND + SMK_FUN_EXPAND ) const val SMK_VARS_WILDCARDS = "wildcards" @@ -69,53 +72,65 @@ object SnakemakeAPI { * Also see [ImplicitPySymbolsProvider], it also processes 'InputFiles', etc. symbols */ val SECTION_ACCESSOR_CLASSES = mapOf( - "snakemake.io.InputFiles" to "input", - "snakemake.io.OutputFiles" to "output", - "snakemake.io.Params" to "params", - "snakemake.io.Log" to "log", - "snakemake.io.Resources" to "resources" + "snakemake.io.InputFiles" to "input", + "snakemake.io.OutputFiles" to "output", + "snakemake.io.Params" to "params", + "snakemake.io.Log" to "log", + "snakemake.io.Resources" to "resources" ) const val SNAKEMAKE_MODULE_NAME_IO_PY = "io.py" val EXECUTION_SECTIONS_KEYWORDS = setOf( - SECTION_SHELL, SECTION_SCRIPT, - SECTION_WRAPPER, SECTION_CWL, SECTION_NOTEBOOK + SECTION_SHELL, SECTION_SCRIPT, + SECTION_WRAPPER, SECTION_CWL, SECTION_NOTEBOOK ) + /** + * Rule or checkpoint sections that allows only single argument + */ val SINGLE_ARGUMENT_SECTIONS_KEYWORDS = setOf( - SECTION_SHELL, SECTION_SCRIPT, SECTION_WRAPPER, - SECTION_CWL, SECTION_BENCHMARK, SECTION_VERSION, - SECTION_MESSAGE, SECTION_THREADS, SECTION_SINGULARITY, - SECTION_PRIORITY, SECTION_CONDA, SECTION_GROUP, - SECTION_SHADOW, SECTION_CACHE, SECTION_NOTEBOOK, SECTION_CONTAINER, - SECTION_HANDOVER, SECTION_CONTAINERIZED + SECTION_SHELL, SECTION_SCRIPT, SECTION_WRAPPER, + SECTION_CWL, SECTION_BENCHMARK, SECTION_VERSION, + SECTION_MESSAGE, SECTION_THREADS, SECTION_SINGULARITY, + SECTION_PRIORITY, SECTION_CONDA, SECTION_GROUP, + SECTION_SHADOW, SECTION_CACHE, SECTION_NOTEBOOK, SECTION_CONTAINER, + SECTION_HANDOVER, SECTION_CONTAINERIZED + ) + + /** + * Workflow top-level sections that allows only single argument + */ + val SINGLE_ARGUMENT_WORKFLOWS_KEYWORDS = setOf( + WORKFLOW_CONTAINERIZED_KEYWORD, WORKFLOW_CONTAINER_KEYWORD, + WORKFLOW_SINGULARITY_KEYWORD ) /** * For rules parsing */ val RULE_OR_CHECKPOINT_ARGS_SECTION_KEYWORDS = setOf( - SECTION_OUTPUT, SECTION_INPUT, SECTION_PARAMS, SECTION_LOG, SECTION_RESOURCES, - SECTION_BENCHMARK, SECTION_VERSION, SECTION_MESSAGE, SECTION_SHELL, SECTION_THREADS, SECTION_SINGULARITY, - SECTION_PRIORITY, SECTION_WILDCARD_CONSTRAINTS, SECTION_GROUP, SECTION_SHADOW, - SECTION_CONDA, - SECTION_SCRIPT, SECTION_WRAPPER, SECTION_CWL, SECTION_NOTEBOOK, - SECTION_CACHE, - SECTION_CONTAINER, - SECTION_CONTAINERIZED, - SECTION_ENVMODULES, - SECTION_NAME, - SECTION_HANDOVER - ) - val RULE_OR_CHECKPOINT_SECTION_KEYWORDS = (RULE_OR_CHECKPOINT_ARGS_SECTION_KEYWORDS + setOf(SnakemakeNames.SECTION_RUN)) + SECTION_OUTPUT, SECTION_INPUT, SECTION_PARAMS, SECTION_LOG, SECTION_RESOURCES, + SECTION_BENCHMARK, SECTION_VERSION, SECTION_MESSAGE, SECTION_SHELL, SECTION_THREADS, SECTION_SINGULARITY, + SECTION_PRIORITY, SECTION_WILDCARD_CONSTRAINTS, SECTION_GROUP, SECTION_SHADOW, + SECTION_CONDA, + SECTION_SCRIPT, SECTION_WRAPPER, SECTION_CWL, SECTION_NOTEBOOK, + SECTION_CACHE, + SECTION_CONTAINER, + SECTION_CONTAINERIZED, + SECTION_ENVMODULES, + SECTION_NAME, + SECTION_HANDOVER + ) + val RULE_OR_CHECKPOINT_SECTION_KEYWORDS = + (RULE_OR_CHECKPOINT_ARGS_SECTION_KEYWORDS + setOf(SnakemakeNames.SECTION_RUN)) /** * For subworkflows parsing */ val SUBWORKFLOW_SECTIONS_KEYWORDS = setOf( - SnakemakeNames.SUBWORKFLOW_WORKDIR_KEYWORD, - SnakemakeNames.SUBWORKFLOW_SNAKEFILE_KEYWORD, - SnakemakeNames.SUBWORKFLOW_CONFIGFILE_KEYWORD + SnakemakeNames.SUBWORKFLOW_WORKDIR_KEYWORD, + SnakemakeNames.SUBWORKFLOW_SNAKEFILE_KEYWORD, + SnakemakeNames.SUBWORKFLOW_CONFIGFILE_KEYWORD ) /** @@ -124,18 +139,18 @@ object SnakemakeAPI { * to filter these sections for resolve and completion */ val RULE_TYPE_ACCESSIBLE_SECTIONS = setOf( - SECTION_INPUT, - SECTION_LOG, - SECTION_OUTPUT, - SECTION_PARAMS, - SECTION_RESOURCES, - SECTION_VERSION, + SECTION_INPUT, + SECTION_LOG, + SECTION_OUTPUT, + SECTION_PARAMS, + SECTION_RESOURCES, + SECTION_VERSION, - SECTION_MESSAGE, - SECTION_WILDCARD_CONSTRAINTS, - SECTION_BENCHMARK, - SECTION_PRIORITY, - SECTION_WRAPPER + SECTION_MESSAGE, + SECTION_WILDCARD_CONSTRAINTS, + SECTION_BENCHMARK, + SECTION_PRIORITY, + SECTION_WRAPPER ) /** @@ -144,21 +159,21 @@ object SnakemakeAPI { * expand wildcards. */ val SMK_SL_INITIAL_TYPE_ACCESSIBLE_SECTIONS = setOf( - SECTION_INPUT, - SECTION_OUTPUT, SECTION_LOG, - SECTION_THREADS, SECTION_PARAMS, - SECTION_RESOURCES, - SECTION_VERSION + SECTION_INPUT, + SECTION_OUTPUT, SECTION_LOG, + SECTION_THREADS, SECTION_PARAMS, + SECTION_RESOURCES, + SECTION_VERSION ) val SECTIONS_INVALID_FOR_INJECTION = setOf( - SECTION_WILDCARD_CONSTRAINTS, - SECTION_SHADOW, - SECTION_WRAPPER, - SECTION_VERSION, SECTION_THREADS, - SECTION_PRIORITY, SECTION_SINGULARITY, SECTION_CACHE, - SECTION_CONTAINER, SECTION_CONTAINERIZED, SECTION_NOTEBOOK, - SECTION_ENVMODULES, SECTION_HANDOVER + SECTION_WILDCARD_CONSTRAINTS, + SECTION_SHADOW, + SECTION_WRAPPER, + SECTION_VERSION, SECTION_THREADS, + SECTION_PRIORITY, SECTION_SINGULARITY, SECTION_CACHE, + SECTION_CONTAINER, SECTION_CONTAINERIZED, SECTION_NOTEBOOK, + SECTION_ENVMODULES, SECTION_HANDOVER ) /** @@ -167,9 +182,9 @@ object SnakemakeAPI { * TODO: Consider implementing this as PSI interface in order not to compare keyword string each time */ val WILDCARDS_EXPANDING_SECTIONS_KEYWORDS = setOf( - SECTION_INPUT, SECTION_OUTPUT, SECTION_CONDA, - SECTION_RESOURCES, SECTION_GROUP, SECTION_BENCHMARK, - SECTION_LOG, SECTION_PARAMS + SECTION_INPUT, SECTION_OUTPUT, SECTION_CONDA, + SECTION_RESOURCES, SECTION_GROUP, SECTION_BENCHMARK, + SECTION_LOG, SECTION_PARAMS ) /** @@ -178,7 +193,7 @@ object SnakemakeAPI { * TODO: Consider implementing this as PSI interface in order not to compare keyword string each time */ val WILDCARDS_DEFINING_SECTIONS_KEYWORDS = listOf( - SECTION_OUTPUT, SECTION_LOG, SECTION_BENCHMARK + SECTION_OUTPUT, SECTION_LOG, SECTION_BENCHMARK ) /** @@ -206,31 +221,41 @@ object SnakemakeAPI { SMK_VARS_ATTEMPT ) ) - val SECTION_LAMBDA_ARG_POSSIBLE_PARAMS: Set = ALLOWED_LAMBDA_OR_CALLABLE_ARGS.values.flatMap { it.asIterable() }.toMutableSet().also { - it.addAll(RULE_OR_CHECKPOINT_ARGS_SECTION_KEYWORDS) - } + val SECTION_LAMBDA_ARG_POSSIBLE_PARAMS: Set = + ALLOWED_LAMBDA_OR_CALLABLE_ARGS.values.flatMap { it.asIterable() }.toMutableSet().also { + it.addAll(RULE_OR_CHECKPOINT_ARGS_SECTION_KEYWORDS) + } /** - * Set of rule\checkpoint sections that does not expect keyword arguments + * Rule/checkpoint sections that does not allow keyword arguments */ val SECTIONS_WHERE_KEYWORD_ARGS_PROHIBITED = setOf( - SECTION_BENCHMARK, SECTION_VERSION, SECTION_MESSAGE, SECTION_SHELL, SECTION_THREADS, SECTION_SINGULARITY, - SECTION_PRIORITY, SECTION_GROUP, SECTION_SHADOW, SECTION_CONDA, SECTION_SCRIPT, SECTION_WRAPPER, - SECTION_CWL, SECTION_NOTEBOOK, SECTION_CACHE, SECTION_CONTAINER, SECTION_CONTAINERIZED, SECTION_ENVMODULES, - SECTION_NAME, SECTION_HANDOVER + SECTION_BENCHMARK, SECTION_VERSION, SECTION_MESSAGE, SECTION_SHELL, SECTION_THREADS, SECTION_SINGULARITY, + SECTION_PRIORITY, SECTION_GROUP, SECTION_SHADOW, SECTION_CONDA, SECTION_SCRIPT, SECTION_WRAPPER, + SECTION_CWL, SECTION_NOTEBOOK, SECTION_CACHE, SECTION_CONTAINER, SECTION_CONTAINERIZED, SECTION_ENVMODULES, + SECTION_NAME, SECTION_HANDOVER + ) + + + /** + * Workflow top-level sections that does not allow keyword args + */ + val WORKFLOWS_WHERE_KEYWORD_ARGS_PROHIBITED = setOf( + WORKFLOW_CONTAINERIZED_KEYWORD, WORKFLOW_CONTAINER_KEYWORD, + WORKFLOW_SINGULARITY_KEYWORD ) 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, SECTION_LOG, SECTION_BENCHMARK), - 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) + 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, SECTION_LOG, SECTION_BENCHMARK), + 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) ) val SMK_API_PKG_NAME_SMK = "snakemake" diff --git a/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkSectionMultipleArgsInspection.kt b/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkSectionMultipleArgsInspection.kt index efd86900a..9dc9ab571 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkSectionMultipleArgsInspection.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkSectionMultipleArgsInspection.kt @@ -5,14 +5,17 @@ import com.intellij.codeInspection.ProblemsHolder import com.jetbrains.python.psi.PyArgumentList import com.jetbrains.snakecharm.SnakemakeBundle import com.jetbrains.snakecharm.codeInsight.SnakemakeAPI.SINGLE_ARGUMENT_SECTIONS_KEYWORDS +import com.jetbrains.snakecharm.codeInsight.SnakemakeAPI.SINGLE_ARGUMENT_WORKFLOWS_KEYWORDS +import com.jetbrains.snakecharm.lang.psi.SmkArgsSection import com.jetbrains.snakecharm.lang.psi.SmkRuleOrCheckpointArgsSection import com.jetbrains.snakecharm.lang.psi.SmkSubworkflowArgsSection +import com.jetbrains.snakecharm.lang.psi.SmkWorkflowArgsSection class SmkSectionMultipleArgsInspection : SnakemakeInspection() { override fun buildVisitor( - holder: ProblemsHolder, - isOnTheFly: Boolean, - session: LocalInspectionToolSession + holder: ProblemsHolder, + isOnTheFly: Boolean, + session: LocalInspectionToolSession, ) = object : SnakemakeInspectionVisitor(holder, session) { override fun visitSmkSubworkflowArgsSection(st: SmkSubworkflowArgsSection) { @@ -20,22 +23,31 @@ class SmkSectionMultipleArgsInspection : SnakemakeInspection() { } override fun visitSmkRuleOrCheckpointArgsSection(st: SmkRuleOrCheckpointArgsSection) { - if (st.name in SINGLE_ARGUMENT_SECTIONS_KEYWORDS) { - checkArgumentList(st.argumentList, st.name!!) + checkArgumentList(st, SINGLE_ARGUMENT_SECTIONS_KEYWORDS) + } + + override fun visitSmkWorkflowArgsSection(st: SmkWorkflowArgsSection) { + checkArgumentList(st, SINGLE_ARGUMENT_WORKFLOWS_KEYWORDS) + } + + private fun checkArgumentList(st: SmkArgsSection, sectionKeywords: Set) { + val keyword = st.sectionKeyword + if (keyword != null && keyword in sectionKeywords) { + checkArgumentList(st.argumentList, keyword) } } private fun checkArgumentList( - argumentList: PyArgumentList?, - sectionName: String + argumentList: PyArgumentList?, + sectionName: String, ) { val args = argumentList?.arguments ?: emptyArray() if (args.size > 1) { args.forEachIndexed { i, arg -> if (i > 0) { registerProblem( - arg, - SnakemakeBundle.message("INSP.NAME.section.multiple.args.message", sectionName) + arg, + SnakemakeBundle.message("INSP.NAME.section.multiple.args.message", sectionName) ) } } diff --git a/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkSectionUnexpectedKeywordArgsInspection.kt b/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkSectionUnexpectedKeywordArgsInspection.kt index dcae6cde9..627a78304 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkSectionUnexpectedKeywordArgsInspection.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkSectionUnexpectedKeywordArgsInspection.kt @@ -6,15 +6,17 @@ import com.jetbrains.python.psi.PyArgumentList import com.jetbrains.python.psi.PyKeywordArgument import com.jetbrains.snakecharm.SnakemakeBundle import com.jetbrains.snakecharm.codeInsight.SnakemakeAPI.SECTIONS_WHERE_KEYWORD_ARGS_PROHIBITED +import com.jetbrains.snakecharm.codeInsight.SnakemakeAPI.WORKFLOWS_WHERE_KEYWORD_ARGS_PROHIBITED import com.jetbrains.snakecharm.lang.psi.SmkArgsSection import com.jetbrains.snakecharm.lang.psi.SmkRuleOrCheckpointArgsSection import com.jetbrains.snakecharm.lang.psi.SmkSubworkflowArgsSection +import com.jetbrains.snakecharm.lang.psi.SmkWorkflowArgsSection class SmkSectionUnexpectedKeywordArgsInspection : SnakemakeInspection() { override fun buildVisitor( - holder: ProblemsHolder, - isOnTheFly: Boolean, - session: LocalInspectionToolSession + holder: ProblemsHolder, + isOnTheFly: Boolean, + session: LocalInspectionToolSession, ) = object : SnakemakeInspectionVisitor(holder, session) { override fun visitSmkSubworkflowArgsSection(st: SmkSubworkflowArgsSection) { @@ -22,24 +24,36 @@ class SmkSectionUnexpectedKeywordArgsInspection : SnakemakeInspection() { } override fun visitSmkRuleOrCheckpointArgsSection(st: SmkRuleOrCheckpointArgsSection) { - if (st.sectionKeyword in SECTIONS_WHERE_KEYWORD_ARGS_PROHIBITED) { + checkArgumentList(st, SECTIONS_WHERE_KEYWORD_ARGS_PROHIBITED) + } + + override fun visitSmkWorkflowArgsSection(st: SmkWorkflowArgsSection) { + checkArgumentList(st, WORKFLOWS_WHERE_KEYWORD_ARGS_PROHIBITED) + } + + private fun checkArgumentList( + st: SmkArgsSection, + sectionKeywords: Set, + ) { + val keyword = st.sectionKeyword + if (keyword != null && keyword in sectionKeywords) { checkArgumentList(st.argumentList, st) } } private fun checkArgumentList( - argumentList: PyArgumentList?, - section: SmkArgsSection + argumentList: PyArgumentList?, + section: SmkArgsSection, ) { val args = argumentList?.arguments ?: emptyArray() args.forEach { arg -> if (arg is PyKeywordArgument) { registerProblem( - arg, - SnakemakeBundle.message( - "INSP.NAME.section.unexpected.keyword.args.message", - section.sectionKeyword!! - ) + arg, + SnakemakeBundle.message( + "INSP.NAME.section.unexpected.keyword.args.message", + section.sectionKeyword!! + ) ) } } diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/SnakemakeNames.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/SnakemakeNames.kt index b7d540fb3..679d769ae 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/lang/SnakemakeNames.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/SnakemakeNames.kt @@ -8,30 +8,26 @@ object SnakemakeNames { const val RULE_KEYWORD = "rule" const val CHECKPOINT_KEYWORD = "checkpoint" + const val WORKFLOW_ONSUCCESS_KEYWORD = "onsuccess" + const val WORKFLOW_ONERROR_KEYWORD = "onerror" + const val WORKFLOW_ONSTART_KEYWORD = "onstart" + const val WORKFLOW_LOCALRULES_KEYWORD = "localrules" + const val WORKFLOW_RULEORDER_KEYWORD = "ruleorder" const val WORKFLOW_CONFIGFILE_KEYWORD = "configfile" const val WORKFLOW_REPORT_KEYWORD = "report" const val WORKFLOW_WILDCARD_CONSTRAINTS_KEYWORD = "wildcard_constraints" const val WORKFLOW_SINGULARITY_KEYWORD = "singularity" const val WORKFLOW_INCLUDE_KEYWORD = "include" const val WORKFLOW_WORKDIR_KEYWORD = "workdir" - - const val WORKFLOW_LOCALRULES_KEYWORD = "localrules" - - const val WORKFLOW_RULEORDER_KEYWORD = "ruleorder" - - const val WORKFLOW_ONSUCCESS_KEYWORD = "onsuccess" - const val WORKFLOW_ONERROR_KEYWORD = "onerror" - const val WORKFLOW_ONSTART_KEYWORD = "onstart" + const val WORKFLOW_ENVVARS_KEYWORD = "envvars" + const val WORKFLOW_CONTAINER_KEYWORD = "container" + const val WORKFLOW_CONTAINERIZED_KEYWORD = "containerized" // => 6.0.0 const val SUBWORKFLOW_KEYWORD = "subworkflow" - const val SUBWORKFLOW_WORKDIR_KEYWORD = WORKFLOW_WORKDIR_KEYWORD - const val SUBWORKFLOW_CONFIGFILE_KEYWORD = WORKFLOW_CONFIGFILE_KEYWORD + const val SUBWORKFLOW_WORKDIR_KEYWORD = WORKFLOW_WORKDIR_KEYWORD + const val SUBWORKFLOW_CONFIGFILE_KEYWORD = WORKFLOW_CONFIGFILE_KEYWORD const val SUBWORKFLOW_SNAKEFILE_KEYWORD = "snakefile" - const val WORKFLOW_ENVVARS_KEYWORD = "envvars" - const val WORKFLOW_CONTAINER_KEYWORD = "container" - const val WORKFLOW_CONTAINERIZED_KEYWORD = "containerized" - const val SECTION_INPUT = "input" const val SECTION_OUTPUT = "output" const val SECTION_LOG = "log" @@ -39,9 +35,9 @@ object SnakemakeNames { const val SECTION_VERSION = "version" const val SECTION_MESSAGE = "message" const val SECTION_THREADS = "threads" - const val SECTION_SINGULARITY = "singularity" + const val SECTION_SINGULARITY = WORKFLOW_SINGULARITY_KEYWORD const val SECTION_PRIORITY = "priority" - const val SECTION_WILDCARD_CONSTRAINTS = "wildcard_constraints" + const val SECTION_WILDCARD_CONSTRAINTS = WORKFLOW_WILDCARD_CONSTRAINTS_KEYWORD const val SECTION_GROUP = "group" const val SECTION_CONDA = "conda" // >= 4.8 const val SECTION_RESOURCES = "resources" @@ -53,8 +49,8 @@ object SnakemakeNames { const val SECTION_SHADOW = "shadow" const val SECTION_RUN = "run" const val SECTION_CACHE = "cache" // >= 5.12.0 - const val SECTION_CONTAINER = "container" - const val SECTION_CONTAINERIZED = "containerized" // => 6.0.0 + const val SECTION_CONTAINER = WORKFLOW_CONTAINER_KEYWORD + const val SECTION_CONTAINERIZED = WORKFLOW_CONTAINERIZED_KEYWORD const val SECTION_NOTEBOOK = "notebook" const val SECTION_ENVMODULES = "envmodules" // >= 5.9 const val SECTION_NAME = "name" // >= 5.31 diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/validation/SmkSyntaxErrorAnnotator.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/validation/SmkSyntaxErrorAnnotator.kt index 8cb2b8a30..21bd1cd5e 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/lang/validation/SmkSyntaxErrorAnnotator.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/validation/SmkSyntaxErrorAnnotator.kt @@ -11,14 +11,22 @@ import com.jetbrains.snakecharm.lang.psi.* object SmkSyntaxErrorAnnotator : SmkAnnotator() { override fun visitSmkRuleOrCheckpointArgsSection(st: SmkRuleOrCheckpointArgsSection) { - if (!SnakemakeLanguageDialect.isInsideSmkFile(st)) { + findAndHighlightIncorrectArguments(st) + } + + override fun visitSmkWorkflowArgsSection(st: SmkWorkflowArgsSection) { + findAndHighlightIncorrectArguments(st) + } + + private fun findAndHighlightIncorrectArguments(argsSection: SmkArgsSection) { + if (!SnakemakeLanguageDialect.isInsideSmkFile(argsSection)) { return } val seenKeywords2Value = HashMap() var encounteredKeywordArgument = false - st.argumentList?.arguments?.forEach { arg -> + argsSection.argumentList?.arguments?.forEach { arg -> when (arg) { is PyKeywordArgument -> { arg.keyword?.let { keyword -> @@ -30,7 +38,7 @@ object SmkSyntaxErrorAnnotator : SmkAnnotator() { holder.newAnnotation( ERROR, SnakemakeBundle.message("ANN.keyword.argument.already.provided", keywordValue) - ).range(arg.keywordNode!!).create() + ).range(arg.keywordNode ?: return@let).create() } } encounteredKeywordArgument = true diff --git a/src/test/resources/features/highlighting/inspections/multiple_args_inspection.feature b/src/test/resources/features/highlighting/inspections/multiple_args_inspection.feature index c3b5be5f3..645eb813a 100644 --- a/src/test/resources/features/highlighting/inspections/multiple_args_inspection.feature +++ b/src/test/resources/features/highlighting/inspections/multiple_args_inspection.feature @@ -58,4 +58,26 @@ Feature: Inspection for multiple arguments in various sections | notebook | | container | | containerized | - | handover | \ No newline at end of file + | handover | + + Scenario Outline: Multiple arguments in workflow section + Given a snakemake project + Given I open a file "foo.smk" with text + """ + : "a", "b", "c" + """ + And SmkSectionMultipleArgsInspection inspection is enabled + Then I expect inspection error on <"b"> with message + """ + Only one argument is allowed for '' section. + """ + And I expect inspection error on <"c"> with message + """ + Only one argument is allowed for '' section. + """ + When I check highlighting errors + Examples: + | section_name | + | containerized | + | singularity | + | container | diff --git a/src/test/resources/features/highlighting/inspections/unexpected_keyword_args_inspection.feature b/src/test/resources/features/highlighting/inspections/unexpected_keyword_args_inspection.feature index e96d34227..3ef0c54ff 100644 --- a/src/test/resources/features/highlighting/inspections/unexpected_keyword_args_inspection.feature +++ b/src/test/resources/features/highlighting/inspections/unexpected_keyword_args_inspection.feature @@ -74,3 +74,21 @@ Feature: Inspection for unexpected keyword arguments in section | checkpoint | log | | checkpoint | resources | | checkpoint | wildcard_constraints | + + Scenario Outline: Unexpected keyword arguments in workflow + Given a snakemake project + Given I open a file "foo.smk" with text + """ + : a="foo.bar" + """ + And SmkSectionUnexpectedKeywordArgsInspection inspection is enabled + Then I expect inspection error on with message + """ + Section '' does not support keyword arguments + """ + When I check highlighting errors + Examples: + | section_name | + | containerized | + | singularity | + | container | diff --git a/src/test/resources/features/highlighting/smk_syntax_error_annotator.feature b/src/test/resources/features/highlighting/smk_syntax_error_annotator.feature index 7c0d3957c..f4809f4e7 100644 --- a/src/test/resources/features/highlighting/smk_syntax_error_annotator.feature +++ b/src/test/resources/features/highlighting/smk_syntax_error_annotator.feature @@ -20,6 +20,20 @@ Feature: Annotate syntax errors """ When I check highlighting errors + Scenario: Annotate keyword argument duplication in workflow + Given a snakemake project + Given I open a file "foo.smk" with text + """ + wildcard_constraints: + foo = ".*", + foo = "other.*" + """ + Then I expect inspection error on in with message + """ + Keyword argument already provided: foo = \".*\". + """ + When I check highlighting errors + Scenario Outline: Annotate positional argument after keyword argument Given a snakemake project Given I open a file "foo.smk" with text