Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

More inspections #22

Merged
merged 18 commits into from
Dec 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions src/main/grammars/LalrpopParser.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ grammar_params ::= LPAREN <<comma grammar_param>> RPAREN

grammar_param ::= id COLON type_ref

grammar_item ::= use_stmt | match_token | extern_token | nonterminal | COMMENT
private grammar_item ::= use_stmt | match_token | extern_token | nonterminal | COMMENT

use_stmt ::= USE IMPORT_CODE SEMICOLON {
implements = "com.intellij.psi.PsiLanguageInjectionHost"
Expand All @@ -61,9 +61,14 @@ nonterminal ::= annotation* visibility? nonterminal_name (COLON type_ref)? EQUAL
mixin = "com.mdrobnak.lalrpop.psi.ext.LpNonterminalMixin"
}

annotation_arg ::= LPAREN id EQUALS STR_LITERAL RPAREN
annotation_arg ::= LPAREN annotation_arg_name EQUALS annotation_arg_value RPAREN

annotation ::= POUND LBRACKET ID annotation_arg? RBRACKET
annotation_arg_name ::= id
annotation_arg_value ::= STR_LITERAL

annotation ::= POUND LBRACKET annotation_name annotation_arg? RBRACKET

annotation_name ::= ID

nonterminal_name ::= ID nonterminal_params? {
implements = "com.mdrobnak.lalrpop.psi.LpNamedElement"
Expand All @@ -87,7 +92,7 @@ alternatives ::= alternative SEMICOLON | LBRACE <<comma alternative>> RBRACE SEM
mixin = "com.mdrobnak.lalrpop.psi.ext.LpAlternativesMixin"
}

