Skip to content

Commit

Permalink
Merge pull request #82 from anboralabs/psi-impl
Browse files Browse the repository at this point in the history
Annotators
  • Loading branch information
dalgarins authored Sep 25, 2021
2 parents ee2ac69 + 5c9eee0 commit 1f4b3e9
Show file tree
Hide file tree
Showing 20 changed files with 295 additions and 12 deletions.
6 changes: 3 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
plugins {
id 'java'
id 'org.jetbrains.intellij' version '1.1.2'
id 'org.jetbrains.intellij' version '1.1.6'
id 'org.jetbrains.kotlin.jvm' version '1.5.21'
id "org.jetbrains.grammarkit" version "2021.1.3"
}
Expand All @@ -10,7 +10,7 @@ apply plugin: 'org.jetbrains.grammarkit'
import org.jetbrains.grammarkit.tasks.*

group 'co.anbora.labs'
version '2.6.5'
version '2.6.6'

repositories {
mavenCentral()
Expand Down Expand Up @@ -58,7 +58,7 @@ sourceSets {

// See https://github.com/JetBrains/gradle-intellij-plugin/
intellij {
version = 'LATEST-EAP-SNAPSHOT'
version = '2021.2'
}

java {
Expand Down
16 changes: 11 additions & 5 deletions src/main/grammar/FirebaseRules.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
DOT = '.'
Q_MARK = '?'
DOT_COMMA = ';'
WILD_CARD = '=**'
MODULO = '%'
char = 'regexp:[\n\r\u2028\u2029]'
LINE_COMMENT='LINE_COMMENT'
Expand Down Expand Up @@ -106,10 +107,13 @@ private FullPathStatementItem ::= !('{') SLASH PathStatement {
pin=1
recoverWhile=FullPathStatementItem_recover
}
private FullPathStatementItem_recover ::= !(SLASH | '{' | IDENTIFIER | PATH_VARIABLE)
private FullPathStatementItem_recover ::= !(SLASH | '{' | IDENTIFIER | VariableInPath)
//Match Definition End

PathStatement ::= (DOT? IDENTIFIER|PATH_VARIABLE)
PathStatement ::= (DOT? IDENTIFIER|VariableInPath)
VariableInPath ::= PATH_VARIABLE {
mixin="co.anbora.labs.firebase.lang.core.psi.mixings.IdentifierMixing"
}

//Allow Statement Begin
AllowStatement ::= EmptyAllowStm | ConditionalAllowStm
Expand Down Expand Up @@ -146,7 +150,7 @@ BuiltInFunctionStatement ::= (GET_KEYWORD|EXITS_KEYWORD)
BuiltInTypes ::= LIST_KEYWORD

//Function Definition Begin
FunctionDef ::= FUNCTION_KEYWORD IDENTIFIER FunctionParameterList FunctionBlock
FunctionDef ::= FUNCTION_KEYWORD IdentifierExpr FunctionParameterList FunctionBlock
{
pin=1
}
Expand Down Expand Up @@ -205,7 +209,9 @@ private RangeExpr ::= Expression ':' Expression
TernaryExpr ::= Expression '?' Expression ':' Expression


IdentifierExpr ::= IDENTIFIER
IdentifierExpr ::= IDENTIFIER {
mixin="co.anbora.labs.firebase.lang.core.psi.mixings.IdentifierMixing"
}
DotExpr ::= Expression DOT Expression
CallExpr ::= Expression CallArguments
CallArguments ::= '(' ParameterStatement? ')'
Expand Down Expand Up @@ -259,7 +265,7 @@ private ArrayExpr ::= LB (ParameterStatement) RB

LiteralStatement ::= (number|string)

VariableStatement ::= LET_KEYWORD IDENTIFIER EQ Expression (DOT_COMMA?)
VariableStatement ::= LET_KEYWORD IdentifierExpr EQ Expression (DOT_COMMA?)

NullStatement ::= NULL_KEYWORD

Expand Down
12 changes: 10 additions & 2 deletions src/main/html/change-notes.html
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
Versions:
<ul>
<li>All Intellij products support: 2.6.5</li>
<li>Android Studio support: 2.6.5</li>
<li>All Intellij products support: 2.6.6</li>
<li>Android Studio support: 2.6.6</li>
</ul>
<br>
Plugin updates:
<ul>
<li><b>2.6.6</b> <em>(2021-09-24)</em> - Added local inspections</li>
<ul>
<li>Added local inspection for duplicated functions. </li>
<li>Added local inspection for weak security rules. </li>
<li>Added local inspection for duplicated path variables. </li>
<li>Added color support for service name. </li>
<li>Added quote handler. </li>
</ul>
<li><b>2.6.5</b> <em>(2021-09-13)</em> - Fixed issue</li>
<ul>
<li>Fixed issue with external library. </li>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Use of this source code is governed by the MIT license that can be
* found in the LICENSE file.
*/

package co.anbora.labs.firebase.ide.annotator

import com.intellij.lang.annotation.AnnotationHolder
import com.intellij.lang.annotation.Annotator
import com.intellij.openapi.Disposable
import com.intellij.openapi.util.Disposer
import com.intellij.psi.PsiElement
import com.intellij.util.containers.ContainerUtil
import org.jetbrains.annotations.TestOnly

abstract class FirebaseAnnotator : Annotator {
final override fun annotate(element: PsiElement, holder: AnnotationHolder) {
if (javaClass in enabledAnnotators) {
annotateInternal(element, holder)
}
}

protected abstract fun annotateInternal(element: PsiElement, holder: AnnotationHolder)

companion object {
private val enabledAnnotators: MutableSet<Class<out FirebaseAnnotator>> = ContainerUtil.newConcurrentSet()

@TestOnly
fun enableAnnotator(annotatorClass: Class<out FirebaseAnnotator>, parentDisposable: Disposable) {
enabledAnnotators += annotatorClass
Disposer.register(
parentDisposable
) { enabledAnnotators -= annotatorClass }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package co.anbora.labs.firebase.ide.annotator

class HighlightingAnnotator {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package co.anbora.labs.firebase.ide.annotator

import co.anbora.labs.firebase.ide.color.FirebaseColors
import co.anbora.labs.firebase.lang.core.psi.FirebaseRulesIdentifierExpr
import co.anbora.labs.firebase.lang.core.psi.FirebaseRulesMatchDef
import co.anbora.labs.firebase.lang.core.psi.FirebaseRulesPathStatement
import co.anbora.labs.firebase.lang.core.psi.FirebaseRulesVisitor
import com.intellij.lang.annotation.AnnotationHolder
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiTreeUtil

class PathVariableHighlightAnnotator : FirebaseAnnotator() {
override fun annotateInternal(element: PsiElement, holder: AnnotationHolder) {
val visitor = object : FirebaseRulesVisitor() {
override fun visitMatchDef(o: FirebaseRulesMatchDef) {
checkPathVariablesInMatchDef(holder, element)
}
}
element.accept(visitor)
}

private fun checkPathVariablesInMatchDef(holder: AnnotationHolder, element: PsiElement) {
val color = FirebaseColors.NUMBERS
val pathVariables = PsiTreeUtil.collectElementsOfType(element, FirebaseRulesPathStatement::class.java)
.map { it.text }
.map { it.replace("{", "")
.replace("}", "")
.replace("=", "")
.replace("*", "")
}.toSet()
val variables = PsiTreeUtil.collectElementsOfType(element, FirebaseRulesIdentifierExpr::class.java)
variables.forEach {
if (pathVariables.contains(it.text)) {

}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ enum class FirebaseColors(humanName: String, default: TextAttributesKey) {
PATH_AND_STRING("Path and Strings", DefaultLanguageHighlighterColors.STRING),
COMMENTS("Comments", DefaultLanguageHighlighterColors.LINE_COMMENT),
CALL_FUNCTION("Functions", DefaultLanguageHighlighterColors.FUNCTION_CALL),
NUMBERS("Numbers", DefaultLanguageHighlighterColors.NUMBER);
NUMBERS("Numbers", DefaultLanguageHighlighterColors.NUMBER),
SERVICE_NAME("Service name", DefaultLanguageHighlighterColors.NUMBER);

val textAttributesKey = TextAttributesKey.createTextAttributesKey("co.anbora.labs.firebase.$name", default)
val attributesDescriptor = AttributesDescriptor(humanName, textAttributesKey)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package co.anbora.labs.firebase.ide.editor

import co.anbora.labs.firebase.lang.core.psi.FirebaseRulesTypes.STRING
import com.intellij.codeInsight.editorActions.SimpleTokenSetQuoteHandler

class FirebaseQuoteHandler: SimpleTokenSetQuoteHandler(STRING)
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ class FirebaseSyntaxHighlighter: SyntaxHighlighterBase() {
GET_KEYWORD, READ_KEYWORD, WRITE_KEYWORD,
LIST_KEYWORD, CREATE_KEYWORD, UPDATE_KEYWORD,
DELETE_KEYWORD, EXITS_KEYWORD -> FirebaseColors.PERMISSIONS
PATH_VARIABLE, PATH_BUILT_IN, STRING -> FirebaseColors.PATH_AND_STRING
PATH_VARIABLE, PATH_BUILT_IN, STRING, VERSIONS -> FirebaseColors.PATH_AND_STRING
LINE_COMMENT, BLOCK_COMMENT -> FirebaseColors.COMMENTS
CALL_EXPR -> FirebaseColors.CALL_FUNCTION
NUMBER -> FirebaseColors.NUMBERS
SERVICE_NAME -> FirebaseColors.NUMBERS
TokenType.BAD_CHARACTER -> FirebaseColors.BAD_CHAR
else -> null
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package co.anbora.labs.firebase.ide.inspections

import co.anbora.labs.firebase.lang.core.psi.*
import com.intellij.codeInspection.LocalInspectionTool
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.util.PsiTreeUtil

class DuplicateFunctionsDeclarationInspection : LocalInspectionTool() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object: PsiElementVisitor() {
override fun visitElement(element: PsiElement) {
super.visitElement(element)
when (element) {
is FirebaseFile -> checkFunctionSignature(element, holder)
is FirebaseRulesFullPathStatement -> checkPathVariable(element, holder)
}
}
}
}
}

private fun checkFunctionSignature(element: FirebaseFile, holder: ProblemsHolder) {
val functions = PsiTreeUtil.collectElementsOfType(element, FirebaseRulesFunctionDef::class.java)
val mapFunctions = HashMap<String, Unit>()
functions.forEach {
val key = it.identifierExpr?.text + "_" + it.functionParameterList?.functionParameterList?.size
mapFunctions.compute(key) { _, v ->
if (v != null) markDuplicate(it.identifierExpr ?: it, holder)
}
}
}

private fun checkPathVariable(element: FirebaseRulesFullPathStatement, holder: ProblemsHolder) {
val variables = PsiTreeUtil.collectElementsOfType(element, FirebaseRulesVariableInPath::class.java)
val mapPathVariables = HashMap<String, Unit>()
variables.forEach {
val key = it.text
mapPathVariables.compute(key) { _, v ->
if (v != null) markDuplicate(it, holder)
}
}
}

private fun markDuplicate(element: FirebaseElement, holder: ProblemsHolder) {
holder.registerProblem(element, "Duplicate definitions with name `${element.text}`", ProblemHighlightType.ERROR)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package co.anbora.labs.firebase.ide.inspections

import co.anbora.labs.firebase.lang.core.psi.FirebaseRulesConditionalBlock
import co.anbora.labs.firebase.lang.core.psi.FirebaseRulesLiteralExpr
import com.intellij.codeInspection.LocalInspectionTool
import com.intellij.codeInspection.ProblemHighlightType
import com.intellij.codeInspection.ProblemsHolder
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementVisitor

private const val FIREBASE_TRUE = "true"

class FirebaseWeakRulesInspection: LocalInspectionTool() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
return object: PsiElementVisitor() {
override fun visitElement(element: PsiElement) {
super.visitElement(element)
when (element) {
is FirebaseRulesConditionalBlock -> checkWeakRule(element, holder)
}
}
}
}
}

private fun checkWeakRule(element: FirebaseRulesConditionalBlock, holder: ProblemsHolder) {
val decl = element.expression
val literal = decl as? FirebaseRulesLiteralExpr
val booleanStm = literal?.booleanStatement
if (booleanStm?.text == FIREBASE_TRUE) {
holder.registerProblem(booleanStm, "Weak rule, this is not recommended for production environments.", ProblemHighlightType.WEAK_WARNING)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package co.anbora.labs.firebase.lang.core.psi

import com.intellij.psi.PsiNameIdentifierOwner

interface FirebaseNameIdentifierOwner : FirebaseNamedElement,
PsiNameIdentifierOwner
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package co.anbora.labs.firebase.lang.core.psi

import co.anbora.labs.firebase.lang.core.psi.FirebaseRulesTypes.IDENTIFIER
import co.anbora.labs.firebase.lang.core.psi.ext.findLastChildByType
import com.intellij.psi.NavigatablePsiElement
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiNamedElement

interface FirebaseNamedElement : FirebaseElement,
PsiNamedElement,
NavigatablePsiElement {

val nameElement: PsiElement?
get() = findLastChildByType(IDENTIFIER)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package co.anbora.labs.firebase.lang.core.psi.ext

import com.intellij.psi.PsiElement
import com.intellij.psi.tree.IElementType
import com.intellij.psi.util.PsiUtilCore

val PsiElement.elementType: IElementType
get() = PsiUtilCore.getElementType(this)

val PsiElement.childrenWithLeaves: Sequence<PsiElement>
get() = generateSequence(this.firstChild) { it.nextSibling }

fun PsiElement.childrenByType(type: IElementType): Sequence<PsiElement> =
childrenWithLeaves.filter { it.elementType == type }

fun PsiElement.findLastChildByType(type: IElementType): PsiElement? =
childrenByType(type).lastOrNull()
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package co.anbora.labs.firebase.lang.core.psi.impl

import co.anbora.labs.firebase.lang.core.psi.FirebaseNameIdentifierOwner
import com.intellij.lang.ASTNode
import com.intellij.psi.PsiElement

abstract class FirebaseNameIdentifierOwnerImpl(
node: ASTNode
) : FirebaseNamedElementImpl(node), FirebaseNameIdentifierOwner {
override fun getNameIdentifier(): PsiElement? = nameElement
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package co.anbora.labs.firebase.lang.core.psi.impl

import co.anbora.labs.firebase.lang.core.psi.FirebaseElementImpl
import co.anbora.labs.firebase.lang.core.psi.FirebaseNamedElement
import com.intellij.lang.ASTNode
import com.intellij.psi.PsiElement

abstract class FirebaseNamedElementImpl(
node: ASTNode
) : FirebaseElementImpl(node), FirebaseNamedElement {
override fun getName(): String? = nameElement?.text

override fun setName(name: String): PsiElement {
//nameElement?.replace(MovePsiFactory(project).createIdentifier(name))
return this
}

override fun getNavigationElement(): PsiElement = nameElement ?: this

override fun getTextOffset(): Int = nameElement?.textOffset ?: super.getTextOffset()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package co.anbora.labs.firebase.lang.core.psi.mixings

import co.anbora.labs.firebase.lang.core.psi.FirebaseRulesIdentifierExpr
import co.anbora.labs.firebase.lang.core.psi.impl.FirebaseNameIdentifierOwnerImpl
import com.intellij.lang.ASTNode

abstract class IdentifierMixing(node: ASTNode): FirebaseNameIdentifierOwnerImpl(node), FirebaseRulesIdentifierExpr {
}
Loading

0 comments on commit 1f4b3e9

Please sign in to comment.