From 82271fa4a0c49e59dadb21a94876c972283e49d3 Mon Sep 17 00:00:00 2001 From: Dmitry Kochik <55440084+dakochik@users.noreply.github.com> Date: Thu, 5 Aug 2021 16:55:30 +0300 Subject: [PATCH] Features/#355 module use support (#403) feat: Basic 'module' and 'use' features support (#355) Resolves: #355 --- CHANGELOG.md | 1 + .../psi/elementTypes/SmkStubElementTypes.java | 14 +- .../snakecharm/codeInsight/SnakemakeAPI.kt | 37 +- .../SmkKeywordCompletionContributor.kt | 104 ++++- .../SmkModuleRedeclarationInspection.kt | 32 ++ .../SmkRuleRedeclarationInspection.kt | 33 +- .../SmkSectionMultipleArgsInspection.kt | 9 +- .../SmkSectionRedeclarationInspection.kt | 26 +- ...kSectionUnexpectedKeywordArgsInspection.kt | 9 +- .../SmkUnrecognizedSectionInspection.kt | 46 ++- .../snakecharm/lang/SmkTokenSetContributor.kt | 28 +- .../snakecharm/lang/SnakemakeNames.kt | 12 + .../lang/highlighter/SmkSyntaxAnnotator.kt | 35 +- .../lang/parser/SmkStatementParsing.kt | 369 ++++++++++++++++-- .../snakecharm/lang/parser/SmkTokenTypes.kt | 8 +- .../snakecharm/lang/parser/SnakemakeLexer.kt | 59 +-- .../snakecharm/lang/psi/SmkElementVisitor.kt | 12 + .../jetbrains/snakecharm/lang/psi/SmkFile.kt | 30 ++ .../snakecharm/lang/psi/SmkPsiElements.kt | 14 +- .../lang/psi/elementTypes/SmkElementTypes.kt | 50 ++- .../lang/psi/impl/SmkModuleArgsSectionImpl.kt | 15 + .../snakecharm/lang/psi/impl/SmkModuleImpl.kt | 26 ++ .../lang/psi/impl/SmkUseArgsSectionImpl.kt | 25 ++ .../snakecharm/lang/psi/impl/SmkUseImpl.kt | 32 ++ .../lang/psi/impl/SmkUseNameIdentifier.kt | 8 + .../refs/SmkRuleOrCheckpointNameReference.kt | 60 ++- .../psi/impl/stubs/SmkModuleElementType.kt | 23 ++ .../psi/impl/stubs/SmkRuleLikeStubImpl.kt | 52 +-- .../lang/psi/impl/stubs/SmkUseElementType.kt | 23 ++ .../snakecharm/lang/psi/stubs/SmkStubs.kt | 12 +- .../lang/psi/stubs/SmkUseNameIndex.kt | 24 ++ .../types/AbstractSmkRuleOrCheckpointType.kt | 68 ++-- .../snakecharm/lang/psi/types/SmkUsesType.kt | 17 + .../stringLanguage/lang/SmkDSInjector.kt | 7 + src/main/resources/META-INF/plugin.xml | 12 + src/main/resources/SnakemakeBundle.properties | 19 + .../SmkModuleRedeclarationInspection.html | 5 + .../lang/parser/SnakemakeLexerTest.kt | 43 ++ .../lang/parser/SnakemakeParsingTest.kt | 20 + .../completion/keywords_completion.feature | 102 ++++- .../inspections/module_redeclaration.feature | 21 + .../multiple_args_inspection.feature | 20 +- .../inspections/rule_redeclaration.feature | 36 ++ .../inspections/section_redeclaration.feature | 25 +- ...unexpected_keyword_args_inspection.feature | 51 +-- .../unrecognized_section_inspection.feature | 39 ++ .../highlighting/smk_syntax_annotator.feature | 63 ++- .../uses_and_modules_name_resolve.feature | 62 +++ testData/psi/Module.smk | 5 + testData/psi/Module.txt | 52 +++ testData/psi/ModuleIncomplete.smk | 5 + testData/psi/ModuleIncomplete.txt | 42 ++ testData/psi/Use.smk | 18 + testData/psi/Use.txt | 258 ++++++++++++ testData/psi/UseIncomplete.smk | 12 + testData/psi/UseIncomplete.txt | 152 ++++++++ testData/psi/UseInvalid.smk | 32 ++ testData/psi/UseInvalid.txt | 353 +++++++++++++++++ 58 files changed, 2510 insertions(+), 257 deletions(-) create mode 100644 src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkModuleRedeclarationInspection.kt create mode 100644 src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/SmkModuleArgsSectionImpl.kt create mode 100644 src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/SmkModuleImpl.kt create mode 100644 src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/SmkUseArgsSectionImpl.kt create mode 100644 src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/SmkUseImpl.kt create mode 100644 src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/SmkUseNameIdentifier.kt create mode 100644 src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/stubs/SmkModuleElementType.kt create mode 100644 src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/stubs/SmkUseElementType.kt create mode 100644 src/main/kotlin/com/jetbrains/snakecharm/lang/psi/stubs/SmkUseNameIndex.kt create mode 100644 src/main/kotlin/com/jetbrains/snakecharm/lang/psi/types/SmkUsesType.kt create mode 100644 src/main/kotlin/com/jetbrains/snakecharm/stringLanguage/lang/SmkDSInjector.kt create mode 100644 src/main/resources/inspectionDescriptions/SmkModuleRedeclarationInspection.html create mode 100644 src/test/resources/features/highlighting/inspections/module_redeclaration.feature create mode 100644 src/test/resources/features/resolve/uses_and_modules_name_resolve.feature create mode 100644 testData/psi/Module.smk create mode 100644 testData/psi/Module.txt create mode 100644 testData/psi/ModuleIncomplete.smk create mode 100644 testData/psi/ModuleIncomplete.txt create mode 100644 testData/psi/Use.smk create mode 100644 testData/psi/Use.txt create mode 100644 testData/psi/UseIncomplete.smk create mode 100644 testData/psi/UseIncomplete.txt create mode 100644 testData/psi/UseInvalid.smk create mode 100644 testData/psi/UseInvalid.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 125488d63..f26aea49b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ Released on ... - TODO (see [#NNN](https://github.com/JetBrains-Research/snakecharm/issues/NNN)) ### Added +- Support for 'module' and 'use' keywords (see [#355](https://github.com/JetBrains-Research/snakecharm/issues/355)) - Inspection for improperly called functions (see [#148](https://github.com/JetBrains-Research/snakecharm/issues/148)) - Ability for memorising new section name (see [#372](https://github.com/JetBrains-Research/snakecharm/issues/372)) - Support for 'handover' section (see [#362](https://github.com/JetBrains-Research/snakecharm/issues/362)) diff --git a/src/main/java/com/jetbrains/snakecharm/lang/psi/elementTypes/SmkStubElementTypes.java b/src/main/java/com/jetbrains/snakecharm/lang/psi/elementTypes/SmkStubElementTypes.java index fea80953d..365b1fa34 100644 --- a/src/main/java/com/jetbrains/snakecharm/lang/psi/elementTypes/SmkStubElementTypes.java +++ b/src/main/java/com/jetbrains/snakecharm/lang/psi/elementTypes/SmkStubElementTypes.java @@ -1,18 +1,14 @@ package com.jetbrains.snakecharm.lang.psi.elementTypes; import com.intellij.psi.stubs.IStubElementType; -import com.jetbrains.snakecharm.lang.psi.SmkCheckPoint; -import com.jetbrains.snakecharm.lang.psi.SmkRule; -import com.jetbrains.snakecharm.lang.psi.SmkSubworkflow; -import com.jetbrains.snakecharm.lang.psi.impl.stubs.SmkCheckpointElementType; -import com.jetbrains.snakecharm.lang.psi.impl.stubs.SmkRuleElementType; -import com.jetbrains.snakecharm.lang.psi.impl.stubs.SmkSubworkflowElementType; -import com.jetbrains.snakecharm.lang.psi.stubs.SmkCheckpointStub; -import com.jetbrains.snakecharm.lang.psi.stubs.SmkRuleStub; -import com.jetbrains.snakecharm.lang.psi.stubs.SmkSubworkflowStub; +import com.jetbrains.snakecharm.lang.psi.*; +import com.jetbrains.snakecharm.lang.psi.impl.stubs.*; +import com.jetbrains.snakecharm.lang.psi.stubs.*; public interface SmkStubElementTypes { IStubElementType RULE_DECLARATION_STATEMENT = new SmkRuleElementType(); IStubElementType CHECKPOINT_DECLARATION_STATEMENT = new SmkCheckpointElementType(); IStubElementType SUBWORKFLOW_DECLARATION_STATEMENT = new SmkSubworkflowElementType(); + IStubElementType MODULE_DECLARATION_STATEMENT = new SmkModuleElementType(); + IStubElementType USE_DECLARATION_STATEMENT = new SmkUseElementType(); } diff --git a/src/main/kotlin/com/jetbrains/snakecharm/codeInsight/SnakemakeAPI.kt b/src/main/kotlin/com/jetbrains/snakecharm/codeInsight/SnakemakeAPI.kt index 1c7f222ba..f959f76df 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/codeInsight/SnakemakeAPI.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/codeInsight/SnakemakeAPI.kt @@ -1,6 +1,12 @@ package com.jetbrains.snakecharm.codeInsight import com.jetbrains.snakecharm.lang.SnakemakeNames +import com.jetbrains.snakecharm.lang.SnakemakeNames.MODULE_CONFIG_KEYWORD +import com.jetbrains.snakecharm.lang.SnakemakeNames.MODULE_META_WRAPPER_KEYWORD +import com.jetbrains.snakecharm.lang.SnakemakeNames.MODULE_REPLACE_PREFIX_KEYWORD +import com.jetbrains.snakecharm.lang.SnakemakeNames.MODULE_SKIP_VALIDATION_KEYWORD +import com.jetbrains.snakecharm.lang.SnakemakeNames.MODULE_SNAKEFILE_KEYWORD +import com.jetbrains.snakecharm.lang.SnakemakeNames.RULE_KEYWORD import com.jetbrains.snakecharm.lang.SnakemakeNames.SECTION_BENCHMARK import com.jetbrains.snakecharm.lang.SnakemakeNames.SECTION_CACHE import com.jetbrains.snakecharm.lang.SnakemakeNames.SECTION_CONDA @@ -19,6 +25,7 @@ import com.jetbrains.snakecharm.lang.SnakemakeNames.SECTION_OUTPUT import com.jetbrains.snakecharm.lang.SnakemakeNames.SECTION_PARAMS import com.jetbrains.snakecharm.lang.SnakemakeNames.SECTION_PRIORITY import com.jetbrains.snakecharm.lang.SnakemakeNames.SECTION_RESOURCES +import com.jetbrains.snakecharm.lang.SnakemakeNames.SECTION_RUN import com.jetbrains.snakecharm.lang.SnakemakeNames.SECTION_SCRIPT import com.jetbrains.snakecharm.lang.SnakemakeNames.SECTION_SHADOW import com.jetbrains.snakecharm.lang.SnakemakeNames.SECTION_SHELL @@ -27,6 +34,9 @@ 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.SMK_AS_KEYWORD +import com.jetbrains.snakecharm.lang.SnakemakeNames.SMK_FROM_KEYWORD +import com.jetbrains.snakecharm.lang.SnakemakeNames.SMK_WITH_KEYWORD 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 @@ -121,8 +131,8 @@ object SnakemakeAPI { SECTION_NAME, SECTION_HANDOVER ) - val RULE_OR_CHECKPOINT_SECTION_KEYWORDS = - (RULE_OR_CHECKPOINT_ARGS_SECTION_KEYWORDS + setOf(SnakemakeNames.SECTION_RUN)) + + val RULE_OR_CHECKPOINT_SECTION_KEYWORDS = (RULE_OR_CHECKPOINT_ARGS_SECTION_KEYWORDS + setOf(SECTION_RUN)) /** * For subworkflows parsing @@ -133,6 +143,29 @@ object SnakemakeAPI { SnakemakeNames.SUBWORKFLOW_CONFIGFILE_KEYWORD ) + /** + * For modules parsing + */ + val MODULE_SECTIONS_KEYWORDS = setOf( + MODULE_SNAKEFILE_KEYWORD, + MODULE_CONFIG_KEYWORD, + MODULE_SKIP_VALIDATION_KEYWORD, + MODULE_META_WRAPPER_KEYWORD, + MODULE_REPLACE_PREFIX_KEYWORD + ) + + /** + * For uses parsing + */ + val USE_SECTIONS_KEYWORDS = RULE_OR_CHECKPOINT_SECTION_KEYWORDS - EXECUTION_SECTIONS_KEYWORDS - SECTION_RUN + + val USE_DECLARATION_KEYWORDS = setOf( + RULE_KEYWORD, + SMK_FROM_KEYWORD, + SMK_AS_KEYWORD, + SMK_WITH_KEYWORD + ) + /** * For type inference: * Some sections in snakemake are inaccessible after `rules.NAME.
`, so this set is required diff --git a/src/main/kotlin/com/jetbrains/snakecharm/codeInsight/completion/SmkKeywordCompletionContributor.kt b/src/main/kotlin/com/jetbrains/snakecharm/codeInsight/completion/SmkKeywordCompletionContributor.kt index 001ab0d9c..475b1fa71 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/codeInsight/completion/SmkKeywordCompletionContributor.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/codeInsight/completion/SmkKeywordCompletionContributor.kt @@ -9,7 +9,6 @@ import com.intellij.patterns.PlatformPatterns import com.intellij.patterns.PlatformPatterns.psiElement import com.intellij.patterns.PsiElementPattern import com.intellij.patterns.StandardPatterns -import com.intellij.profile.codeInspection.InspectionProfileManager import com.intellij.psi.PsiComment import com.intellij.psi.PsiElement import com.intellij.psi.util.PsiTreeUtil @@ -18,14 +17,19 @@ import com.intellij.util.ProcessingContext import com.jetbrains.python.PyTokenTypes import com.jetbrains.python.codeInsight.completion.PythonLookupElement import com.jetbrains.python.psi.* +import com.jetbrains.snakecharm.codeInsight.SnakemakeAPI.MODULE_SECTIONS_KEYWORDS import com.jetbrains.snakecharm.codeInsight.SnakemakeAPI.RULE_OR_CHECKPOINT_SECTION_KEYWORDS import com.jetbrains.snakecharm.codeInsight.SnakemakeAPI.SUBWORKFLOW_SECTIONS_KEYWORDS -import com.jetbrains.snakecharm.inspections.SmkUnrecognizedSectionInspection +import com.jetbrains.snakecharm.codeInsight.SnakemakeAPI.USE_DECLARATION_KEYWORDS +import com.jetbrains.snakecharm.codeInsight.SnakemakeAPI.USE_SECTIONS_KEYWORDS import com.jetbrains.snakecharm.lang.SnakemakeLanguageDialect +import com.jetbrains.snakecharm.lang.SnakemakeNames import com.jetbrains.snakecharm.lang.parser.SmkTokenTypes.RULE_LIKE +import com.jetbrains.snakecharm.lang.parser.SmkTokenTypes.USE_KEYWORD import com.jetbrains.snakecharm.lang.parser.SmkTokenTypes.WORKFLOW_TOPLEVEL_DECORATORS_WO_RULE_LIKE import com.jetbrains.snakecharm.lang.parser.SnakemakeLexer import com.jetbrains.snakecharm.lang.psi.* +import com.jetbrains.snakecharm.lang.psi.impl.SmkUseArgsSectionImpl /** * @author Roman.Chernyatchik @@ -54,6 +58,25 @@ class SmkKeywordCompletionContributor : CompletionContributor() { SubworkflowSectionKeywordsProvider.CAPTURE, SubworkflowSectionKeywordsProvider ) + + extend( + CompletionType.BASIC, + ModuleSectionKeywordsProvider.CAPTURE, + ModuleSectionKeywordsProvider + ) + + extend( + CompletionType.BASIC, + UseSectionKeywordsProvider.CAPTURE, + UseSectionKeywordsProvider + ) + } +} + +object RuleKeywordTail : TailType() { + override fun processTail(editor: Editor, tailOffset: Int): Int { + editor.document.insertString(tailOffset, " ${SnakemakeNames.RULE_KEYWORD} ") + return moveCaret(editor, tailOffset, 2 + SnakemakeNames.RULE_KEYWORD.length) } } @@ -95,7 +118,6 @@ object WorkflowTopLevelKeywordsProvider : CompletionProvider v to k } .toMap() - listOf( WORKFLOW_TOPLEVEL_DECORATORS_WO_RULE_LIKE to ColonAndWhiteSpaceTail, RULE_LIKE to TailType.SPACE @@ -103,10 +125,12 @@ object WorkflowTopLevelKeywordsProvider : CompletionProvider val s = tokenType2Name[tt]!! + val modifiedTail = if (tt == USE_KEYWORD) RuleKeywordTail else tail + result.addElement( SmkCompletionUtil.createPrioritizedLookupElement( TailTypeDecorator.withTail( - PythonLookupElement(s, true, null), tail + PythonLookupElement(s, true, null), modifiedTail ), SmkCompletionUtil.KEYWORDS_PRIORITY ) @@ -152,7 +176,10 @@ object RuleSectionKeywordsProvider : CompletionProvider() .inside(SmkRuleOrCheckpoint::class.java)!! .andNot( psiElement().insideOneOf( - PyArgumentList::class.java, SmkRunSection::class.java, PsiComment::class.java + PyArgumentList::class.java, + SmkRunSection::class.java, + PsiComment::class.java, + SmkUseArgsSectionImpl::class.java ) ) @@ -204,6 +231,73 @@ object SubworkflowSectionKeywordsProvider : CompletionProvider() { + val CAPTURE = psiElement() + .inFile(SmkKeywordCompletionContributor.IN_SNAKEMAKE) + .inside(psiElement().inside(SmkModule::class.java)) + .andNot( + psiElement().insideOneOf(PyArgumentList::class.java, PsiComment::class.java) + ) + + override fun addCompletions( + parameters: CompletionParameters, + context: ProcessingContext, + result: CompletionResultSet + ) { + MODULE_SECTIONS_KEYWORDS.forEach { s -> + result.addElement( + SmkCompletionUtil.createPrioritizedLookupElement( + TailTypeDecorator.withTail( + PythonLookupElement(s, true, PlatformIcons.PROPERTY_ICON), + ColonAndWhiteSpaceTail + ), + priority = SmkCompletionUtil.SECTIONS_KEYS_PRIORITY + ) + ) + } + } +} + +object UseSectionKeywordsProvider : CompletionProvider() { + val CAPTURE = psiElement() + .inFile(SmkKeywordCompletionContributor.IN_SNAKEMAKE) + .inside(psiElement().inside(SmkUse::class.java)) + .andNot( + psiElement().insideOneOf(PyArgumentList::class.java, PsiComment::class.java) + ) + + override fun addCompletions( + parameters: CompletionParameters, + context: ProcessingContext, + result: CompletionResultSet + ) { + USE_DECLARATION_KEYWORDS.forEach { s -> + val tail = if (s != SnakemakeNames.SMK_WITH_KEYWORD) TailType.SPACE else TailType.CASE_COLON + result.addElement( + SmkCompletionUtil.createPrioritizedLookupElement( + TailTypeDecorator.withTail( + PythonLookupElement(s, true, PlatformIcons.PROPERTY_ICON), + tail + ), + priority = SmkCompletionUtil.SECTIONS_KEYS_PRIORITY + ) + ) + } + + USE_SECTIONS_KEYWORDS.forEach { s -> + result.addElement( + SmkCompletionUtil.createPrioritizedLookupElement( + TailTypeDecorator.withTail( + PythonLookupElement(s, true, PlatformIcons.PROPERTY_ICON), + ColonAndWhiteSpaceTail + ), + priority = SmkCompletionUtil.SECTIONS_KEYS_PRIORITY + ) + ) + } + } +} + fun PsiElementPattern.Capture.insideOneOf( vararg classes: Class ) = inside( diff --git a/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkModuleRedeclarationInspection.kt b/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkModuleRedeclarationInspection.kt new file mode 100644 index 000000000..5f524abbe --- /dev/null +++ b/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkModuleRedeclarationInspection.kt @@ -0,0 +1,32 @@ +package com.jetbrains.snakecharm.inspections + +import com.intellij.codeInspection.LocalInspectionToolSession +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.codeInspection.ProblemsHolder +import com.jetbrains.snakecharm.SnakemakeBundle +import com.jetbrains.snakecharm.lang.psi.SmkFile +import com.jetbrains.snakecharm.lang.psi.SmkModule + +class SmkModuleRedeclarationInspection : SnakemakeInspection() { + override fun buildVisitor( + holder: ProblemsHolder, + isOnTheFly: Boolean, + session: LocalInspectionToolSession + ) = object : SnakemakeInspectionVisitor(holder, session) { + private val modulesNameAndPsi = (session.file as? SmkFile)?.collectModules() ?: emptyList() + + override fun visitSmkModule(module: SmkModule) { + val name = module.name ?: return + + if (module !== modulesNameAndPsi.findLast { it.first == name }?.second) { + registerProblem( + module.originalElement, + SnakemakeBundle.message("INSP.NAME.module.redeclaration"), + ProblemHighlightType.LIKE_UNUSED_SYMBOL + ) + } + } + } + + override fun getDisplayName(): String = SnakemakeBundle.message("INSP.NAME.module.redeclaration") +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkRuleRedeclarationInspection.kt b/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkRuleRedeclarationInspection.kt index 0d38c2c69..3a4c65ac0 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkRuleRedeclarationInspection.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkRuleRedeclarationInspection.kt @@ -5,6 +5,7 @@ import com.intellij.codeInspection.ProblemHighlightType import com.intellij.codeInspection.ProblemHighlightType.GENERIC_ERROR import com.intellij.codeInspection.ProblemHighlightType.WEAK_WARNING import com.intellij.codeInspection.ProblemsHolder +import com.intellij.psi.PsiElement import com.intellij.psi.util.PsiTreeUtil import com.jetbrains.python.psi.PyStatement import com.jetbrains.snakecharm.SnakemakeBundle @@ -12,13 +13,14 @@ import com.jetbrains.snakecharm.inspections.quickfix.RenameElementWithoutUsagesQ import com.jetbrains.snakecharm.lang.psi.* import com.jetbrains.snakecharm.lang.psi.stubs.SmkCheckpointNameIndex import com.jetbrains.snakecharm.lang.psi.stubs.SmkRuleNameIndex +import com.jetbrains.snakecharm.lang.psi.stubs.SmkUseNameIndex import com.jetbrains.snakecharm.lang.psi.types.AbstractSmkRuleOrCheckpointType class SmkRuleRedeclarationInspection : SnakemakeInspection() { override fun buildVisitor( - holder: ProblemsHolder, - isOnTheFly: Boolean, - session: LocalInspectionToolSession + holder: ProblemsHolder, + isOnTheFly: Boolean, + session: LocalInspectionToolSession ) = object : SnakemakeInspectionVisitor(holder, session) { private val localRules by lazy { holder.file.let { psiFile -> @@ -40,6 +42,16 @@ class SmkRuleRedeclarationInspection : SnakemakeInspection() { } } } + private val localUses by lazy { + holder.file.let { psiFile -> + when (psiFile) { + is SmkFile -> { + psiFile.collectUses().map { it.second } + } + else -> emptyList() + } + } + } override fun visitSmkRule(rule: SmkRule) { visitSMKRuleLike(rule) @@ -49,6 +61,10 @@ class SmkRuleRedeclarationInspection : SnakemakeInspection() { visitSMKRuleLike(checkPoint) } + override fun visitSmkUse(use: SmkUse) { + visitSMKRuleLike(use) + } + private fun visitSMKRuleLike(ruleLike: SmkRuleLike) { val containingFile = ruleLike.containingFile val nameToCheck = ruleLike.name ?: return @@ -61,11 +77,17 @@ class SmkRuleRedeclarationInspection : SnakemakeInspection() { ruleLike, nameToCheck, SmkCheckpointNameIndex.KEY, SmkCheckPoint::class.java ) { localCheckpoints } - val resolveResults = ruleResolveResults + cpResolveResults + val usesResolveResults: Collection = + AbstractSmkRuleOrCheckpointType.findAvailableRuleLikeElementByName( + ruleLike, nameToCheck, SmkUseNameIndex.KEY, SmkUse::class.java + ) { localUses } + + val resolveResults = ruleResolveResults + cpResolveResults + usesResolveResults if (resolveResults.isEmpty()) { return } + if (resolveResults.size == 1 && resolveResults.single() == ruleLike) { return } @@ -98,7 +120,7 @@ class SmkRuleRedeclarationInspection : SnakemakeInspection() { } private fun registerProblem( - ruleLike: SmkRuleLike, + ruleLike: SmkSection, msg: String, severity: ProblemHighlightType ) { @@ -114,5 +136,6 @@ class SmkRuleRedeclarationInspection : SnakemakeInspection() { ) } } + override fun getDisplayName(): String = SnakemakeBundle.message("INSP.NAME.rule.redeclaration") } \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkSectionMultipleArgsInspection.kt b/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkSectionMultipleArgsInspection.kt index 9dc9ab571..7f037ca9b 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkSectionMultipleArgsInspection.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkSectionMultipleArgsInspection.kt @@ -7,6 +7,7 @@ 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.SmkModuleArgsSection import com.jetbrains.snakecharm.lang.psi.SmkRuleOrCheckpointArgsSection import com.jetbrains.snakecharm.lang.psi.SmkSubworkflowArgsSection import com.jetbrains.snakecharm.lang.psi.SmkWorkflowArgsSection @@ -15,7 +16,7 @@ class SmkSectionMultipleArgsInspection : SnakemakeInspection() { override fun buildVisitor( holder: ProblemsHolder, isOnTheFly: Boolean, - session: LocalInspectionToolSession, + session: LocalInspectionToolSession ) = object : SnakemakeInspectionVisitor(holder, session) { override fun visitSmkSubworkflowArgsSection(st: SmkSubworkflowArgsSection) { @@ -37,9 +38,13 @@ class SmkSectionMultipleArgsInspection : SnakemakeInspection() { } } + override fun visitSmkModuleArgsSection(st: SmkModuleArgsSection) { + checkArgumentList(st.argumentList, "module") + } + private fun checkArgumentList( argumentList: PyArgumentList?, - sectionName: String, + sectionName: String ) { val args = argumentList?.arguments ?: emptyArray() if (args.size > 1) { diff --git a/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkSectionRedeclarationInspection.kt b/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkSectionRedeclarationInspection.kt index ef67acacb..5179399c4 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkSectionRedeclarationInspection.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkSectionRedeclarationInspection.kt @@ -9,9 +9,9 @@ import com.jetbrains.snakecharm.lang.psi.* class SmkSectionRedeclarationInspection : SnakemakeInspection() { override fun buildVisitor( - holder: ProblemsHolder, - isOnTheFly: Boolean, - session: LocalInspectionToolSession + holder: ProblemsHolder, + isOnTheFly: Boolean, + session: LocalInspectionToolSession ) = object : SnakemakeInspectionVisitor(holder, session) { override fun visitSmkRule(rule: SmkRule) { visitSMKRuleLike(rule) @@ -25,6 +25,14 @@ class SmkSectionRedeclarationInspection : SnakemakeInspection() { visitSMKRuleLike(subworkflow) } + override fun visitSmkModule(module: SmkModule) { + visitSMKRuleLike(module) + } + + override fun visitSmkUse(use: SmkUse) { + visitSMKRuleLike(use) + } + private fun visitSMKRuleLike(rule: SmkRuleLike) { val sectionNamesSet = HashSet() @@ -46,12 +54,12 @@ class SmkSectionRedeclarationInspection : SnakemakeInspection() { } registerProblem( - section, - SnakemakeBundle.message("INSP.NAME.section.redeclaration.message", name), - // No suitable severity, so is WEAK WARNING in plugin.xml - ProblemHighlightType.LIKE_UNUSED_SYMBOL, - null, - *fixes.toTypedArray() + section, + SnakemakeBundle.message("INSP.NAME.section.redeclaration.message", name), + // No suitable severity, so is WEAK WARNING in plugin.xml + ProblemHighlightType.LIKE_UNUSED_SYMBOL, + null, + *fixes.toTypedArray() ) } sectionNamesSet.add(name) diff --git a/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkSectionUnexpectedKeywordArgsInspection.kt b/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkSectionUnexpectedKeywordArgsInspection.kt index 627a78304..158f6f0ed 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkSectionUnexpectedKeywordArgsInspection.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkSectionUnexpectedKeywordArgsInspection.kt @@ -8,6 +8,7 @@ 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.SmkModuleArgsSection import com.jetbrains.snakecharm.lang.psi.SmkRuleOrCheckpointArgsSection import com.jetbrains.snakecharm.lang.psi.SmkSubworkflowArgsSection import com.jetbrains.snakecharm.lang.psi.SmkWorkflowArgsSection @@ -16,7 +17,7 @@ class SmkSectionUnexpectedKeywordArgsInspection : SnakemakeInspection() { override fun buildVisitor( holder: ProblemsHolder, isOnTheFly: Boolean, - session: LocalInspectionToolSession, + session: LocalInspectionToolSession ) = object : SnakemakeInspectionVisitor(holder, session) { override fun visitSmkSubworkflowArgsSection(st: SmkSubworkflowArgsSection) { @@ -41,9 +42,13 @@ class SmkSectionUnexpectedKeywordArgsInspection : SnakemakeInspection() { } } + override fun visitSmkModuleArgsSection(st: SmkModuleArgsSection) { + checkArgumentList(st.argumentList, st) + } + private fun checkArgumentList( argumentList: PyArgumentList?, - section: SmkArgsSection, + section: SmkArgsSection ) { val args = argumentList?.arguments ?: emptyArray() args.forEach { arg -> diff --git a/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkUnrecognizedSectionInspection.kt b/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkUnrecognizedSectionInspection.kt index 77db3f42d..f1fb56448 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkUnrecognizedSectionInspection.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/inspections/SmkUnrecognizedSectionInspection.kt @@ -3,13 +3,16 @@ package com.jetbrains.snakecharm.inspections import com.intellij.codeInspection.LocalInspectionToolSession import com.intellij.codeInspection.ProblemHighlightType import com.intellij.codeInspection.ProblemsHolder +import com.intellij.psi.util.elementType import com.intellij.codeInspection.ui.ListEditForm import com.jetbrains.snakecharm.SnakemakeBundle +import com.jetbrains.snakecharm.codeInsight.SnakemakeAPI.MODULE_SECTIONS_KEYWORDS import com.jetbrains.snakecharm.codeInsight.SnakemakeAPI.RULE_OR_CHECKPOINT_ARGS_SECTION_KEYWORDS import com.jetbrains.snakecharm.codeInsight.SnakemakeAPI.SUBWORKFLOW_SECTIONS_KEYWORDS +import com.jetbrains.snakecharm.codeInsight.SnakemakeAPI.USE_SECTIONS_KEYWORDS +import com.jetbrains.snakecharm.lang.psi.* +import com.jetbrains.snakecharm.lang.psi.elementTypes.SmkElementTypes import com.jetbrains.snakecharm.inspections.quickfix.AddIgnoredElementQuickFix -import com.jetbrains.snakecharm.lang.psi.SmkRuleOrCheckpointArgsSection -import com.jetbrains.snakecharm.lang.psi.SmkSubworkflowArgsSection import javax.swing.JComponent class SmkUnrecognizedSectionInspection : SnakemakeInspection() { @@ -22,26 +25,35 @@ class SmkUnrecognizedSectionInspection : SnakemakeInspection() { session: LocalInspectionToolSession ) = object : SnakemakeInspectionVisitor(holder, session) { override fun visitSmkSubworkflowArgsSection(st: SmkSubworkflowArgsSection) { - val sectionNamePsi = st.nameIdentifier - val sectionKeyword = st.sectionKeyword + isSectionRecognized(st, SUBWORKFLOW_SECTIONS_KEYWORDS) + } - if (sectionNamePsi != null && sectionKeyword != null && sectionKeyword !in SUBWORKFLOW_SECTIONS_KEYWORDS - && sectionKeyword !in ignoredItems) { - registerProblem( - sectionNamePsi, - SnakemakeBundle.message("INSP.NAME.section.unrecognized.message", sectionKeyword), - ProblemHighlightType.WEAK_WARNING, - null, AddIgnoredElementQuickFix(sectionKeyword) - ) + override fun visitSmkRuleOrCheckpointArgsSection(st: SmkRuleOrCheckpointArgsSection) { + if (st.originalElement.elementType == SmkElementTypes.USE_ARGS_SECTION_STATEMENT) { + isSectionRecognized(st, USE_SECTIONS_KEYWORDS) + } else { + isSectionRecognized(st, RULE_OR_CHECKPOINT_ARGS_SECTION_KEYWORDS) } } - override fun visitSmkRuleOrCheckpointArgsSection(st: SmkRuleOrCheckpointArgsSection) { - val sectionNamePsi = st.nameIdentifier - val sectionKeyword = st.sectionKeyword + override fun visitSmkModuleArgsSection(st: SmkModuleArgsSection) { + isSectionRecognized(st, MODULE_SECTIONS_KEYWORDS) + } + + /** + * Checks whether [argsSection] name identifier and section keyword are not null and + * whether the section keyword is a member of the [setOfValidNames]. If no, it shows a weak warning. + */ + private fun isSectionRecognized( + argsSection: SmkArgsSection, + setOfValidNames: Set + ) { + val sectionNamePsi = argsSection.nameIdentifier + val sectionKeyword = argsSection.sectionKeyword - if (sectionNamePsi != null && sectionKeyword != null && sectionKeyword !in RULE_OR_CHECKPOINT_ARGS_SECTION_KEYWORDS - && sectionKeyword !in ignoredItems) { + if (sectionNamePsi != null && sectionKeyword != null && sectionKeyword !in setOfValidNames + && sectionKeyword !in ignoredItems + ) { registerProblem( sectionNamePsi, SnakemakeBundle.message("INSP.NAME.section.unrecognized.message", sectionKeyword), diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/SmkTokenSetContributor.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/SmkTokenSetContributor.kt index f2fbaebfb..13127f3c3 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/lang/SmkTokenSetContributor.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/SmkTokenSetContributor.kt @@ -13,21 +13,27 @@ import com.jetbrains.snakecharm.lang.psi.elementTypes.SmkStubElementTypes */ class SmkTokenSetContributor : PythonDialectsTokenSetContributorBase() { override fun getStatementTokens() = TokenSet.create( - SmkElementTypes.WORKFLOW_ARGS_SECTION_STATEMENT, - SmkElementTypes.WORKFLOW_LOCALRULES_SECTION_STATEMENT, - SmkElementTypes.WORKFLOW_RULEORDER_SECTION_STATEMENT, - SmkElementTypes.WORKFLOW_PY_BLOCK_SECTION_STATEMENT, + SmkElementTypes.WORKFLOW_ARGS_SECTION_STATEMENT, + SmkElementTypes.WORKFLOW_LOCALRULES_SECTION_STATEMENT, + SmkElementTypes.WORKFLOW_RULEORDER_SECTION_STATEMENT, + SmkElementTypes.WORKFLOW_PY_BLOCK_SECTION_STATEMENT, - SmkStubElementTypes.RULE_DECLARATION_STATEMENT, - SmkStubElementTypes.CHECKPOINT_DECLARATION_STATEMENT, - SmkElementTypes.RULE_OR_CHECKPOINT_ARGS_SECTION_STATEMENT, + SmkStubElementTypes.RULE_DECLARATION_STATEMENT, + SmkStubElementTypes.CHECKPOINT_DECLARATION_STATEMENT, + SmkElementTypes.RULE_OR_CHECKPOINT_ARGS_SECTION_STATEMENT, - SmkStubElementTypes.SUBWORKFLOW_DECLARATION_STATEMENT, - SmkElementTypes.SUBWORKFLOW_ARGS_SECTION_STATEMENT + SmkStubElementTypes.SUBWORKFLOW_DECLARATION_STATEMENT, + SmkElementTypes.SUBWORKFLOW_ARGS_SECTION_STATEMENT, + + SmkStubElementTypes.MODULE_DECLARATION_STATEMENT, + SmkElementTypes.MODULE_ARGS_SECTION_STATEMENT, + + SmkStubElementTypes.USE_DECLARATION_STATEMENT, + SmkElementTypes.USE_ARGS_SECTION_STATEMENT ) override fun getExpressionTokens() = TokenSet.create( - SmkElementTypes.REFERENCE_EXPRESSION, SMK_PY_REFERENCE_EXPRESSION + SmkElementTypes.REFERENCE_EXPRESSION, SMK_PY_REFERENCE_EXPRESSION ) /** @@ -41,7 +47,7 @@ class SmkTokenSetContributor : PythonDialectsTokenSetContributorBase() { override fun getReferenceExpressionTokens() = TokenSet.create(SMK_PY_REFERENCE_EXPRESSION) - override fun getFunctionDeclarationTokens()= TokenSet.EMPTY!! + override fun getFunctionDeclarationTokens() = TokenSet.EMPTY!! override fun getUnbalancedBracesRecoveryTokens(): TokenSet { return WORKFLOW_TOPLEVEL_DECORATORS diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/SnakemakeNames.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/SnakemakeNames.kt index 679d769ae..bb9627da7 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/lang/SnakemakeNames.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/SnakemakeNames.kt @@ -28,6 +28,18 @@ object SnakemakeNames { const val SUBWORKFLOW_CONFIGFILE_KEYWORD = WORKFLOW_CONFIGFILE_KEYWORD const val SUBWORKFLOW_SNAKEFILE_KEYWORD = "snakefile" + const val MODULE_KEYWORD = "module" + const val MODULE_SNAKEFILE_KEYWORD = "snakefile" + const val MODULE_CONFIG_KEYWORD = "config" + const val MODULE_SKIP_VALIDATION_KEYWORD = "skip_validation" + const val MODULE_META_WRAPPER_KEYWORD = "meta_wrapper" + const val MODULE_REPLACE_PREFIX_KEYWORD = "replace_prefix" + + const val USE_KEYWORD = "use" + const val SMK_FROM_KEYWORD = "from" + const val SMK_AS_KEYWORD = "as" + const val SMK_WITH_KEYWORD = "with" + const val SECTION_INPUT = "input" const val SECTION_OUTPUT = "output" const val SECTION_LOG = "log" diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/highlighter/SmkSyntaxAnnotator.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/highlighter/SmkSyntaxAnnotator.kt index 94d95128c..b2cff3c7a 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/lang/highlighter/SmkSyntaxAnnotator.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/highlighter/SmkSyntaxAnnotator.kt @@ -1,11 +1,14 @@ package com.jetbrains.snakecharm.lang.highlighter +import com.jetbrains.python.PyTokenTypes import com.jetbrains.python.highlighting.PyHighlighter import com.jetbrains.python.highlighting.PyHighlighter.PY_FUNC_DEFINITION +import com.jetbrains.snakecharm.lang.parser.SmkTokenTypes import com.jetbrains.snakecharm.lang.psi.* +import com.jetbrains.snakecharm.lang.psi.elementTypes.SmkElementTypes import com.jetbrains.snakecharm.lang.validation.SmkAnnotator -object SmkSyntaxAnnotator: SmkAnnotator() { +object SmkSyntaxAnnotator : SmkAnnotator() { override fun visitSmkRule(rule: SmkRule) { highlightSMKRuleLike(rule) } @@ -18,6 +21,32 @@ object SmkSyntaxAnnotator: SmkAnnotator() { highlightSMKRuleLike(subworkflow) } + override fun visitSmkModule(module: SmkModule) { + highlightSMKRuleLike(module) + } + + override fun visitSmkUse(use: SmkUse) { + highlightWorkflowSection(use) + + val node = use.node.firstChildNode + var next = node.treeNext + var done = false + while (!done && next != null) { + when (next.elementType) { + SmkTokenTypes.RULE_KEYWORD, SmkTokenTypes.SMK_FROM_KEYWORD, + SmkTokenTypes.SMK_AS_KEYWORD, SmkTokenTypes.SMK_WITH_KEYWORD -> addHighlightingAnnotation( + next, + PyHighlighter.PY_KEYWORD + ) + SmkElementTypes.USE_NAME_IDENTIFIER, PyTokenTypes.IDENTIFIER -> addHighlightingAnnotation( + next, PY_FUNC_DEFINITION + ) + PyTokenTypes.COLON -> done = true + } + next = next.treeNext + } + } + override fun visitSmkRuleOrCheckpointArgsSection(st: SmkRuleOrCheckpointArgsSection) { highlightRuleLikeSection(st) } @@ -26,6 +55,10 @@ object SmkSyntaxAnnotator: SmkAnnotator() { highlightRuleLikeSection(st) } + override fun visitSmkModuleArgsSection(st: SmkModuleArgsSection) { + highlightRuleLikeSection(st) + } + override fun visitSmkRunSection(st: SmkRunSection) { st.getSectionKeywordNode()?.let { addHighlightingAnnotation(it, PyHighlighter.PY_PREDEFINED_DEFINITION) diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/parser/SmkStatementParsing.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/parser/SmkStatementParsing.kt index 157062a95..561705037 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/lang/parser/SmkStatementParsing.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/parser/SmkStatementParsing.kt @@ -19,38 +19,54 @@ import com.jetbrains.snakecharm.lang.psi.elementTypes.SmkStubElementTypes.* * @date 2018-12-31 */ class SmkStatementParsing( - context: SmkParserContext + context: SmkParserContext ) : StatementParsing(context) { private val ruleSectionParsingData = SectionParsingData( - declaration = RULE_DECLARATION_STATEMENT, - name = "rule", - parameterListStatement = SmkElementTypes.RULE_OR_CHECKPOINT_ARGS_SECTION_STATEMENT, - sectionKeyword= SmkTokenTypes.RULE_KEYWORD + declaration = RULE_DECLARATION_STATEMENT, + name = "rule", + parameterListStatement = SmkElementTypes.RULE_OR_CHECKPOINT_ARGS_SECTION_STATEMENT, + sectionKeyword = SmkTokenTypes.RULE_KEYWORD ) private val checkpointSectionParsingData = SectionParsingData( - declaration = CHECKPOINT_DECLARATION_STATEMENT, - name = "checkpoint", - parameterListStatement = SmkElementTypes.RULE_OR_CHECKPOINT_ARGS_SECTION_STATEMENT, - sectionKeyword= SmkTokenTypes.CHECKPOINT_KEYWORD + declaration = CHECKPOINT_DECLARATION_STATEMENT, + name = "checkpoint", + parameterListStatement = SmkElementTypes.RULE_OR_CHECKPOINT_ARGS_SECTION_STATEMENT, + sectionKeyword = SmkTokenTypes.CHECKPOINT_KEYWORD ) private val subworkflowSectionParsingData = SectionParsingData( - declaration = SUBWORKFLOW_DECLARATION_STATEMENT, - name = "subworkflow", - parameterListStatement = SmkElementTypes.SUBWORKFLOW_ARGS_SECTION_STATEMENT, - sectionKeyword= SmkTokenTypes.SUBWORKFLOW_KEYWORD + declaration = SUBWORKFLOW_DECLARATION_STATEMENT, + name = "subworkflow", + parameterListStatement = SmkElementTypes.SUBWORKFLOW_ARGS_SECTION_STATEMENT, + sectionKeyword = SmkTokenTypes.SUBWORKFLOW_KEYWORD + ) + + private val moduleSectionParsingData = SectionParsingData( + declaration = MODULE_DECLARATION_STATEMENT, + name = "module", + parameterListStatement = SmkElementTypes.MODULE_ARGS_SECTION_STATEMENT, + sectionKeyword = SmkTokenTypes.MODULE_KEYWORD + ) + + private val useSectionParsingData = SectionParsingData( + declaration = USE_DECLARATION_STATEMENT, + name = "use", + parameterListStatement = SmkElementTypes.USE_ARGS_SECTION_STATEMENT, + sectionKeyword = SmkTokenTypes.USE_KEYWORD ) override fun getReferenceType() = SmkElementTypes.SMK_PY_REFERENCE_EXPRESSION private fun getSectionParsingData(tokenType: IElementType) = - when { - tokenType === SmkTokenTypes.SUBWORKFLOW_KEYWORD -> subworkflowSectionParsingData - tokenType === SmkTokenTypes.CHECKPOINT_KEYWORD -> checkpointSectionParsingData - else -> ruleSectionParsingData - } + when { + tokenType === SmkTokenTypes.SUBWORKFLOW_KEYWORD -> subworkflowSectionParsingData + tokenType === SmkTokenTypes.CHECKPOINT_KEYWORD -> checkpointSectionParsingData + tokenType === SmkTokenTypes.MODULE_KEYWORD -> moduleSectionParsingData + tokenType === SmkTokenTypes.USE_KEYWORD -> useSectionParsingData + else -> ruleSectionParsingData + } override fun getParsingContext() = myContext as SmkParserContext @@ -89,9 +105,9 @@ class SmkStatementParsing( nextToken() val res = parsingContext.expressionParser.parseArgumentList( - ",", PyTokenTypes.COMMA, - SnakemakeBundle.message("PARSE.expected.identifier"), - this::parseIdentifier + ",", PyTokenTypes.COMMA, + SnakemakeBundle.message("PARSE.expected.identifier"), + this::parseIdentifier ) if (!res) { @@ -103,14 +119,14 @@ class SmkStatementParsing( nextToken() } } - tt === SmkTokenTypes.WORKFLOW_RULEORDER_KEYWORD -> { + tt === SmkTokenTypes.WORKFLOW_RULEORDER_KEYWORD -> { val workflowParam = myBuilder.mark() nextToken() val res = parsingContext.expressionParser.parseArgumentList( - ">", PyTokenTypes.GT, - SnakemakeBundle.message("PARSE.expected.identifier"), - this::parseIdentifier + ">", PyTokenTypes.GT, + SnakemakeBundle.message("PARSE.expected.identifier"), + this::parseIdentifier ) if (!res) { @@ -146,9 +162,22 @@ class SmkStatementParsing( val ruleLikeMarker = myBuilder.mark() nextToken() - // rule name - //val ruleNameMarker: PsiBuilder.Marker = myBuilder.mark() - if (atToken(PyTokenTypes.IDENTIFIER)) { + // Parse second word in 'use rule' + if (section == useSectionParsingData) { + if (myBuilder.tokenText != SnakemakeNames.RULE_KEYWORD) { + myBuilder.error(SnakemakeBundle.message("PARSE.use.rule.keyword.expected")) + } else { + myBuilder.remapCurrentToken(SmkTokenTypes.RULE_KEYWORD) + nextToken() + } + // Parse rest words in 'use' definition + val names = mutableListOf() + parseRestUseStatement(parseUseSection(names), names, section) + ruleLikeMarker.done(section.declaration) + return + } else if (atToken(PyTokenTypes.IDENTIFIER)) { + // rule name + //val ruleNameMarker: PsiBuilder.Marker = myBuilder.mark() nextToken() } @@ -260,7 +289,7 @@ class SmkStatementParsing( myContext.popScope() } else -> { - // Snakemeake often adds new sections => let's by default allow all here + + // Snakemake often adds new sections => let's by default allow all here + // show inspection error for keyword not in `section.parameters` instead of parsing errors.. result = parsingContext.expressionParser.parseRuleLikeSectionArgumentList() ruleParam.done(section.parameterListStatement) @@ -289,13 +318,287 @@ class SmkStatementParsing( referenceMarker.drop() return false } + + /** + * Parses 'use' section. If any part of 'use' section declaration is missing, + * it will create an error message and keep parsing the section. + * Returns true if 'as' part was detected + */ + private fun parseUseSection(names: MutableList): Boolean { + var asKeywordExists = false + + when (myBuilder.tokenType) { + PyTokenTypes.FROM_KEYWORD -> myBuilder.apply { + error(SnakemakeBundle.message("PARSE.use.names.expected")) + asKeywordExists = fromSignatureParsing(names) + } + PyTokenTypes.AS_KEYWORD -> myBuilder.apply { + asKeywordExists = true + error(SnakemakeBundle.message("PARSE.use.names.expected")) + asSignatureParsing(names) + } + PyTokenTypes.IDENTIFIER -> { + val marker = myBuilder.mark() + parseIdentifierFromIdentifiersList(names) + marker.done(SmkElementTypes.USE_IMPORTED_RULES_NAMES) + asKeywordExists = endOfImportedRulesDeclaration(false, names) + } + PyTokenTypes.MULT -> { + val marker = myBuilder.mark() + names.add(myBuilder.tokenText!!) + nextToken() + if (atToken(PyTokenTypes.COMMA)) { + myBuilder.error(SnakemakeBundle.message("PARSE.use.wildcard.in.names.list")) + nextToken() + parseIdentifierFromIdentifiersList(names) + } + marker.done(SmkElementTypes.USE_IMPORTED_RULES_NAMES) + asKeywordExists = endOfImportedRulesDeclaration(true, names) + } + else -> myBuilder.error(SnakemakeBundle.message("PARSE.use.names.expected")) + } + + return asKeywordExists + } + + /** + * Uses when we need to parse list of imported rules names. + */ + private fun parseIdentifierFromIdentifiersList( + list: MutableList + ) { + var hasNext = true + while (hasNext) { + hasNext = when (myBuilder.tokenType) { + PyTokenTypes.IDENTIFIER -> { + val referenceMarker = myBuilder.mark() // Register new name + list.add(myBuilder.tokenText ?: return) + Parsing.advanceIdentifierLike(myBuilder) + referenceMarker.done(SmkElementTypes.REFERENCE_EXPRESSION) + registerCommaOrEndOfNames() + } + PyTokenTypes.MULT -> { + myBuilder.error(SnakemakeBundle.message("PARSE.use.wildcard.in.names.list")) + nextToken() + registerCommaOrEndOfNames() + } + PyTokenTypes.COMMA -> { + myBuilder.error(SnakemakeBundle.message("PARSE.use.names.expected")) + nextToken() + true + } + // Actually, Snakemake allows any name for rules and modules, + // that have python token type NAME, which may contains such words as: + // 'use', 'as', 'from' + else -> { + myBuilder.error(SnakemakeBundle.message("PARSE.use.names.expected")) + false + } + } + } + } + + /** + * Uses when we need to register comma in list of imported rules names + * or ending of the list + */ + private fun registerCommaOrEndOfNames(): Boolean = when (myBuilder.tokenType) { + PyTokenTypes.FROM_KEYWORD, PyTokenTypes.AS_KEYWORD, PyTokenTypes.WITH_KEYWORD -> false + PyTokenTypes.COMMA -> { + nextToken() + true + } + else -> { + myBuilder.error(SnakemakeBundle.message("PARSE.use.unexpected.names.separator")) + false + } + } + + /** + * Uses when we've finished to collect rules names and went to the next step + * Returns true if 'as' part was detected + */ + private fun endOfImportedRulesDeclaration( + definedByWildcard: Boolean, + names: List + ): Boolean = when (myBuilder.tokenType) { + PyTokenTypes.FROM_KEYWORD -> fromSignatureParsing(names) + PyTokenTypes.AS_KEYWORD -> { + if (definedByWildcard) { + myBuilder.error(SnakemakeBundle.message("PARSE.use.unexpected.list.ending")) + } + if (names.size > 1) { + myBuilder.error(SnakemakeBundle.message("PARSE.use.few.names.from.current.module")) + } + asSignatureParsing(names) + true + } + else -> { + myBuilder.error(SnakemakeBundle.message("PARSE.use.unexpected.list.ending")) + false + } + } + + /** + * Parses 'from' part in 'use' section declaration. + * Returns true if 'as' part was detected after 'from' part + */ + private fun fromSignatureParsing(names: List): Boolean { + myBuilder.remapCurrentToken(SmkTokenTypes.SMK_FROM_KEYWORD) + nextToken() + + if (!atToken(PyTokenTypes.IDENTIFIER)) { + myBuilder.error(SnakemakeBundle.message("PARSE.use.expecting.module.name")) + } else { + parseIdentifier() + } + + if (atToken(PyTokenTypes.AS_KEYWORD)) { + asSignatureParsing(names) + return true + } + + return false + } + + /** + * Parses 'as' part in 'use' section declaration + */ + private fun asSignatureParsing(names: List) { + myBuilder.remapCurrentToken(SmkTokenTypes.SMK_AS_KEYWORD) + nextToken() + + var lasTokenIsIdentifier = + !atToken(PyTokenTypes.IDENTIFIER) // Default value need to ve reversed + var simpleName = true // Does new rule name consist of one identifier + var hasIdentifier = false // Do we have new rule name + val name = myBuilder.mark() + while (true) { + when (myBuilder.tokenType) { + PyTokenTypes.IDENTIFIER -> { + if (lasTokenIsIdentifier) { + break // Because it's separated by whitespace so it isn't name anymore + } + lasTokenIsIdentifier = true + hasIdentifier = true + nextToken() + } + PyTokenTypes.MULT -> { + if (!lasTokenIsIdentifier) { + myBuilder.error(SnakemakeBundle.message("PARSE.use.double.mult.sign")) + } + lasTokenIsIdentifier = false + hasIdentifier = true + simpleName = false + nextToken() + } + PyTokenTypes.EXP, PyTokenTypes.COMMA -> { + myBuilder.error(SnakemakeBundle.message("PARSE.use.double.mult.sign")) + lasTokenIsIdentifier = false + simpleName = false + nextToken() + } + else -> break + } + } + if (!hasIdentifier) { // No identifiers and/or '*' symbols + name.drop() + // Currently (6.5.3) it's ok for snakemake + // We can write 'as with:' or just 'as' and end line + // Both variants are allowed and original rule names will be taken + } else { + if (!simpleName) { // New rule name contains at least one '*' symbol + name.done(SmkElementTypes.USE_NAME_IDENTIFIER) + } else { // New rule name consists of one identifier + name.drop() + if (names.size > 1) { + myBuilder.error(SnakemakeBundle.message("PARSE.use.requires.wildcard")) + } + } + } + } + + /** + * Parses end of 'use' section declaration and its arguments sections + */ + private fun parseRestUseStatement( + asKeywordExists: Boolean, + names: List, + section: SectionParsingData + ) { + // Parses 'with' part + val argsSectionsBanned = (names.size == 1 && names[0] == "*") + var gotWithOrColon = false + + if (atToken(PyTokenTypes.WITH_KEYWORD)) { + gotWithOrColon = true + if (argsSectionsBanned) { + myBuilder.error(SnakemakeBundle.message("PARSE.use.with.not.allowed")) + } + myBuilder.remapCurrentToken(SmkTokenTypes.SMK_WITH_KEYWORD) + nextToken() + } + + if (atToken(PyTokenTypes.COLON)) { + if (!gotWithOrColon) { + myBuilder.error(SnakemakeBundle.message("PARSE.use.with.missed")) + } + gotWithOrColon = true + nextToken() + } else if (gotWithOrColon) { + myBuilder.error(SnakemakeBundle.message("PARSE.use.expecting.colon")) + } + + // Parses arguments sections + if (myBuilder.tokenType.isPythonString()) { + parsingContext.expressionParser.parseStringLiteralExpression() + } + if (!atToken(PyTokenTypes.STATEMENT_BREAK)) { + val ruleStatements = myBuilder.mark() + verifyArgsSections(argsSectionsBanned, gotWithOrColon, asKeywordExists) + parseRuleParameter(section) + ruleStatements.done(PyElementTypes.STATEMENT_LIST) + return + } + val ruleStatements = myBuilder.mark() + nextToken() + + if (!atToken(PyTokenTypes.INDENT)) { + ruleStatements.done(PyElementTypes.STATEMENT_LIST) + // No error if we got 'with:' without arguments sections + return + } + + verifyArgsSections(argsSectionsBanned, gotWithOrColon, asKeywordExists) + + nextToken() + while (!atToken(PyTokenTypes.DEDENT)) { + if (!parseRuleParameter(section)) { + break + } + } + ruleStatements.done(PyElementTypes.STATEMENT_LIST) + nextToken() + } + + private fun verifyArgsSections(argsSectionsBanned: Boolean, gotWithOrColon: Boolean, asKeywordExists: Boolean) { + if (argsSectionsBanned && !gotWithOrColon) { + myBuilder.error(SnakemakeBundle.message("PARSE.use.args.sections.forbidden")) + } + + if (!gotWithOrColon && !asKeywordExists) { + myBuilder.error(SnakemakeBundle.message("PARSE.use.as.or.with.expecting")) + } else if (!gotWithOrColon) { + myBuilder.error(SnakemakeBundle.message("PARSE.use.with.expecting")) + } + } } fun IElementType?.isPythonString() = this in PyTokenTypes.STRING_NODES || this == PyTokenTypes.FSTRING_START private data class SectionParsingData( - val declaration: IElementType, - val name: String, - val parameterListStatement: PyElementType, - val sectionKeyword: PyElementType + val declaration: IElementType, + val name: String, + val parameterListStatement: PyElementType, + val sectionKeyword: PyElementType ) diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/parser/SmkTokenTypes.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/parser/SmkTokenTypes.kt index 1c039685c..812ef31df 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/lang/parser/SmkTokenTypes.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/parser/SmkTokenTypes.kt @@ -12,6 +12,12 @@ object SmkTokenTypes { val RULE_KEYWORD = PyElementType("RULE_KEYWORD") // rule val SUBWORKFLOW_KEYWORD = PyElementType("SUBWORKFLOW_KEYWORD") val CHECKPOINT_KEYWORD = PyElementType("CHECKPOINT_KEYWORD") // rule + val MODULE_KEYWORD = PyElementType("MODULE_KEYWORD") + val USE_KEYWORD = PyElementType("USE_KEYWORD") + + val SMK_FROM_KEYWORD = PyElementType("SMK_FROM_KEYWORD") + val SMK_AS_KEYWORD = PyElementType("SMK_AS_KEYWORD") + val SMK_WITH_KEYWORD = PyElementType("SMK_WITH_KEYWORD") val WORKFLOW_CONFIGFILE_KEYWORD = PyElementType("WORKFLOW_CONFIGFILE_KEYWORD") val WORKFLOW_REPORT_KEYWORD = PyElementType("WORKFLOW_REPORT_KEYWORD") @@ -57,7 +63,7 @@ object SmkTokenTypes { ) val RULE_LIKE = TokenSet.orSet( - RULE_OR_CHECKPOINT, TokenSet.create(SUBWORKFLOW_KEYWORD) + RULE_OR_CHECKPOINT, TokenSet.create(SUBWORKFLOW_KEYWORD, MODULE_KEYWORD, USE_KEYWORD) ) val WORKFLOW_TOPLEVEL_DECORATORS = TokenSet.orSet( diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/parser/SnakemakeLexer.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/parser/SnakemakeLexer.kt index 88ac206c0..d34c8a85d 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/lang/parser/SnakemakeLexer.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/parser/SnakemakeLexer.kt @@ -63,36 +63,43 @@ class SnakemakeLexer : PythonIndentingLexer() { companion object { val RULE_LIKE_KEYWORDS = ImmutableSet.Builder() - .add(SnakemakeNames.RULE_KEYWORD) - .add(SnakemakeNames.CHECKPOINT_KEYWORD) - .add(SnakemakeNames.SUBWORKFLOW_KEYWORD) - .build()!! + .add(SnakemakeNames.RULE_KEYWORD) + .add(SnakemakeNames.CHECKPOINT_KEYWORD) + .add(SnakemakeNames.SUBWORKFLOW_KEYWORD) + .add(SnakemakeNames.MODULE_KEYWORD) + .add(SnakemakeNames.USE_KEYWORD) + .build()!! val PYTHON_BLOCK_KEYWORDS = ImmutableSet.Builder() - .add(SnakemakeNames.WORKFLOW_ONSTART_KEYWORD) - .add(SnakemakeNames.WORKFLOW_ONSUCCESS_KEYWORD) - .add(SnakemakeNames.WORKFLOW_ONERROR_KEYWORD) - .build()!! + .add(SnakemakeNames.WORKFLOW_ONSTART_KEYWORD) + .add(SnakemakeNames.WORKFLOW_ONSUCCESS_KEYWORD) + .add(SnakemakeNames.WORKFLOW_ONERROR_KEYWORD) + .build()!! val KEYWORDS = ImmutableMap.Builder() - .put(SnakemakeNames.RULE_KEYWORD, SmkTokenTypes.RULE_KEYWORD) - .put(SnakemakeNames.CHECKPOINT_KEYWORD, SmkTokenTypes.CHECKPOINT_KEYWORD) - .put(SnakemakeNames.WORKFLOW_CONFIGFILE_KEYWORD, SmkTokenTypes.WORKFLOW_CONFIGFILE_KEYWORD) - .put(SnakemakeNames.WORKFLOW_REPORT_KEYWORD, SmkTokenTypes.WORKFLOW_REPORT_KEYWORD) - .put(SnakemakeNames.WORKFLOW_WILDCARD_CONSTRAINTS_KEYWORD, SmkTokenTypes.WORKFLOW_WILDCARD_CONSTRAINTS_KEYWORD) - .put(SnakemakeNames.WORKFLOW_SINGULARITY_KEYWORD, SmkTokenTypes.WORKFLOW_SINGULARITY_KEYWORD) - .put(SnakemakeNames.WORKFLOW_INCLUDE_KEYWORD, SmkTokenTypes.WORKFLOW_INCLUDE_KEYWORD) - .put(SnakemakeNames.WORKFLOW_WORKDIR_KEYWORD, SmkTokenTypes.WORKFLOW_WORKDIR_KEYWORD) - .put(SnakemakeNames.WORKFLOW_ENVVARS_KEYWORD, SmkTokenTypes.WORKFLOW_ENVVARS_KEYWORD) - .put(SnakemakeNames.WORKFLOW_CONTAINER_KEYWORD, SmkTokenTypes.WORKFLOW_CONTAINER_KEYWORD) - .put(SnakemakeNames.WORKFLOW_CONTAINERIZED_KEYWORD, SmkTokenTypes.WORKFLOW_CONTAINERIZED_KEYWORD) - .put(SnakemakeNames.WORKFLOW_LOCALRULES_KEYWORD, SmkTokenTypes.WORKFLOW_LOCALRULES_KEYWORD) - .put(SnakemakeNames.WORKFLOW_RULEORDER_KEYWORD, SmkTokenTypes.WORKFLOW_RULEORDER_KEYWORD) - .put(SnakemakeNames.WORKFLOW_ONSUCCESS_KEYWORD, SmkTokenTypes.WORKFLOW_ONSUCCESS_KEYWORD) - .put(SnakemakeNames.WORKFLOW_ONERROR_KEYWORD, SmkTokenTypes.WORKFLOW_ONERROR_KEYWORD) - .put(SnakemakeNames.WORKFLOW_ONSTART_KEYWORD, SmkTokenTypes.WORKFLOW_ONSTART_KEYWORD) - .put(SnakemakeNames.SUBWORKFLOW_KEYWORD, SmkTokenTypes.SUBWORKFLOW_KEYWORD) - .build()!! + .put(SnakemakeNames.RULE_KEYWORD, SmkTokenTypes.RULE_KEYWORD) + .put(SnakemakeNames.CHECKPOINT_KEYWORD, SmkTokenTypes.CHECKPOINT_KEYWORD) + .put(SnakemakeNames.WORKFLOW_CONFIGFILE_KEYWORD, SmkTokenTypes.WORKFLOW_CONFIGFILE_KEYWORD) + .put(SnakemakeNames.WORKFLOW_REPORT_KEYWORD, SmkTokenTypes.WORKFLOW_REPORT_KEYWORD) + .put( + SnakemakeNames.WORKFLOW_WILDCARD_CONSTRAINTS_KEYWORD, + SmkTokenTypes.WORKFLOW_WILDCARD_CONSTRAINTS_KEYWORD + ) + .put(SnakemakeNames.WORKFLOW_SINGULARITY_KEYWORD, SmkTokenTypes.WORKFLOW_SINGULARITY_KEYWORD) + .put(SnakemakeNames.WORKFLOW_INCLUDE_KEYWORD, SmkTokenTypes.WORKFLOW_INCLUDE_KEYWORD) + .put(SnakemakeNames.WORKFLOW_WORKDIR_KEYWORD, SmkTokenTypes.WORKFLOW_WORKDIR_KEYWORD) + .put(SnakemakeNames.WORKFLOW_ENVVARS_KEYWORD, SmkTokenTypes.WORKFLOW_ENVVARS_KEYWORD) + .put(SnakemakeNames.WORKFLOW_CONTAINER_KEYWORD, SmkTokenTypes.WORKFLOW_CONTAINER_KEYWORD) + .put(SnakemakeNames.WORKFLOW_CONTAINERIZED_KEYWORD, SmkTokenTypes.WORKFLOW_CONTAINERIZED_KEYWORD) + .put(SnakemakeNames.WORKFLOW_LOCALRULES_KEYWORD, SmkTokenTypes.WORKFLOW_LOCALRULES_KEYWORD) + .put(SnakemakeNames.WORKFLOW_RULEORDER_KEYWORD, SmkTokenTypes.WORKFLOW_RULEORDER_KEYWORD) + .put(SnakemakeNames.WORKFLOW_ONSUCCESS_KEYWORD, SmkTokenTypes.WORKFLOW_ONSUCCESS_KEYWORD) + .put(SnakemakeNames.WORKFLOW_ONERROR_KEYWORD, SmkTokenTypes.WORKFLOW_ONERROR_KEYWORD) + .put(SnakemakeNames.WORKFLOW_ONSTART_KEYWORD, SmkTokenTypes.WORKFLOW_ONSTART_KEYWORD) + .put(SnakemakeNames.SUBWORKFLOW_KEYWORD, SmkTokenTypes.SUBWORKFLOW_KEYWORD) + .put(SnakemakeNames.MODULE_KEYWORD, SmkTokenTypes.MODULE_KEYWORD) + .put(SnakemakeNames.USE_KEYWORD, SmkTokenTypes.USE_KEYWORD) + .build()!! val KEYWORDS_2_TEXT = KEYWORDS.map { it.value to it.key }.toMap() } diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/SmkElementVisitor.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/SmkElementVisitor.kt index 1c5d03e16..b3fd8c3b4 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/SmkElementVisitor.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/SmkElementVisitor.kt @@ -27,6 +27,14 @@ interface SmkElementVisitor { pyElementVisitor.visitPyElement(subworkflow) } + fun visitSmkModule(module: SmkModule) { + pyElementVisitor.visitPyElement(module) + } + + fun visitSmkUse(use: SmkUse) { + pyElementVisitor.visitPyElement(use) + } + fun visitSmkRuleOrCheckpointArgsSection(st: SmkRuleOrCheckpointArgsSection) { pyElementVisitor.visitPyStatement(st) } @@ -39,6 +47,10 @@ interface SmkElementVisitor { pyElementVisitor.visitPyStatement(st) } + fun visitSmkModuleArgsSection(st: SmkModuleArgsSection) { + pyElementVisitor.visitPyStatement(st) + } + fun visitSmkWorkflowArgsSection(st: SmkWorkflowArgsSection) { pyElementVisitor.visitPyStatement(st) } diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/SmkFile.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/SmkFile.kt index 5c419f3e6..5e096b5c6 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/SmkFile.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/SmkFile.kt @@ -37,6 +37,36 @@ class SmkFile(viewProvider: FileViewProvider) : PyFileImpl(viewProvider, Snakema return subworkflowNameAndPsi } + fun collectModules(): List> { + val moduleNameAndPsi = arrayListOf>() + + acceptChildren(object : PyElementVisitor(), SmkElementVisitor { + override val pyElementVisitor: PyElementVisitor = this + + override fun visitSmkModule(module: SmkModule) { + if (module.name != null) { + moduleNameAndPsi.add(module.name!! to module) + } + } + }) + return moduleNameAndPsi + } + + fun collectUses(): List> { + val useNameAndPsi = arrayListOf>() + + acceptChildren(object : PyElementVisitor(), SmkElementVisitor { + override val pyElementVisitor: PyElementVisitor = this + + override fun visitSmkUse(use: SmkUse) { + if (use.name != null) { + useNameAndPsi.add(use.name!! to use) + } + } + }) + return useNameAndPsi + } + fun collectCheckPoints(): List> { val checkpointNameAndPsi = arrayListOf>() diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/SmkPsiElements.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/SmkPsiElements.kt index 30d04f2a8..986d3d2f1 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/SmkPsiElements.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/SmkPsiElements.kt @@ -7,9 +7,9 @@ import com.jetbrains.python.codeInsight.controlflow.ScopeOwner import com.jetbrains.python.psi.* import com.jetbrains.snakecharm.codeInsight.SnakemakeAPI.WILDCARDS_DEFINING_SECTIONS_KEYWORDS import com.jetbrains.snakecharm.codeInsight.SnakemakeAPI.WILDCARDS_EXPANDING_SECTIONS_KEYWORDS -import com.jetbrains.snakecharm.lang.psi.stubs.SmkCheckpointStub -import com.jetbrains.snakecharm.lang.psi.stubs.SmkRuleStub -import com.jetbrains.snakecharm.lang.psi.stubs.SmkSubworkflowStub +import com.jetbrains.snakecharm.codeInsight.resolve.SmkResolveUtil +import com.jetbrains.snakecharm.lang.psi.stubs.* +import com.jetbrains.snakecharm.stringLanguage.lang.psi.SmkSLExpression interface SmkToplevelSection : SmkSection { override fun getParentRuleOrCheckPoint(): SmkRuleOrCheckpoint? = null @@ -21,6 +21,10 @@ interface SmkCheckPoint : SmkRuleOrCheckpoint, StubBasedPsiElement, StubBasedPsiElement +interface SmkModule : SmkRuleLike, StubBasedPsiElement + +interface SmkUse : SmkRuleOrCheckpoint, StubBasedPsiElement + interface SmkRuleOrCheckpointArgsSection : SmkArgsSection, PyTypedElement { // PyNamedElementContainer /** * Considers any variable as wildcard @@ -44,6 +48,10 @@ interface SmkSubworkflowArgsSection : SmkArgsSection { override fun getParentRuleOrCheckPoint(): SmkRuleOrCheckpoint? = null } +interface SmkModuleArgsSection : SmkArgsSection { + override fun getParentRuleOrCheckPoint(): SmkRuleOrCheckpoint? = null +} + interface SmkWorkflowArgsSection : SmkArgsSection, SmkToplevelSection // PyNamedElementContainer interface SmkRunSection : SmkSection, PyStatementListContainer, PyDocStringOwner { diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/elementTypes/SmkElementTypes.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/elementTypes/SmkElementTypes.kt index cd5be5456..a7e5cbba1 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/elementTypes/SmkElementTypes.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/elementTypes/SmkElementTypes.kt @@ -10,60 +10,88 @@ import com.jetbrains.snakecharm.lang.psi.impl.* */ object SmkElementTypes { val RULE_OR_CHECKPOINT_ARGS_SECTION_STATEMENT = PyElementType( - "SMK_RULE_OR_CHECKPOINT_ARGS_SECTION_STATEMENT" + "SMK_RULE_OR_CHECKPOINT_ARGS_SECTION_STATEMENT" ) { SmkRuleOrCheckpointArgsSectionImpl(it) } - val SUBWORKFLOW_ARGS_SECTION_STATEMENT = PyElementType( - "SMK_SUBWORKFLOW_ARGS_SECTION_STATEMENT" + val SUBWORKFLOW_ARGS_SECTION_STATEMENT = PyElementType( + "SMK_SUBWORKFLOW_ARGS_SECTION_STATEMENT" ) { SmkSubworkflowArgsSectionImpl(it) } + val MODULE_ARGS_SECTION_STATEMENT = PyElementType( + "SMK_MODULE_ARGS_SECTION_STATEMENT" + ) { + SmkModuleArgsSectionImpl(it) + } + + val USE_ARGS_SECTION_STATEMENT = PyElementType( + "SMK_USE_ARGS_SECTION_STATEMENT" + ) { + SmkUseArgsSectionImpl(it) + } + + val USE_NAME_IDENTIFIER = PyElementType( + "SMK_USE_NAME_IDENTIFIER" + ) { + SmkUseNameIdentifier(it) + } + + val USE_IMPORTED_RULES_NAMES = PyElementType( + "USE_IMPORTED_RULES_NAMES" + ) { + SmkImportedRulesNames(it) + } + val WORKFLOW_ARGS_SECTION_STATEMENT = PyElementType( - "SMK_WORKFLOW_ARGS_SECTION_STATEMENT" + "SMK_WORKFLOW_ARGS_SECTION_STATEMENT" ) { SmkWorkflowArgsSectionImpl(it) } val RULE_OR_CHECKPOINT_RUN_SECTION_STATEMENT = PyElementType( - "SMK_RULE_OR_CHECKPOINT_RUN_SECTION_STATEMENT" + "SMK_RULE_OR_CHECKPOINT_RUN_SECTION_STATEMENT" ) { SmkRunSectionImpl(it) } val WORKFLOW_PY_BLOCK_SECTION_STATEMENT = PyElementType( - "SMK_WORKFLOW_PY_BLOCK_SECTION_STATEMENT" + "SMK_WORKFLOW_PY_BLOCK_SECTION_STATEMENT" ) { SmkWorkflowPythonBlockSectionImpl(it) } val WORKFLOW_LOCALRULES_SECTION_STATEMENT = PyElementType( - "SMK_WORKFLOW_LOCALRULES_SECTION_STATEMENT" + "SMK_WORKFLOW_LOCALRULES_SECTION_STATEMENT" ) { SmkWorkflowLocalrulesSectionImpl(it) } val WORKFLOW_RULEORDER_SECTION_STATEMENT = PyElementType( - "SMK_WORKFLOW_RULEORDER_SECTION_STATEMENT" + "SMK_WORKFLOW_RULEORDER_SECTION_STATEMENT" ) { SmkWorkflowRuleorderSectionImpl(it) } val REFERENCE_EXPRESSION = PyElementType( - "SMK_REFERENCE_EXPRESSION" + "SMK_REFERENCE_EXPRESSION" ) { SmkReferenceExpressionImpl(it) } val SMK_PY_REFERENCE_EXPRESSION = PyElementType( - "SMK_PY_REFERENCE_EXPRESSION" + "SMK_PY_REFERENCE_EXPRESSION" ) { SmkPyReferenceExpressionImpl(it) } val RULE_LIKE_STATEMENTS = TokenSet.create( - SmkStubElementTypes.RULE_DECLARATION_STATEMENT, SmkStubElementTypes.CHECKPOINT_DECLARATION_STATEMENT, SmkStubElementTypes.SUBWORKFLOW_DECLARATION_STATEMENT + SmkStubElementTypes.RULE_DECLARATION_STATEMENT, + SmkStubElementTypes.CHECKPOINT_DECLARATION_STATEMENT, + SmkStubElementTypes.SUBWORKFLOW_DECLARATION_STATEMENT, + SmkStubElementTypes.MODULE_DECLARATION_STATEMENT, + SmkStubElementTypes.USE_DECLARATION_STATEMENT ) } \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/SmkModuleArgsSectionImpl.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/SmkModuleArgsSectionImpl.kt new file mode 100644 index 000000000..514245817 --- /dev/null +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/SmkModuleArgsSectionImpl.kt @@ -0,0 +1,15 @@ +package com.jetbrains.snakecharm.lang.psi.impl + +import com.intellij.lang.ASTNode +import com.jetbrains.python.psi.PyElementVisitor +import com.jetbrains.snakecharm.lang.psi.SmkElementVisitor +import com.jetbrains.snakecharm.lang.psi.SmkModuleArgsSection + +class SmkModuleArgsSectionImpl(node: ASTNode) : SmkArgsSectionImpl(node), SmkModuleArgsSection { + override fun acceptPyVisitor(pyVisitor: PyElementVisitor?) { + when (pyVisitor) { + is SmkElementVisitor -> pyVisitor.visitSmkModuleArgsSection(this) + else -> super.acceptPyVisitor(pyVisitor) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/SmkModuleImpl.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/SmkModuleImpl.kt new file mode 100644 index 000000000..55da0d018 --- /dev/null +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/SmkModuleImpl.kt @@ -0,0 +1,26 @@ +package com.jetbrains.snakecharm.lang.psi.impl + +import com.intellij.lang.ASTNode +import com.jetbrains.python.psi.PyElementType +import com.jetbrains.python.psi.PyElementVisitor +import com.jetbrains.snakecharm.lang.parser.SmkTokenTypes +import com.jetbrains.snakecharm.lang.psi.SmkElementVisitor +import com.jetbrains.snakecharm.lang.psi.SmkModule +import com.jetbrains.snakecharm.lang.psi.SmkModuleArgsSection +import com.jetbrains.snakecharm.lang.psi.elementTypes.SmkStubElementTypes +import com.jetbrains.snakecharm.lang.psi.stubs.SmkModuleStub + +class SmkModuleImpl : SmkRuleLikeImpl, SmkModule { + + constructor(node: ASTNode) : super(node) + constructor(stub: SmkModuleStub) : super(stub, SmkStubElementTypes.MODULE_DECLARATION_STATEMENT) + + override val sectionTokenType: PyElementType = SmkTokenTypes.MODULE_KEYWORD + + override fun acceptPyVisitor(pyVisitor: PyElementVisitor?) { + when (pyVisitor) { + is SmkElementVisitor -> pyVisitor.visitSmkModule(this) + else -> super.acceptPyVisitor(pyVisitor) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/SmkUseArgsSectionImpl.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/SmkUseArgsSectionImpl.kt new file mode 100644 index 000000000..a1c3997c6 --- /dev/null +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/SmkUseArgsSectionImpl.kt @@ -0,0 +1,25 @@ +package com.jetbrains.snakecharm.lang.psi.impl + +import com.intellij.lang.ASTNode +import com.jetbrains.python.psi.PyElementVisitor +import com.jetbrains.python.psi.types.PyType +import com.jetbrains.python.psi.types.TypeEvalContext +import com.jetbrains.snakecharm.lang.psi.SmkElementVisitor +import com.jetbrains.snakecharm.lang.psi.SmkRuleOrCheckpointArgsSection +import com.jetbrains.snakecharm.lang.psi.types.SmkRuleLikeSectionArgsType + +class SmkUseArgsSectionImpl(node: ASTNode) : SmkArgsSectionImpl(node), SmkRuleOrCheckpointArgsSection { + override fun acceptPyVisitor(pyVisitor: PyElementVisitor?) { + when (pyVisitor) { + is SmkElementVisitor -> pyVisitor.visitSmkRuleOrCheckpointArgsSection(this) + else -> super.acceptPyVisitor(pyVisitor) + } + } + + override fun multilineSectionDefinition(): Boolean = + SmkRuleOrCheckpointArgsSectionImpl.multilineSectionDefinition(this) + + + override fun getType(context: TypeEvalContext, key: TypeEvalContext.Key): PyType = + SmkRuleLikeSectionArgsType(this) +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/SmkUseImpl.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/SmkUseImpl.kt new file mode 100644 index 000000000..aa9bf54b5 --- /dev/null +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/SmkUseImpl.kt @@ -0,0 +1,32 @@ +package com.jetbrains.snakecharm.lang.psi.impl + +import com.intellij.lang.ASTNode +import com.jetbrains.python.psi.PyElementType +import com.jetbrains.python.psi.PyElementVisitor +import com.jetbrains.python.psi.types.TypeEvalContext +import com.jetbrains.snakecharm.lang.parser.SmkTokenTypes +import com.jetbrains.snakecharm.lang.psi.SmkElementVisitor +import com.jetbrains.snakecharm.lang.psi.SmkRuleOrCheckpointArgsSection +import com.jetbrains.snakecharm.lang.psi.SmkUse +import com.jetbrains.snakecharm.lang.psi.elementTypes.SmkStubElementTypes +import com.jetbrains.snakecharm.lang.psi.stubs.SmkUseStub +import com.jetbrains.snakecharm.lang.psi.types.SmkRuleLikeSectionType + +class SmkUseImpl : SmkRuleLikeImpl, SmkUse { + + constructor(node: ASTNode) : super(node) + constructor(stub: SmkUseStub) : super(stub, SmkStubElementTypes.USE_DECLARATION_STATEMENT) + + override val sectionTokenType: PyElementType = SmkTokenTypes.USE_KEYWORD + + override val wildcardsElement = SmkRuleImpl.createFakeWildcardsPsiElement(this) + + override fun getType(context: TypeEvalContext, key: TypeEvalContext.Key) = SmkRuleLikeSectionType(this) + + override fun acceptPyVisitor(pyVisitor: PyElementVisitor?) { + when (pyVisitor) { + is SmkElementVisitor -> pyVisitor.visitSmkUse(this) + else -> super.acceptPyVisitor(pyVisitor) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/SmkUseNameIdentifier.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/SmkUseNameIdentifier.kt new file mode 100644 index 000000000..c7daa4d4a --- /dev/null +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/SmkUseNameIdentifier.kt @@ -0,0 +1,8 @@ +package com.jetbrains.snakecharm.lang.psi.impl + +import com.intellij.lang.ASTNode +import com.jetbrains.python.psi.impl.PyElementImpl + +class SmkUseNameIdentifier(node: ASTNode) : PyElementImpl(node) + +class SmkImportedRulesNames(node: ASTNode) : PyElementImpl(node) \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/refs/SmkRuleOrCheckpointNameReference.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/refs/SmkRuleOrCheckpointNameReference.kt index 6cfd42ecc..40a52236b 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/refs/SmkRuleOrCheckpointNameReference.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/refs/SmkRuleOrCheckpointNameReference.kt @@ -1,20 +1,27 @@ package com.jetbrains.snakecharm.lang.psi.impl.refs import com.intellij.lang.annotation.HighlightSeverity +import com.intellij.psi.util.elementType +import com.intellij.psi.util.parentOfType import com.jetbrains.python.psi.AccessDirection import com.jetbrains.python.psi.impl.references.PyReferenceImpl import com.jetbrains.python.psi.resolve.PyResolveContext import com.jetbrains.python.psi.resolve.RatedResolveResult import com.jetbrains.python.psi.types.TypeEvalContext import com.jetbrains.snakecharm.SnakemakeBundle +import com.jetbrains.snakecharm.codeInsight.resolve.SmkResolveUtil import com.jetbrains.snakecharm.lang.psi.SmkFile import com.jetbrains.snakecharm.lang.psi.SmkReferenceExpression +import com.jetbrains.snakecharm.lang.psi.SmkRuleOrCheckpoint +import com.jetbrains.snakecharm.lang.psi.elementTypes.SmkElementTypes +import com.jetbrains.snakecharm.lang.psi.elementTypes.SmkStubElementTypes import com.jetbrains.snakecharm.lang.psi.types.SmkCheckpointType import com.jetbrains.snakecharm.lang.psi.types.SmkRulesType +import com.jetbrains.snakecharm.lang.psi.types.SmkUsesType class SmkRuleOrCheckpointNameReference( - element: SmkReferenceExpression, - context: PyResolveContext + element: SmkReferenceExpression, + context: PyResolveContext ) : PyReferenceImpl(element, context) { //.Poly(element, textRange, true), PsiReferenceEx { override fun getElement() = super.getElement() as SmkReferenceExpression @@ -23,7 +30,7 @@ class SmkRuleOrCheckpointNameReference( * Severity is handled by [SmkPyQualifiedReference] */ override fun getUnresolvedDescription() = SnakemakeBundle.message( - "INSP.py.unresolved.ref.rule.like.ref.message", element.name ?: "" + "INSP.py.unresolved.ref.rule.like.ref.message", element.name ?: "" ) @@ -36,13 +43,60 @@ class SmkRuleOrCheckpointNameReference( return mutableListOf() } + val name = element.text val results = arrayListOf() val ctx = AccessDirection.of(this.myElement) results.addAll(SmkRulesType(null, smkFile).resolveMember(name, element, ctx, myContext)) results.addAll(SmkCheckpointType(null, smkFile).resolveMember(name, element, ctx, myContext)) + results.addAll(SmkUsesType(null, smkFile).resolveMember(name, element, ctx, myContext)) + results.addAll(collectModulesAndResolveThem(smkFile, name)) + results.addAll(collectModuleFromUseSection(element)) return results } + + /** + * Collects all modules sections names from local file which name is [name] + */ + private fun collectModulesAndResolveThem(smkFile: SmkFile, name: String): List { + val modules = smkFile.collectModules().map { it.second }.filter { elem -> elem.name == name } + if (modules.isEmpty()) { + return emptyList() + } + return modules.map { element -> + RatedResolveResult(SmkResolveUtil.RATE_NORMAL, element) + } + } + + /** + * Resolve rule reference, which is declared in 'use' section. + * It refers to module, which imports such rule. + * If there no such module, return empty array. + */ + private fun collectModuleFromUseSection( + element: SmkReferenceExpression + ): List { + if (element.parent.elementType != SmkElementTypes.USE_IMPORTED_RULES_NAMES) { + return emptyList() + } + val parent = element.parentOfType() + if (parent != null && parent.elementType == SmkStubElementTypes.USE_DECLARATION_STATEMENT) { + var moduleRef = element.parent.nextSibling + while (moduleRef != null && moduleRef.elementType != SmkElementTypes.REFERENCE_EXPRESSION) { + moduleRef = moduleRef.nextSibling + } + if (moduleRef != null) { + return listOf( + RatedResolveResult( + SmkResolveUtil.RATE_NORMAL, + (moduleRef as SmkReferenceExpression).reference.resolve() + ) + ) + } + } + + return emptyList() + } } \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/stubs/SmkModuleElementType.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/stubs/SmkModuleElementType.kt new file mode 100644 index 000000000..d59575773 --- /dev/null +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/stubs/SmkModuleElementType.kt @@ -0,0 +1,23 @@ +package com.jetbrains.snakecharm.lang.psi.impl.stubs + +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement +import com.intellij.psi.stubs.StubElement +import com.jetbrains.snakecharm.lang.psi.SmkModule +import com.jetbrains.snakecharm.lang.psi.impl.SmkModuleImpl +import com.jetbrains.snakecharm.lang.psi.stubs.SmkModuleStub + +class SmkModuleElementType + : SmkRuleLikeElementType("SMK_MODULE_DECLARATION_STATEMENT", null) { + + override fun createStub(name: String?, parentStub: StubElement<*>?): SmkModuleStub = + SmkModuleStubImpl(name, parentStub) + + override fun createStub(psi: SmkModule, parentStub: StubElement?): SmkModuleStub = + SmkModuleStubImpl(psi.name, parentStub) + + override fun createPsi(stub: SmkModuleStub): SmkModule = SmkModuleImpl(stub) + + override fun createElement(node: ASTNode): PsiElement = SmkModuleImpl(node) + +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/stubs/SmkRuleLikeStubImpl.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/stubs/SmkRuleLikeStubImpl.kt index cd613a6d0..209c83a92 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/stubs/SmkRuleLikeStubImpl.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/stubs/SmkRuleLikeStubImpl.kt @@ -6,35 +6,41 @@ import com.intellij.psi.stubs.NamedStub import com.intellij.psi.stubs.StubBase import com.intellij.psi.stubs.StubElement import com.jetbrains.python.psi.PyElement -import com.jetbrains.snakecharm.lang.psi.SmkCheckPoint -import com.jetbrains.snakecharm.lang.psi.SmkRule -import com.jetbrains.snakecharm.lang.psi.SmkSubworkflow -import com.jetbrains.snakecharm.lang.psi.elementTypes.SmkStubElementTypes.CHECKPOINT_DECLARATION_STATEMENT -import com.jetbrains.snakecharm.lang.psi.elementTypes.SmkStubElementTypes.RULE_DECLARATION_STATEMENT -import com.jetbrains.snakecharm.lang.psi.elementTypes.SmkStubElementTypes.SUBWORKFLOW_DECLARATION_STATEMENT -import com.jetbrains.snakecharm.lang.psi.stubs.SmkCheckpointStub -import com.jetbrains.snakecharm.lang.psi.stubs.SmkRuleStub -import com.jetbrains.snakecharm.lang.psi.stubs.SmkSubworkflowStub +import com.jetbrains.snakecharm.lang.psi.* +import com.jetbrains.snakecharm.lang.psi.elementTypes.SmkStubElementTypes.* +import com.jetbrains.snakecharm.lang.psi.stubs.* -abstract class SmkRuleLikeStubImpl, PsiT>( - private val myName: String?, - parent: StubElement<*>?, - type: IStubElementType -) : StubBase(parent, type), NamedStub where PsiT : PsiNamedElement, PsiT: PyElement { +abstract class SmkRuleLikeStubImpl, PsiT>( + private val myName: String?, + parent: StubElement<*>?, + type: IStubElementType +) : StubBase(parent, type), NamedStub where PsiT : PsiNamedElement, PsiT : PyElement { override fun getName() = myName } class SmkCheckpointStubImpl( - name: String?, - parent: StubElement<*>? -): SmkRuleLikeStubImpl(name, parent, CHECKPOINT_DECLARATION_STATEMENT), SmkCheckpointStub + name: String?, + parent: StubElement<*>? +) : SmkRuleLikeStubImpl(name, parent, CHECKPOINT_DECLARATION_STATEMENT), + SmkCheckpointStub class SmkRuleStubImpl( - name: String?, - parent: StubElement<*>? -): SmkRuleLikeStubImpl(name, parent, RULE_DECLARATION_STATEMENT), SmkRuleStub + name: String?, + parent: StubElement<*>? +) : SmkRuleLikeStubImpl(name, parent, RULE_DECLARATION_STATEMENT), SmkRuleStub class SmkSubworkflowStubImpl( - name: String?, - parent: StubElement<*>? -): SmkRuleLikeStubImpl(name, parent, SUBWORKFLOW_DECLARATION_STATEMENT), SmkSubworkflowStub + name: String?, + parent: StubElement<*>? +) : SmkRuleLikeStubImpl(name, parent, SUBWORKFLOW_DECLARATION_STATEMENT), + SmkSubworkflowStub + +class SmkModuleStubImpl( + name: String?, + parent: StubElement<*>? +) : SmkRuleLikeStubImpl(name, parent, MODULE_DECLARATION_STATEMENT), SmkModuleStub + +class SmkUseStubImpl( + name: String?, + parent: StubElement<*>? +) : SmkRuleLikeStubImpl(name, parent, USE_DECLARATION_STATEMENT), SmkUseStub \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/stubs/SmkUseElementType.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/stubs/SmkUseElementType.kt new file mode 100644 index 000000000..2e96092df --- /dev/null +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/impl/stubs/SmkUseElementType.kt @@ -0,0 +1,23 @@ +package com.jetbrains.snakecharm.lang.psi.impl.stubs + +import com.intellij.lang.ASTNode +import com.intellij.psi.PsiElement +import com.intellij.psi.stubs.StubElement +import com.jetbrains.snakecharm.lang.psi.SmkUse +import com.jetbrains.snakecharm.lang.psi.impl.SmkUseImpl +import com.jetbrains.snakecharm.lang.psi.stubs.SmkUseNameIndex.Companion.KEY +import com.jetbrains.snakecharm.lang.psi.stubs.SmkUseStub + +class SmkUseElementType + : SmkRuleLikeElementType("SMK_USE_DECLARATION_STATEMENT", KEY) { + + override fun createStub(name: String?, parentStub: StubElement<*>?): SmkUseStub = + SmkUseStubImpl(name, parentStub) + + override fun createStub(psi: SmkUse, parentStub: StubElement?): SmkUseStub = + createStub(psi.name, parentStub) + + override fun createPsi(stub: SmkUseStub): SmkUse = SmkUseImpl(stub) + + override fun createElement(node: ASTNode): PsiElement = SmkUseImpl(node) +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/stubs/SmkStubs.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/stubs/SmkStubs.kt index e4cd52313..e44d0172a 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/stubs/SmkStubs.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/stubs/SmkStubs.kt @@ -1,10 +1,10 @@ package com.jetbrains.snakecharm.lang.psi.stubs import com.intellij.psi.stubs.NamedStub -import com.jetbrains.snakecharm.lang.psi.SmkCheckPoint -import com.jetbrains.snakecharm.lang.psi.SmkRule -import com.jetbrains.snakecharm.lang.psi.SmkSubworkflow +import com.jetbrains.snakecharm.lang.psi.* -interface SmkRuleStub: NamedStub -interface SmkCheckpointStub: NamedStub -interface SmkSubworkflowStub: NamedStub +interface SmkRuleStub : NamedStub +interface SmkCheckpointStub : NamedStub +interface SmkSubworkflowStub : NamedStub +interface SmkModuleStub : NamedStub +interface SmkUseStub : NamedStub \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/stubs/SmkUseNameIndex.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/stubs/SmkUseNameIndex.kt new file mode 100644 index 000000000..51953c4f1 --- /dev/null +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/stubs/SmkUseNameIndex.kt @@ -0,0 +1,24 @@ +package com.jetbrains.snakecharm.lang.psi.stubs + +import com.intellij.openapi.project.Project +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.search.ProjectScope +import com.intellij.psi.stubs.StringStubIndexExtension +import com.intellij.psi.stubs.StubIndex +import com.intellij.psi.stubs.StubIndexKey +import com.jetbrains.snakecharm.lang.psi.SmkUse + +class SmkUseNameIndex : StringStubIndexExtension() { + override fun getKey() = KEY + + companion object { + val KEY = StubIndexKey.createIndexKey("Smk.use.shortName") + + fun find( + name: String, + project: Project, + scope: GlobalSearchScope = ProjectScope.getAllScope(project) + ): Collection = + StubIndex.getElements(KEY, name, project, scope, SmkUse::class.java) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/types/AbstractSmkRuleOrCheckpointType.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/types/AbstractSmkRuleOrCheckpointType.kt index 816ed32d4..0c4ea901a 100644 --- a/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/types/AbstractSmkRuleOrCheckpointType.kt +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/types/AbstractSmkRuleOrCheckpointType.kt @@ -26,11 +26,11 @@ import com.jetbrains.snakecharm.lang.psi.impl.SmkPsiUtil import gnu.trove.THashSet -abstract class AbstractSmkRuleOrCheckpointType( - private val containingRule: T?, - private val typeName: String, - private val indexKey: StubIndexKey, - private val clazz: Class +abstract class AbstractSmkRuleOrCheckpointType( + private val containingRule: T?, + private val typeName: String, + private val indexKey: StubIndexKey, + private val clazz: Class ) : PyType { abstract val currentFileDeclarations: List @@ -38,9 +38,9 @@ abstract class AbstractSmkRuleOrCheckpointType( override fun getName() = typeName override fun getCompletionVariants( - completionPrefix: String?, - location: PsiElement, - context: ProcessingContext? + completionPrefix: String?, + location: PsiElement, + context: ProcessingContext? ): Array = emptyArray() override fun assertValid(message: String?) { @@ -53,17 +53,17 @@ abstract class AbstractSmkRuleOrCheckpointType( } override fun resolveMember( - name: String, - location: PyExpression?, - direction: AccessDirection, - resolveContext: PyResolveContext + name: String, + location: PyExpression?, + direction: AccessDirection, + resolveContext: PyResolveContext ): List { if (!SmkPsiUtil.isInsideSnakemakeOrSmkSLFile(location) || location == null) { return emptyList() } val results = findAvailableRuleLikeElementByName( - location.originalElement, name, indexKey, clazz + location.originalElement, name, indexKey, clazz ) { currentFileDeclarations } if (results.isEmpty()) { @@ -78,7 +78,7 @@ abstract class AbstractSmkRuleOrCheckpointType( override fun isBuiltin() = false companion object { - fun createRuleLikeLookupItem(name: String, elem: T): LookupElement { + fun createRuleLikeLookupItem(name: String, elem: T): LookupElement { val containingFileName = elem.containingFile.name /* it is important to use originalFile to access virtualFile @@ -90,8 +90,8 @@ abstract class AbstractSmkRuleOrCheckpointType( containingFileName } else { val fileContentRootDirectory = ProjectRootManager.getInstance(elem.project) - .fileIndex - .getContentRootForFile(virtualFile) + .fileIndex + .getContentRootForFile(virtualFile) if (fileContentRootDirectory == null) { containingFileName } else { @@ -100,22 +100,22 @@ abstract class AbstractSmkRuleOrCheckpointType( } return PrioritizedLookupElement.withPriority( - LookupElementBuilder - .createWithSmartPointer(name, elem) - .withTypeText(displayPath) - .withIcon(elem.getIcon(0)), - SmkCompletionUtil.RULES_AND_CHECKPOINTS_PRIORITY + LookupElementBuilder + .createWithSmartPointer(name, elem) + .withTypeText(displayPath) + .withIcon(elem.getIcon(0)), + SmkCompletionUtil.RULES_AND_CHECKPOINTS_PRIORITY ) } - fun findAvailableRuleLikeElementByName( - location: PsiElement, - name: String, - indexKey: StubIndexKey, - clazz: Class, - getCurrentFileDeclarationsFunction: () -> List + fun findAvailableRuleLikeElementByName( + location: PsiElement, + name: String, + indexKey: StubIndexKey, + clazz: Class, + getCurrentFileDeclarationsFunction: () -> List ): Collection { - val module = location.let { ModuleUtilCore.findModuleForPsiElement(it.originalElement) } + val module = location.let { ModuleUtilCore.findModuleForPsiElement(it.originalElement) } return if (module != null) { val scope = searchScope(module) StubIndex.getElements(indexKey, name, module.project, scope, clazz) @@ -127,18 +127,18 @@ abstract class AbstractSmkRuleOrCheckpointType( } } - fun getVariantsFromIndex( - indexKey: StubIndexKey, - module: Module, - clazz: Class, - scope: GlobalSearchScope = searchScope(module) + fun getVariantsFromIndex( + indexKey: StubIndexKey, + module: Module, + clazz: Class, + scope: GlobalSearchScope = searchScope(module) ): List { val results = mutableListOf() val project = module.project val stubIndex = StubIndex.getInstance() val allKeys = THashSet() stubIndex.processAllKeys( - indexKey, Processors.cancelableCollectProcessor(allKeys), scope, null + indexKey, Processors.cancelableCollectProcessor(allKeys), scope, null ) for (key in allKeys) { diff --git a/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/types/SmkUsesType.kt b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/types/SmkUsesType.kt new file mode 100644 index 000000000..a5f4a9021 --- /dev/null +++ b/src/main/kotlin/com/jetbrains/snakecharm/lang/psi/types/SmkUsesType.kt @@ -0,0 +1,17 @@ +package com.jetbrains.snakecharm.lang.psi.types + +import com.jetbrains.snakecharm.codeInsight.SnakemakeAPI +import com.jetbrains.snakecharm.lang.psi.SmkFile +import com.jetbrains.snakecharm.lang.psi.SmkUse +import com.jetbrains.snakecharm.lang.psi.stubs.SmkUseNameIndex + +class SmkUsesType( + containingUseSection: SmkUse?, + smkFile: SmkFile +) : AbstractSmkRuleOrCheckpointType( + containingUseSection, SnakemakeAPI.SMK_VARS_RULES, SmkUseNameIndex.KEY, SmkUse::class.java +) { + override val currentFileDeclarations: List by lazy { + smkFile.collectUses().map { it.second } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/jetbrains/snakecharm/stringLanguage/lang/SmkDSInjector.kt b/src/main/kotlin/com/jetbrains/snakecharm/stringLanguage/lang/SmkDSInjector.kt new file mode 100644 index 000000000..2d17658df --- /dev/null +++ b/src/main/kotlin/com/jetbrains/snakecharm/stringLanguage/lang/SmkDSInjector.kt @@ -0,0 +1,7 @@ +package com.jetbrains.snakecharm.stringLanguage.lang + +import com.jetbrains.python.documentation.doctest.PyDocstringLanguageInjector + +class SmkDSInjector : PyDocstringLanguageInjector() { + +} \ 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 c7d216d26..d9e95d95b 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -91,6 +91,7 @@ + @@ -345,6 +346,17 @@ implementationClass="com.jetbrains.snakecharm.inspections.SmkSubworkflowRedeclarationInspection" /> + + + +Only last module with the same name will be used + + \ No newline at end of file diff --git a/src/test/kotlin/com/jetbrains/snakecharm/lang/parser/SnakemakeLexerTest.kt b/src/test/kotlin/com/jetbrains/snakecharm/lang/parser/SnakemakeLexerTest.kt index a7f774422..31b8d4d52 100644 --- a/src/test/kotlin/com/jetbrains/snakecharm/lang/parser/SnakemakeLexerTest.kt +++ b/src/test/kotlin/com/jetbrains/snakecharm/lang/parser/SnakemakeLexerTest.kt @@ -285,6 +285,49 @@ class SnakemakeLexerTest : PyLexerTestCase() { "Py:PASS_KEYWORD", "Py:STATEMENT_BREAK") } + fun testModuleSection1() { + doTest( + """ + |module foo: + | input: + """.trimMargin().trimStart(), + "Py:IDENTIFIER", "Py:SPACE", "Py:IDENTIFIER", "Py:COLON", + "Py:STATEMENT_BREAK", "Py:LINE_BREAK", "Py:INDENT", "Py:IDENTIFIER", + "Py:COLON", "Py:STATEMENT_BREAK") + } + + fun testUseSection1() { + doTest( + """ + |use rule a as b with: + | input: + """.trimMargin().trimStart(), + "Py:IDENTIFIER", "Py:SPACE", "Py:IDENTIFIER", "Py:SPACE", "Py:IDENTIFIER", + "Py:SPACE", "Py:IDENTIFIER", "Py:SPACE", "Py:IDENTIFIER", "Py:SPACE", "Py:IDENTIFIER", + "Py:COLON", "Py:STATEMENT_BREAK", "Py:LINE_BREAK", "Py:INDENT", "Py:IDENTIFIER", + "Py:COLON", "Py:STATEMENT_BREAK") + } + + fun testUseSection2() { + doTest( + """ + |use rule * from foo as foo_* + """.trimMargin().trimStart(), + "Py:IDENTIFIER", "Py:SPACE", "Py:IDENTIFIER", "Py:SPACE", "Py:MULT", + "Py:SPACE", "Py:FROM_KEYWORD", "Py:SPACE", "Py:IDENTIFIER", "Py:SPACE", "Py:IDENTIFIER", + "Py:SPACE", "Py:IDENTIFIER", "Py:MULT", "Py:STATEMENT_BREAK") + } + + fun testUseSection3() { + doTest( + """ + |use rule rule from foo as other_rule + """.trimMargin().trimStart(), + "Py:IDENTIFIER", "Py:SPACE", "Py:IDENTIFIER", "Py:SPACE", "Py:IDENTIFIER", + "Py:SPACE", "Py:FROM_KEYWORD", "Py:SPACE", "Py:IDENTIFIER", "Py:SPACE", "Py:IDENTIFIER", + "Py:SPACE", "Py:IDENTIFIER", "Py:STATEMENT_BREAK") + } + private fun doTest(text: String, vararg expectedTokens: String) { doLexerTest(text, SnakemakeLexer(), *expectedTokens) } diff --git a/src/test/kotlin/com/jetbrains/snakecharm/lang/parser/SnakemakeParsingTest.kt b/src/test/kotlin/com/jetbrains/snakecharm/lang/parser/SnakemakeParsingTest.kt index e8d8b4de8..fd6cc473d 100644 --- a/src/test/kotlin/com/jetbrains/snakecharm/lang/parser/SnakemakeParsingTest.kt +++ b/src/test/kotlin/com/jetbrains/snakecharm/lang/parser/SnakemakeParsingTest.kt @@ -385,6 +385,26 @@ class SnakemakeParsingTest : ParsingTestCase( doTest() } + fun testModule() { + doTest() + } + + fun testModuleIncomplete() { + doTest() + } + + fun testUse() { + doTest() + } + + fun testUseIncomplete() { + doTest() + } + + fun testUseInvalid() { + doTest() + } + private fun doTest() { // Actually snakemake requires python 3.x and no need to have it working with python 2.x //doTest(LanguageLevel.fromPythonVersion("2")) diff --git a/src/test/resources/features/completion/keywords_completion.feature b/src/test/resources/features/completion/keywords_completion.feature index 7533cc0de..dbe68cd00 100644 --- a/src/test/resources/features/completion/keywords_completion.feature +++ b/src/test/resources/features/completion/keywords_completion.feature @@ -48,9 +48,10 @@ Feature: Completion for snakemake keyword-like things "foo" """ Examples: - | item | - | rule | - | checkpoint | + | item | + | rule | + | checkpoint | + | module | Scenario Outline: Replace at toplevel Given a snakemake project @@ -240,18 +241,18 @@ Feature: Completion for snakemake keyword-like things | rule | han | handover | | checkpoint | han | handover | - Scenario Outline: Complete at rule/checkpoint level + Scenario Outline: Complete at rule/checkpoint/module level Given a snakemake project Given I open a file "foo.smk" with text """ NAME: - + #here """ - When I put the caret after + When I put the caret at #here Then I invoke autocompletion popup, select "" lookup item and see a text: """ NAME: - : + : #here """ Examples: | rule_like | str | result | @@ -269,6 +270,66 @@ Feature: Completion for snakemake keyword-like things | checkpoint | co | conda | | rule | sh | shell | | checkpoint | sh | shell | + | module | s | snakefile | + | module | c | config | + | module | m | meta_wrapper | + | module | s | skip_validation | + | module | r | replace_prefix | + + Scenario Outline: Use keyword completion + Given a snakemake project + Given I open a file "foo.smk" with text + """ + # + """ + When I put the caret after + Then I invoke autocompletion popup, select "" lookup item and see a text: + """ + + """ + Examples: + | given | choice | result | + | u | use | use rule # | + | use ru | rule | use rule # | + | use rule f | from | use rule from # | + | use rule w | with | use rule with:# | + + + Scenario Outline: Complete at use level + Given a snakemake project + Given I open a file "foo.smk" with text + """ + use rule NAME1 from MODULE as NAME2 with: + #here + """ + When I put the caret at #here + Then I invoke autocompletion popup, select "" lookup item and see a text: + """ + use rule NAME1 from MODULE as NAME2 with: + : #here + """ + Examples: + | str | result | + | in | input | + | ou | output | + | l | log | + | th | threads | + + Scenario: No rule execution sections in use section + Given a snakemake project + Given I open a file "foo.py" with text + """ + use rule NAME1 from MODULE as NAME2 with: + #here + """ + When I put the caret at #here + And I invoke autocompletion popup + Then completion list shouldn't contain: + | run | + | shell | + | notebook | + | script | + | cwl | Scenario Outline: Complete and replace at rule/checkpoint level Given a snakemake project @@ -393,18 +454,21 @@ Feature: Completion for snakemake keyword-like things | localrules | | onstart | Examples: - | rule_like | text | ptn | - | rule | input: foo | foo | - | rule | input: foo, #here | #here | - | rule | input: foo.boo | boo | - | rule | run: foo | foo | - | checkpoint | input: foo | foo | - | checkpoint | input: foo, #here | #here | - | checkpoint | input: foo.boo | boo | - | checkpoint | run: foo | foo | - | subworkflow | workdir: foo | foo | - | subworkflow | workdir: foo, #here | #here | - | subworkflow | workdir: foo.boo | boo | + | rule_like | text | ptn | + | rule | input: foo | foo | + | rule | input: foo, #here | #here | + | rule | input: foo.boo | boo | + | rule | run: foo | foo | + | checkpoint | input: foo | foo | + | checkpoint | input: foo, #here | #here | + | checkpoint | input: foo.boo | boo | + | checkpoint | run: foo | foo | + | subworkflow | workdir: foo | foo | + | subworkflow | workdir: foo, #here | #here | + | subworkflow | workdir: foo.boo | boo | + | module | nakefile: foo | foo | + | module | nakefile: foo, #here | #here | + | module | nakefile: foo.boo | boo | Scenario Outline: Do not show toplevel keywords in workflow sections Given a snakemake project diff --git a/src/test/resources/features/highlighting/inspections/module_redeclaration.feature b/src/test/resources/features/highlighting/inspections/module_redeclaration.feature new file mode 100644 index 000000000..5b5265f21 --- /dev/null +++ b/src/test/resources/features/highlighting/inspections/module_redeclaration.feature @@ -0,0 +1,21 @@ +Feature: SmkModuleRedeclarationInspection inspection + Scenario: A single SmkModuleRedeclarationInspection + Given a snakemake project + Given I open a file "foo.smk" with text + """ + module NAME: + snakefile: "snake.smk" + + module ANOTHER_NAME: + snakefile: "foo.smk" + + module NAME: #overrides + snakefile: "boo.smk" + """ + And SmkModuleRedeclarationInspection inspection is enabled + Then I expect inspection weak warning with message "Only last module with the same name will be used" on + """ + module NAME: + snakefile: "snake.smk" + """ + When I check highlighting weak warnings \ No newline at end of file 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 645eb813a..177590e4b 100644 --- a/src/test/resources/features/highlighting/inspections/multiple_args_inspection.feature +++ b/src/test/resources/features/highlighting/inspections/multiple_args_inspection.feature @@ -1,26 +1,30 @@ Feature: Inspection for multiple arguments in various sections - Scenario Outline: Multiple arguments in subworkflow section + Scenario Outline: Multiple arguments in module/subworkflow section Given a snakemake project Given I open a file "foo.smk" with text """ - subworkflow NAME: + NAME:
: "a", "b", "c" """ And SmkSectionMultipleArgsInspection inspection is enabled Then I expect inspection error on <"b"> with message """ - Only one argument is allowed for 'subworkflow' section. + Only one argument is allowed for '' section. """ And I expect inspection error on <"c"> with message """ - Only one argument is allowed for 'subworkflow' section. + Only one argument is allowed for '' section. """ When I check highlighting errors Examples: - | section | - | workdir | - | snakefile | - | configfile | + | keyword | section | + | subworkflow | workdir | + | subworkflow | snakefile | + | subworkflow | configfile | + | module | snakefile | + | module | config | + | module | skip_validation | + | module | meta_wrapper | Scenario Outline: Multiple arguments in execution sections Given a snakemake project diff --git a/src/test/resources/features/highlighting/inspections/rule_redeclaration.feature b/src/test/resources/features/highlighting/inspections/rule_redeclaration.feature index b3e2de1ec..f2cc9e09c 100644 --- a/src/test/resources/features/highlighting/inspections/rule_redeclaration.feature +++ b/src/test/resources/features/highlighting/inspections/rule_redeclaration.feature @@ -22,6 +22,42 @@ Feature: SmkRuleRedeclarationInspection inspection """ When I check highlighting errors + Scenario: A single SmkRuleRedeclarationInspection with rule defined in 'use' section + Given a snakemake project + Given I open a file "foo.smk" with text + """ + use rule RULE from MODULE as NAME with: + input: "data.csv" + + rule ANOTHER_NAME: + output: touch("file.txt") + + rule NAME: #overrides + output: touch("output.txt") + """ + And SmkRuleRedeclarationInspection inspection is enabled + Then I expect inspection error on in with message + """ + This rule name is already used by another rule declaration. + """ + When I check highlighting errors + + Scenario: A single SmkRuleRedeclarationInspection with two 'use' sections, which defines same rules + Given a snakemake project + Given I open a file "foo.smk" with text + """ + use rule RULE from MODULE as NAME with: + input: "data.csv" + + use rule ANOTHER_RULE from MODULE as NAME with: #overrides + input: "data_v2.csv" + """ + And SmkRuleRedeclarationInspection inspection is enabled + Then I expect inspection error on in with message + """ + This rule name is already used by another rule declaration. + """ + When I check highlighting errors Scenario: Multiple SmkRuleRedeclarationInspections Given a snakemake project diff --git a/src/test/resources/features/highlighting/inspections/section_redeclaration.feature b/src/test/resources/features/highlighting/inspections/section_redeclaration.feature index 87a75050a..68ad8b238 100644 --- a/src/test/resources/features/highlighting/inspections/section_redeclaration.feature +++ b/src/test/resources/features/highlighting/inspections/section_redeclaration.feature @@ -68,11 +68,11 @@ Feature: Rule SmkSectionRedeclarationInspection inspection | rule | | checkpoint | - Scenario: Subworkflow SmkSectionRedeclarationInspection + Scenario Outline: Subworkflow and Module SmkSectionRedeclarationInspection Given a snakemake project Given I open a file "foo.smk" with text """ - subworkflow NAME: +
NAME: snakefile: "foo.smk" snakefile: "boo.smk" """ @@ -82,7 +82,26 @@ Feature: Rule SmkSectionRedeclarationInspection inspection Declaration of section 'snakefile' above overrides this declaration. """ When I check highlighting weak warnings + Examples: + | header | + | subworkflow | + | module | + Scenario: SmkSectionRedeclarationInspection for 'use' section + Given a snakemake project + Given I open a file "foo.smk" with text + """ + use rule a from module as b with: + input: "some_file" + input: "other_file" + """ + And SmkSectionRedeclarationInspection inspection is enabled + Then I expect inspection weak warning on with message + """ + Declaration of section 'input' above overrides this declaration. + """ + When I check highlighting weak warnings + Scenario Outline: SmkSectionRedeclarationInspection element removal fix test Given a snakemake project Given I open a file "foo.smk" with text @@ -134,4 +153,4 @@ Feature: Rule SmkSectionRedeclarationInspection inspection Examples: | rule_like | | rule | - | checkpoint | + | checkpoint | \ No newline at end of file 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 3ef0c54ff..0ae254c18 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 @@ -19,7 +19,7 @@ Feature: Inspection for unexpected keyword arguments in section | snakefile | | configfile | - Scenario Outline: Unexpected keyword arguments in rule\checkpoint + Scenario Outline: Unexpected keyword arguments in rule\checkpoint\module Given a snakemake project Given I open a file "foo.smk" with text """ @@ -33,28 +33,33 @@ Feature: Inspection for unexpected keyword arguments in section """ When I check highlighting errors Examples: - | rule_like | section | - | rule | benchmark | - | rule | cache | - | rule | conda | - | rule | container | - | rule | containerized | - | rule | cwl | - | rule | group | - | rule | envmodules | - | rule | singularity | - | rule | threads | - | rule | name | - | rule | handover | - | checkpoint | message | - | checkpoint | notebook | - | checkpoint | priority | - | checkpoint | script | - | checkpoint | shadow | - | checkpoint | shell | - | checkpoint | version | - | checkpoint | wrapper | - | checkpoint | handover | + | rule_like | section | + | rule | benchmark | + | rule | cache | + | rule | conda | + | rule | container | + | rule | containerized | + | rule | cwl | + | rule | group | + | rule | envmodules | + | rule | singularity | + | rule | threads | + | rule | name | + | rule | handover | + | checkpoint | message | + | checkpoint | notebook | + | checkpoint | priority | + | checkpoint | script | + | checkpoint | shadow | + | checkpoint | shell | + | checkpoint | version | + | checkpoint | wrapper | + | checkpoint | handover | + | module | snakefile | + | module | config | + | module | skip_validation | + | module | meta_wrapper | + Scenario Outline: No warn on expected keyword arguments in rule\checkpoint Given a snakemake project diff --git a/src/test/resources/features/highlighting/inspections/unrecognized_section_inspection.feature b/src/test/resources/features/highlighting/inspections/unrecognized_section_inspection.feature index 3b57094f4..4bf6dd82f 100644 --- a/src/test/resources/features/highlighting/inspections/unrecognized_section_inspection.feature +++ b/src/test/resources/features/highlighting/inspections/unrecognized_section_inspection.feature @@ -59,3 +59,42 @@ Feature: Inspection if section isn't recognized by SnakeCharm | rule | | subworkflow | | checkpoint | + + Scenario: When 'use' section contains execution subsections + Given a snakemake project + Given I open a file "foo.smk" with text + """ + use rule RULE as NEW_RULE with: + run: "" + shell: "" + notebook: "" + script: "" + cwl: "" + wrapper: "" + """ + And SmkUnrecognizedSectionInspection inspection is enabled + Then I expect inspection weak warning on with message + """ + Section 'run' isn't recognized by SnakeCharm plugin or there could be a typo in the section name. + """ + Then I expect inspection weak warning on with message + """ + Section 'shell' isn't recognized by SnakeCharm plugin or there could be a typo in the section name. + """ + Then I expect inspection weak warning on with message + """ + Section 'notebook' isn't recognized by SnakeCharm plugin or there could be a typo in the section name. + """ + Then I expect inspection weak warning on