alternative ::= symbol+ (IF cond)? action?
alternative ::= annotation* symbol+ (IF cond)? action?
| (IF cond)? action {
implements = "com.mdrobnak.lalrpop.psi.LpResolveType"
mixin = "com.mdrobnak.lalrpop.psi.ext.LpAlternativeMixin"
Expand All @@ -109,13 +114,14 @@ expr_symbol ::= symbol* {
mixin = "com.mdrobnak.lalrpop.psi.ext.LpExprSymbolMixin"
}

symbol ::= LESSTHAN MUT? ID COLON symbol0 GREATERTHAN
| LESSTHAN symbol0 GREATERTHAN
symbol ::= LESSTHAN symbol_name? symbol0 GREATERTHAN
| symbol0 {
mixin = "com.mdrobnak.lalrpop.psi.ext.LpSymbolMixin"
implements = "com.mdrobnak.lalrpop.psi.LpResolveType"
}

symbol_name ::= MUT? ID COLON

symbol0 ::= symbol1 repeat_op* {
implements = "com.mdrobnak.lalrpop.psi.LpResolveType"
mixin = "com.mdrobnak.lalrpop.psi.ext.LpSymbol0Mixin"
Expand Down
6 changes: 2 additions & 4 deletions src/main/kotlin/com/mdrobnak/lalrpop/LpBraceMatcher.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import com.intellij.lang.BracePair
import com.intellij.lang.PairedBraceMatcher
import com.intellij.psi.PsiFile
import com.intellij.psi.tree.IElementType
import com.mdrobnak.lalrpop.psi.LpElementTypes
import com.mdrobnak.lalrpop.psi.LpEnumToken
import com.mdrobnak.lalrpop.psi.LpGrammarItem
import com.mdrobnak.lalrpop.psi.*
import org.rust.lang.core.psi.ext.startOffset

object LpBraceMatcher : PairedBraceMatcher {
Expand All @@ -29,7 +27,7 @@ object LpBraceMatcher : PairedBraceMatcher {
var element = file.findElementAt(openingBraceOffset)
while (element != null) {
when (element) {
is LpGrammarItem, is LpEnumToken -> return element.startOffset
is LpUseStmt, is LpNonterminal, is LpMatchToken, is LpExternToken, is LpEnumToken -> return element.startOffset
}

element = element.parent
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.mdrobnak.lalrpop

import com.intellij.lang.refactoring.RefactoringSupportProvider
import com.intellij.psi.PsiElement
import com.mdrobnak.lalrpop.psi.LpNonterminalName
import com.mdrobnak.lalrpop.psi.LpNonterminalParam

class LpRefactoringSupportProvider : RefactoringSupportProvider() {
override fun isSafeDeleteAvailable(element: PsiElement): Boolean =
element is LpNonterminalName || element is LpNonterminalParam
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class LpHighlightingAnnotator : Annotator {
private fun colorForIdentifier(parent: PsiElement?): LpColor? =
when (parent?.elementType) {
LpElementTypes.GRAMMAR_PARAM -> LpColor.PARAMETER
LpElementTypes.SYMBOL -> LpColor.PARAMETER
LpElementTypes.SYMBOL_NAME -> LpColor.PARAMETER
LpElementTypes.NONTERMINAL_REF ->
if ((parent as LpNonterminalRef).reference?.resolve()?.elementType == LpElementTypes.NONTERMINAL_PARAM)
LpColor.NONTERMINAL_GENERIC_PARAMETER
Expand All @@ -36,4 +36,4 @@ class LpHighlightingAnnotator : Annotator {
LpElementTypes.PATH_ID -> LpColor.PATH
else -> null
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import com.intellij.lang.injection.MultiHostRegistrar
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.parentOfType
import com.mdrobnak.lalrpop.psi.*
import com.mdrobnak.lalrpop.psi.LpAlternative
import com.mdrobnak.lalrpop.psi.LpGrammarDecl
import com.mdrobnak.lalrpop.psi.LpNonterminal
import com.mdrobnak.lalrpop.psi.LpSelectedType
import com.mdrobnak.lalrpop.psi.ext.importCode
import com.mdrobnak.lalrpop.psi.ext.name
import com.mdrobnak.lalrpop.psi.ext.selected
import com.mdrobnak.lalrpop.psi.impl.LpActionImpl
Expand All @@ -22,12 +26,11 @@ class LpRustActionCodeInjector : MultiHostInjector {
}

val codeNode = context.code ?: return
val imports = PsiTreeUtil.findChildrenOfType(context.containingFile, LpUseStmt::class.java)
.joinToString("\n") { it.text }
val imports = context.containingFile.importCode
val nonterminal = context.parentOfType<LpNonterminal>()!!
val alternative = context.parentOfType<LpAlternative>()!!

val inputs = alternative.selected.mapNotNull { (it as LpSymbolImpl).getSelectedType() }
val inputs = alternative.selected.map { (it as LpSymbolImpl).getSelectedType() }
val returnType = nonterminal.resolveType(listOf())

val grammarDecl = PsiTreeUtil.findChildOfType(context.containingFile, LpGrammarDecl::class.java)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.mdrobnak.lalrpop.inspections

import com.intellij.codeInspection.LocalInspectionTool
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.PsiElementVisitor
import com.mdrobnak.lalrpop.psi.LpNonterminal
import com.mdrobnak.lalrpop.psi.LpVisitor

object CannotInferNonterminalTypeInspection : LocalInspectionTool() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : LpVisitor() {
override fun visitNonterminal(nonterminal: LpNonterminal) {
if (nonterminal.typeRef == null &&
nonterminal.alternatives.alternativeList.isNotEmpty() &&
nonterminal.alternatives.alternativeList.all { it.action != null }
) {
holder.registerProblem(
nonterminal,
"Cannot infer type of nonterminal",
) // TODO Quickfix: get from the rust plugin if available.
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.mdrobnak.lalrpop.inspections

import com.intellij.codeInspection.LocalInspectionTool
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.util.parentOfType
import com.mdrobnak.lalrpop.psi.LpNonterminal
import com.mdrobnak.lalrpop.psi.LpNonterminalRef
import com.mdrobnak.lalrpop.psi.LpVisitor
import com.mdrobnak.lalrpop.psi.ext.addParam
import com.mdrobnak.lalrpop.psi.ext.arguments
import com.mdrobnak.lalrpop.psi.ext.createNonterminal

object CannotResolveNonterminalReferenceInspection : LocalInspectionTool() {
override fun buildVisitor(
holder: ProblemsHolder,
isOnTheFly: Boolean
): PsiElementVisitor {
return object : LpVisitor() {
override fun visitNonterminalRef(nonterminalRef: LpNonterminalRef) {
val ref = nonterminalRef.reference
if (ref != null && ref.resolve() == null) {
if (nonterminalRef.arguments == null)
holder.registerProblem(
nonterminalRef,
"Cannot resolve ${ref.canonicalText}",
AddToMacroParamsQuickFix,
CreateNonterminalQuickFix
)
else
holder.registerProblem(
nonterminalRef,
"Cannot resolve ${ref.canonicalText}",
CreateNonterminalQuickFix
)
}
}
}
}
}

object AddToMacroParamsQuickFix : LocalQuickFix {
override fun getFamilyName(): String = "Add to macro parameters"

override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
val nonterminal = descriptor.psiElement.parentOfType<LpNonterminal>() ?: return
nonterminal.nonterminalName.addParam(descriptor.psiElement.text)
}
}

object CreateNonterminalQuickFix : LocalQuickFix {
override fun getFamilyName(): String = "Create nonterminal"

override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
(descriptor.psiElement as LpNonterminalRef).createNonterminal()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.mdrobnak.lalrpop.inspections

import com.intellij.codeInspection.LocalInspectionTool
import com.intellij.codeInspection.LocalInspectionToolSession
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.UserDataHolderBase
Expand Down Expand Up @@ -49,7 +48,6 @@ object MissingTerminalsInspection : LocalInspectionTool() {
problemsHolder.registerProblem(
unresolvedElement,
"Missing declaration of terminal",
ProblemHighlightType.ERROR
)
}
}
Expand All @@ -60,28 +58,38 @@ object MissingTerminalsInspection : LocalInspectionTool() {
isOnTheFly: Boolean,
session: LocalInspectionToolSession
): PsiElementVisitor {
return object : PsiElementVisitor() {
override fun visitElement(element: PsiElement) {
super.visitElement(element)
if (element is LpMatchToken || element is LpEnumToken) session.dataNotNull(checkKey).set(true)
return object : LpVisitor() {
AzureMarker marked this conversation as resolved.
Show resolved Hide resolved
override fun visitMatchToken(o: LpMatchToken) {
session.dataNotNull(checkKey).set(true)
}

if (element is LpMatchItem) {
val terminal = element.matchSymbol?.quotedLiteral
if (terminal != null) {
session.dataNotNull(terminalDefsKey).add(terminal)
} else {
// is _ => there cannot be any unresolved terminals, so don't check.
session.dataNotNull(matchHasWildcardKey).set(true)
}
} else if (element is LpTerminal && element.parent is LpConversion) {
override fun visitEnumToken(o: LpEnumToken) {
session.dataNotNull(checkKey).set(true)
}

override fun visitMatchItem(element: LpMatchItem) {
val terminal = element.matchSymbol?.quotedLiteral
if (terminal != null) {
session.dataNotNull(terminalDefsKey).add(terminal)
} else {
// is _ => there cannot be any unresolved terminals, so don't check.
session.dataNotNull(matchHasWildcardKey).set(true)
}
}

override fun visitTerminal(element: LpTerminal) {
if (element.parent is LpConversion) {
val terminal = element.quotedTerminal
if (terminal != null) {
session.dataNotNull(terminalDefsKey).add(terminal)
}
} else if (element is LpQuotedTerminal && element.parent !is LpTerminal) {
session.dataNotNull(unresolvedKey).add(element)
}
}

override fun visitQuotedTerminal(element: LpQuotedTerminal) {
if (element.parent !is LpTerminal)
session.dataNotNull(unresolvedKey).add(element)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.mdrobnak.lalrpop.inspections

import com.intellij.codeInspection.LocalInspectionTool
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.util.PsiTreeUtil
import com.mdrobnak.lalrpop.psi.*
import com.mdrobnak.lalrpop.psi.ext.isExplicitlySelected
import com.mdrobnak.lalrpop.psi.ext.isNamed
import com.mdrobnak.lalrpop.psi.ext.removeName

/**
* Inspection related to where named symbols can appear.
* Suggests removing the name if there are issues.
*/
object NamedSymbolsInspection : LocalInspectionTool() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object : LpVisitor() {
override fun visitSymbol(symbol: LpSymbol) {
if (!symbol.isNamed) return
val parent = PsiTreeUtil.findFirstParent(symbol) {
it is LpAlternative || it is LpExprSymbol || it is LpNonterminalArguments || it is LpTypeOfSymbol
}
if (parent is LpAlternative) {
// if there is at least one explicitly selected, unnamed symbol
if (parent.symbolList.any { it.isExplicitlySelected && !it.isNamed }) {
// then it is an error
holder.registerProblem(
symbol,
"Usage of named symbol in an alternative where an unnamed symbol was also used",
RemoveNameQuickFix
)
}
} else {
holder.registerProblem(
symbol,
"Usage of named symbol in a context that doesn't allow it",
RemoveNameQuickFix
)
}
}
}
}
}

object RemoveNameQuickFix : LocalQuickFix {
override fun getFamilyName(): String {
return "Remove name"
}

override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
(descriptor.psiElement as LpSymbol).removeName()
}
}
Loading