diff --git a/CHANGELOG.md b/CHANGELOG.md index d19bd199..22375c4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,9 +12,10 @@ Released on - Move snakemake API addition/removal info from JAR into `extra/snakemake_api.yaml` in plugin directory - Inspection: Warn if snakemake section type or API function isn't supported in the current snakemake project based on a snakemake version (see [#500](https://github.com/JetBrains-Research/snakecharm/issues/500) - Code completion: For section keywords show 'since' version and deprecation notice in the completion list. Do not suggest already removed keywords (see [#535](https://github.com/JetBrains-Research/snakecharm/issues/535) -- + ### Fixed - Improve parser error message when rule/module is declared with name but lacks ':' (see [#515](https://github.com/JetBrains-Research/snakecharm/issues/515)) +- Support for `update` and `before_update` flags. Update inspection that warns if flag functions from `snakemake.io` is used in a wrong section, added info for all flags up to 8.23.1 version (see [#537](https://github.com/JetBrains-Research/snakecharm/issues/537)) ### Changed - TODO diff --git a/snakemake_api.yaml b/snakemake_api.yaml index 5c55c2d6..14e03b28 100644 --- a/snakemake_api.yaml +++ b/snakemake_api.yaml @@ -26,9 +26,36 @@ defaultVersion: "7.32.4" # * is_accessible_as_placeholder: False | e.g. in `shell: "{
}"` and other sections where placeholders not treated as wildcards # * placeholders_injection_allowed: False (for function), True (else) | if True, than snakemake expands placeholders before call, e.g. like in 'shell' sections in some flag functions from snakemake.io # * lambda_args: [] | e.g. lambda args for 'params' and 'input' sections are different +# * limit_to_sections: [] | some snakemake.io flag-functions could be used only in certain sections +# * docs_url: '' | Documentation URL # ========================================= changelog: + + # --------------------------------------- + - version: "8.7.0" + introduced: + - name: "snakemake.ioflags.update" + docs_url: https://snakemake.readthedocs.io/en/stable/snakefiles/rules.html#updating-existing-output-files + type: "function" + limit_to_sections: + - "output" + + - name: "snakemake.ioflags.before_update" + docs_url: https://snakemake.readthedocs.io/en/stable/snakefiles/rules.html#updating-existing-output-files + type: "function" + limit_to_sections: + - "input" + # --------------------------------------- + - version: "8.2.0" + introduced: + - name: "snakemake.io.from_queue" + docs_url: https://snakemake.readthedocs.io/en/stable/snakefiles/rules.html#continuously-updated-input + type: "function" + limit_to_sections: + - "output" + + # --------------------------------------- - version: "8.0.0" removed: - name: "snakemake.io.dynamic" @@ -74,6 +101,11 @@ changelog: keyword_args_allowed: False placeholders_injection_allowed: False + - name: "snakemake.io.service" + docs_url: https://snakemake.readthedocs.io/en/stable/snakefiles/rules.html#service-rules-jobs + type: "function" + limit_to_sections: + - "output" # ---------------------- - version: "6.15.0" @@ -202,6 +234,26 @@ changelog: type: "function" advice: "use checkpoints instead." + # ---------------------- + - version: "5.1.0" + + introduced: + - name: "snakemake.io.report" + type: "function" + docs_url: https://snakemake.readthedocs.io/en/stable/snakefiles/reporting.html#reports + limit_to_sections: + - "output" + + # ---------------------- + - version: "5.0.0" + + introduced: + - name: "snakemake.io.pipe" + type: "function" + docs_url: https://snakemake.readthedocs.io/en/stable/snakefiles/rules.html#piped-output + limit_to_sections: + - "output" + # ---------------------- - version: "4.8.0" @@ -396,11 +448,14 @@ changelog: - name: "snakemake.io.ancient" type: "function" + docs_url: https://snakemake.readthedocs.io/en/stable/snakefiles/rules.html#ignoring-timestamps + placeholders_injection_allowed: True limit_to_sections: - "input" - name: "snakemake.io.protected" type: "function" + docs_url: https://snakemake.readthedocs.io/en/stable/snakefiles/rules.html#protected-and-temporary-files limit_to_sections: - "output" - "log" @@ -408,43 +463,49 @@ changelog: - name: "snakemake.io.directory" type: "function" + docs_url: https://snakemake.readthedocs.io/en/stable/snakefiles/rules.html#directories-as-outputs limit_to_sections: - "output" - - name: "snakemake.io.report" + - name: "snakemake.io.temp" type: "function" + docs_url: https://snakemake.readthedocs.io/en/stable/snakefiles/rules.html#protected-and-temporary-files limit_to_sections: - "output" - - name: "snakemake.io.temp" + - name: "snakemake.io.temporary" type: "function" + docs_url: https://snakemake.readthedocs.io/en/stable/snakefiles/rules.html#protected-and-temporary-files limit_to_sections: - - "input" - "output" - name: "snakemake.io.touch" type: "function" + docs_url: https://snakemake.readthedocs.io/en/stable/snakefiles/rules.html#flag-files limit_to_sections: - "output" - "benchmark" - "log" - - name: "snakemake.io.pipe" - type: "function" - limit_to_sections: - - "output" - - name: "snakemake.io.unpack" type: "function" + docs_url: https://snakemake.readthedocs.io/en/stable/snakefiles/rules.html#input-functions-and-unpack limit_to_sections: - "input" - name: "snakemake.io.repeat" type: "function" + docs_url: https://snakemake.readthedocs.io/en/stable/tutorial/additional_features.html#benchmarking limit_to_sections: - "benchmark" - name: "snakemake.io.dynamic" type: "function" limit_to_sections: - - "output" \ No newline at end of file + - "output" + + - name: "snakemake.io.local" + type: "function" + docs_url: https://snakemake.readthedocs.io/en/stable/snakefiles/storage.html#local-input-output-files + limit_to_sections: + - "input" \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/snakecharm/codeInsight/ImplicitPySymbolsProvider.kt b/src/main/kotlin/com/jetbrains/snakecharm/codeInsight/ImplicitPySymbolsProvider.kt index 083d5d90..1c890253 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/codeInsight/ImplicitPySymbolsProvider.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/codeInsight/ImplicitPySymbolsProvider.kt @@ -30,10 +30,12 @@ import com.jetbrains.python.psi.resolve.fromSdk import com.jetbrains.python.psi.resolve.resolveQualifiedName import com.jetbrains.python.psi.types.TypeEvalContext import com.jetbrains.snakecharm.SnakemakeBundle +import com.jetbrains.snakecharm.codeInsight.SnakemakeAPI.SECTION_ACCESSOR_CLASSES import com.jetbrains.snakecharm.codeInsight.SnakemakeAPI.SMK_API_VERS_6_1 import com.jetbrains.snakecharm.codeInsight.completion.SmkCompletionUtil import com.jetbrains.snakecharm.framework.SmkSupportProjectSettings import com.jetbrains.snakecharm.framework.SmkSupportProjectSettingsListener +import com.jetbrains.snakecharm.lang.SnakemakeNames.SNAKEMAKE_MODULE_NAME_IO import java.util.* import javax.swing.SwingUtilities @@ -132,7 +134,12 @@ class ImplicitPySymbolsProvider( /////////////////////////////////////// // E.g. expand, temp, .. from 'snakemake.io' collectTopLevelMethodsFrom( - "snakemake.io", SmkCodeInsightScope.TOP_LEVEL, usedFiles, sdk, elementsCache + SNAKEMAKE_MODULE_NAME_IO, SmkCodeInsightScope.TOP_LEVEL, usedFiles, sdk, elementsCache + ) + progressIndicator?.checkCanceled() + // E.g. flags 'update',.. from 'snakemake.ioflags' + collectTopLevelMethodsFrom( + "snakemake.ioflags", SmkCodeInsightScope.TOP_LEVEL, usedFiles, sdk, elementsCache ) progressIndicator?.checkCanceled() @@ -177,15 +184,12 @@ class ImplicitPySymbolsProvider( // snakemake.io.Wildcards // snakemake.io.Resources collectTopLevelClassesInheretedFrom( - "snakemake.io", + SNAKEMAKE_MODULE_NAME_IO, "snakemake.io.Namedlist", SmkCodeInsightScope.RULELIKE_RUN_SECTION, usedFiles, sdk, elementsCache - ) { className -> - when (className) { - "InputFiles" -> "input" - "OutputFiles" -> "output" - else -> className.lowercase(Locale.getDefault()) - } + ) { classFqn -> + val sectionName = SECTION_ACCESSOR_CLASSES[classFqn] + sectionName ?: classFqn.split('.').last().lowercase(Locale.getDefault()) } progressIndicator?.checkCanceled() @@ -467,7 +471,7 @@ class ImplicitPySymbolsProvider( usedFiles: MutableSet, sdk: Sdk, elementsCache: MutableList, - className2VarNameFun: (String) -> String + classFqn2VarNameFun: (String) -> String ) { val pyFiles = collectPyFiles(pyModuleFqn, usedFiles, sdk) @@ -484,7 +488,8 @@ class ImplicitPySymbolsProvider( ) if (parentClassRequirement == null || pyClass.inherits(typeEvalContext, parentClassRequirement)) { - val varName = className2VarNameFun(pyClass.name!!) + val fqn = pyClass.qualifiedName ?: pyClass.name!! + val varName = classFqn2VarNameFun(fqn) elementsCache.add( ImplicitPySymbol( varName, diff --git a/src/main/kotlin/com/jetbrains/snakecharm/codeInsight/SnakemakeAPI.kt b/src/main/kotlin/com/jetbrains/snakecharm/codeInsight/SnakemakeAPI.kt index a2d5169d..d2430b39 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/codeInsight/SnakemakeAPI.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/codeInsight/SnakemakeAPI.kt @@ -89,8 +89,6 @@ object SnakemakeAPI { "snakemake.io.Log" to "log", "snakemake.io.Resources" to "resources" ) - const val SNAKEMAKE_MODULE_NAME_IO_PY = "io.py" - const val SNAKEMAKE_MODULE_NAME_UTILS_PY = "utils.py" /** * Sections that execute external script with access to 'snakemake' object, i.e to 'snakemake.input', diff --git a/src/main/kotlin/com/jetbrains/snakecharm/framework/snakemakeAPIAnnotations/SmkAPIAnnParsingTypes.kt b/src/main/kotlin/com/jetbrains/snakecharm/framework/snakemakeAPIAnnotations/SmkAPIAnnParsingTypes.kt index 63ad124c..85b0a55e 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/framework/snakemakeAPIAnnotations/SmkAPIAnnParsingTypes.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/framework/snakemakeAPIAnnotations/SmkAPIAnnParsingTypes.kt @@ -23,6 +23,8 @@ data class SmkAPIAnnParsingIntroductionRecord( override val name: String = "", override val type: String = "", val advice: String = "", + val docs_url: String = "", + // for sections: val lambda_args: List = emptyList(), val section: Boolean = true, diff --git a/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkMinVersionWarningInspection.kt b/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkMinVersionWarningInspection.kt index 71e5646a..8ddd2747 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkMinVersionWarningInspection.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkMinVersionWarningInspection.kt @@ -7,9 +7,9 @@ import com.jetbrains.python.psi.PyCallExpression import com.jetbrains.python.psi.PyFunction import com.jetbrains.python.psi.PyStringLiteralExpression import com.jetbrains.snakecharm.SnakemakeBundle -import com.jetbrains.snakecharm.codeInsight.SnakemakeAPI.SNAKEMAKE_MODULE_NAME_UTILS_PY import com.jetbrains.snakecharm.framework.SmkSupportProjectSettings import com.jetbrains.snakecharm.lang.SmkLanguageVersion +import com.jetbrains.snakecharm.lang.SnakemakeNames.SNAKEMAKE_FQN_FUN_MIN_VERSION class SmkMinVersionWarningInspection : SnakemakeInspection() { override fun buildVisitor( @@ -18,7 +18,6 @@ class SmkMinVersionWarningInspection : SnakemakeInspection() { session: LocalInspectionToolSession, ) = object : SnakemakeInspectionVisitor(holder, getContext(session)) { - @Suppress("UnstableApiUsage") override fun visitPyCallExpression(node: PyCallExpression) { val callee = node.callee if (callee?.name != "min_version") { @@ -28,7 +27,7 @@ class SmkMinVersionWarningInspection : SnakemakeInspection() { val reference = callee.reference val resolveResult = reference?.resolve() val function = resolveResult as? PyFunction ?: return - if (function.name != "min_version" || function.containingFile.name != SNAKEMAKE_MODULE_NAME_UTILS_PY) { + if (function.qualifiedName != SNAKEMAKE_FQN_FUN_MIN_VERSION) { return } diff --git a/src/main/kotlin/com/jetbrains/snakecharm/inspections/smksl/SmkSLUndeclaredSectionInspection.kt b/src/main/kotlin/com/jetbrains/snakecharm/inspections/smksl/SmkSLUndeclaredSectionInspection.kt index 9c261070..33dd15f5 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/inspections/smksl/SmkSLUndeclaredSectionInspection.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/inspections/smksl/SmkSLUndeclaredSectionInspection.kt @@ -3,14 +3,15 @@ package com.jetbrains.snakecharm.inspections.smksl import com.intellij.codeInspection.LocalInspectionToolSession import com.intellij.codeInspection.ProblemsHolder import com.intellij.psi.PsiReference +import com.jetbrains.python.extensions.getQName import com.jetbrains.python.psi.PyClass import com.jetbrains.snakecharm.SnakemakeBundle import com.jetbrains.snakecharm.codeInsight.SnakemakeAPI.SECTION_ACCESSOR_CLASSES -import com.jetbrains.snakecharm.codeInsight.SnakemakeAPI.SNAKEMAKE_MODULE_NAME_IO_PY import com.jetbrains.snakecharm.codeInsight.SnakemakeAPIProjectService import com.jetbrains.snakecharm.inspections.SnakemakeInspection import com.jetbrains.snakecharm.inspections.smksl.SmkSLUndeclaredSectionInspectionUtil.checkIsSectionNameUnresolved import com.jetbrains.snakecharm.inspections.smksl.SmkSLUndeclaredSectionInspectionUtil.isSectionNameOfInterest +import com.jetbrains.snakecharm.lang.SnakemakeNames.SNAKEMAKE_MODULE_NAME_IO import com.jetbrains.snakecharm.lang.psi.SmkRuleOrCheckpoint import com.jetbrains.snakecharm.stringLanguage.lang.psi.SmkSLReferenceExpression import com.jetbrains.snakecharm.stringLanguage.lang.psi.references.SmkSLInitialReference @@ -55,8 +56,8 @@ object SmkSLUndeclaredSectionInspectionUtil { return when (declaration) { null -> true is PyClass -> { - // is resolved to io.py - declaration.containingFile.name == SNAKEMAKE_MODULE_NAME_IO_PY + // is resolved to snakemake/io.py + declaration.containingFile.getQName()?.toString() == SNAKEMAKE_MODULE_NAME_IO || declaration.qualifiedName in SECTION_ACCESSOR_CLASSES } diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/SnakemakeNames.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/SnakemakeNames.kt index 3caff003..e5f9ac7b 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/lang/SnakemakeNames.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/SnakemakeNames.kt @@ -68,4 +68,13 @@ object SnakemakeNames { const val RUN_SECTION_VARIABLE_JOBID = "jobid" const val SNAKEMAKE_METHOD_MULTIEXT = "multiext" + + /** + * Constant that holds the module name for Snakemake input/output operations. + * It is used as part of the code insight and symbol resolution process within the Snakemake plugin. + * + * For more details, refer to the class `com.jetbrains.snakecharm.codeInsight.ImplicitPySymbolsProvider`. + */ + const val SNAKEMAKE_MODULE_NAME_IO = "snakemake.io" + const val SNAKEMAKE_FQN_FUN_MIN_VERSION = "snakemake.utils.min_version" } \ No newline at end of file diff --git a/src/test/resources/features/completion/implicit_py_symbols_completion.feature b/src/test/resources/features/completion/implicit_py_symbols_completion.feature index 9d520395..71b490c1 100644 --- a/src/test/resources/features/completion/implicit_py_symbols_completion.feature +++ b/src/test/resources/features/completion/implicit_py_symbols_completion.feature @@ -10,20 +10,21 @@ Feature: Completion in python part of snakemake file When I put the caret after foo = 1; And I invoke autocompletion popup Then completion list should contain: - | expand | - | temp | - | directory | - | directory | - | protected | - | touch | - | unpack | - | ancient | - | ensure | - | shell | - | config | - | rules | - | input | - | pep | + | expand | + | temp | + | update | + | before_update | + | directory | + | protected | + | touch | + | unpack | + | ancient | + | ensure | + | shell | + | config | + | rules | + | input | + | pep | Scenario: Complete at top-level (GTE 6.1) Given a snakemake:6.1 project diff --git a/src/test/resources/features/resolve/implicit_py_symbols_resolve.feature b/src/test/resources/features/resolve/implicit_py_symbols_resolve.feature index 619996ac..ec52aee8 100644 --- a/src/test/resources/features/resolve/implicit_py_symbols_resolve.feature +++ b/src/test/resources/features/resolve/implicit_py_symbols_resolve.feature @@ -49,29 +49,31 @@ Feature: Resolve implicitly imported python names Then reference should resolve to "" in "" Examples: - | smk_vers | ptn | text | symbol_name | file | - | snakemake:5x | ru | rules | rules | workflow.py | - | snakemake:5x | ru | rules.foo | rules | workflow.py | - | snakemake:6.1 | ru | rules | Rules | common.py | - | snakemake:6.1 | ru | rules.foo | Rules | common.py | - | snakemake:6.5 | ru | rules | Rules | __init__.py | - | snakemake:6.5 | ru | rules.foo | Rules | __init__.py | - | snakemake:6.1 | dyn | dynamic() | dynamic | io.py | - | snakemake:7.32.4 | dyn | dynamic() | dynamic | io.py | - | snakemake | exp | expand() | expand | io.py | - | snakemake | tem | temp() | temp | io.py | - | snakemake | dir | directory() | directory | io.py | - | snakemake | dir | directory() | directory | io.py | - | snakemake | pro | protected() | protected | io.py | - | snakemake | tou | touch() | touch | io.py | - | snakemake | un | unpack() | unpack | io.py | - | snakemake | anc | ancient() | ancient | io.py | - | snakemake | ens | ensure() | ensure | io.py | - | snakemake | ru | rules | Rules | __init__.py | - | snakemake | ru | rules.foo | Rules | __init__.py | - | snakemake | inp | input | input | builtins.pyi | - | snakemake | pe | pep | __init__ | project.py | - | snakemake | pe | pep.config | __init__ | project.py | + | smk_vers | ptn | text | symbol_name | file | + | snakemake:5x | ru | rules | rules | workflow.py | + | snakemake:5x | ru | rules.foo | rules | workflow.py | + | snakemake:6.1 | ru | rules | Rules | common.py | + | snakemake:6.1 | ru | rules.foo | Rules | common.py | + | snakemake:6.5 | ru | rules | Rules | __init__.py | + | snakemake:6.5 | ru | rules.foo | Rules | __init__.py | + | snakemake:6.1 | dyn | dynamic() | dynamic | io.py | + | snakemake:7.32.4 | dyn | dynamic() | dynamic | io.py | + | snakemake | exp | expand() | expand | io.py | + | snakemake | tem | temp() | temp | io.py | + | snakemake | dir | directory() | directory | io.py | + | snakemake | dir | directory() | directory | io.py | + | snakemake | pro | protected() | protected | io.py | + | snakemake | upd | update() | update | ioflags.py | + | snakemake | bef | before_update() | before_update | ioflags.py | + | snakemake | tou | touch() | touch | io.py | + | snakemake | un | unpack() | unpack | io.py | + | snakemake | anc | ancient() | ancient | io.py | + | snakemake | ens | ensure() | ensure | io.py | + | snakemake | ru | rules | Rules | __init__.py | + | snakemake | ru | rules.foo | Rules | __init__.py | + | snakemake | inp | input | input | builtins.pyi | + | snakemake | pe | pep | __init__ | project.py | + | snakemake | pe | pep.config | __init__ | project.py | Scenario: Resolve at top-level: shell() Given a snakemake project