Skip to content

Commit 1f4b3e9

Browse files
authored
Merge pull request #82 from anboralabs/psi-impl
Annotators
2 parents ee2ac69 + 5c9eee0 commit 1f4b3e9

20 files changed

+295
-12
lines changed

build.gradle

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
plugins {
22
id 'java'
3-
id 'org.jetbrains.intellij' version '1.1.2'
3+
id 'org.jetbrains.intellij' version '1.1.6'
44
id 'org.jetbrains.kotlin.jvm' version '1.5.21'
55
id "org.jetbrains.grammarkit" version "2021.1.3"
66
}
@@ -10,7 +10,7 @@ apply plugin: 'org.jetbrains.grammarkit'
1010
import org.jetbrains.grammarkit.tasks.*
1111

1212
group 'co.anbora.labs'
13-
version '2.6.5'
13+
version '2.6.6'
1414

1515
repositories {
1616
mavenCentral()
@@ -58,7 +58,7 @@ sourceSets {
5858

5959
// See https://github.com/JetBrains/gradle-intellij-plugin/
6060
intellij {
61-
version = 'LATEST-EAP-SNAPSHOT'
61+
version = '2021.2'
6262
}
6363

6464
java {

src/main/grammar/FirebaseRules.bnf

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
DOT = '.'
3939
Q_MARK = '?'
4040
DOT_COMMA = ';'
41+
WILD_CARD = '=**'
4142
MODULO = '%'
4243
char = 'regexp:[\n\r\u2028\u2029]'
4344
LINE_COMMENT='LINE_COMMENT'
@@ -106,10 +107,13 @@ private FullPathStatementItem ::= !('{') SLASH PathStatement {
106107
pin=1
107108
recoverWhile=FullPathStatementItem_recover
108109
}
109-
private FullPathStatementItem_recover ::= !(SLASH | '{' | IDENTIFIER | PATH_VARIABLE)
110+
private FullPathStatementItem_recover ::= !(SLASH | '{' | IDENTIFIER | VariableInPath)
110111
//Match Definition End
111112

112-
PathStatement ::= (DOT? IDENTIFIER|PATH_VARIABLE)
113+
PathStatement ::= (DOT? IDENTIFIER|VariableInPath)
114+
VariableInPath ::= PATH_VARIABLE {
115+
mixin="co.anbora.labs.firebase.lang.core.psi.mixings.IdentifierMixing"
116+
}
113117

114118
//Allow Statement Begin
115119
AllowStatement ::= EmptyAllowStm | ConditionalAllowStm
@@ -146,7 +150,7 @@ BuiltInFunctionStatement ::= (GET_KEYWORD|EXITS_KEYWORD)
146150
BuiltInTypes ::= LIST_KEYWORD
147151

148152
//Function Definition Begin
149-
FunctionDef ::= FUNCTION_KEYWORD IDENTIFIER FunctionParameterList FunctionBlock
153+
FunctionDef ::= FUNCTION_KEYWORD IdentifierExpr FunctionParameterList FunctionBlock
150154
{
151155
pin=1
152156
}
@@ -205,7 +209,9 @@ private RangeExpr ::= Expression ':' Expression
205209
TernaryExpr ::= Expression '?' Expression ':' Expression
206210

207211

208-
IdentifierExpr ::= IDENTIFIER
212+
IdentifierExpr ::= IDENTIFIER {
213+
mixin="co.anbora.labs.firebase.lang.core.psi.mixings.IdentifierMixing"
214+
}
209215
DotExpr ::= Expression DOT Expression
210216
CallExpr ::= Expression CallArguments
211217
CallArguments ::= '(' ParameterStatement? ')'
@@ -259,7 +265,7 @@ private ArrayExpr ::= LB (ParameterStatement) RB
259265

260266
LiteralStatement ::= (number|string)
261267

262-
VariableStatement ::= LET_KEYWORD IDENTIFIER EQ Expression (DOT_COMMA?)
268+
VariableStatement ::= LET_KEYWORD IdentifierExpr EQ Expression (DOT_COMMA?)
263269

264270
NullStatement ::= NULL_KEYWORD
265271

src/main/html/change-notes.html

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
Versions:
22
<ul>
3-
<li>All Intellij products support: 2.6.5</li>
4-
<li>Android Studio support: 2.6.5</li>
3+
<li>All Intellij products support: 2.6.6</li>
4+
<li>Android Studio support: 2.6.6</li>
55
</ul>
66
<br>
77
Plugin updates:
88
<ul>
9+
<li><b>2.6.6</b> <em>(2021-09-24)</em> - Added local inspections</li>
10+
<ul>
11+
<li>Added local inspection for duplicated functions. </li>
12+
<li>Added local inspection for weak security rules. </li>
13+
<li>Added local inspection for duplicated path variables. </li>
14+
<li>Added color support for service name. </li>
15+
<li>Added quote handler. </li>
16+
</ul>
917
<li><b>2.6.5</b> <em>(2021-09-13)</em> - Fixed issue</li>
1018
<ul>
1119
<li>Fixed issue with external library. </li>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Use of this source code is governed by the MIT license that can be
3+
* found in the LICENSE file.
4+
*/
5+
6+
package co.anbora.labs.firebase.ide.annotator
7+
8+
import com.intellij.lang.annotation.AnnotationHolder
9+
import com.intellij.lang.annotation.Annotator
10+
import com.intellij.openapi.Disposable
11+
import com.intellij.openapi.util.Disposer
12+
import com.intellij.psi.PsiElement
13+
import com.intellij.util.containers.ContainerUtil
14+
import org.jetbrains.annotations.TestOnly
15+
16+
abstract class FirebaseAnnotator : Annotator {
17+
final override fun annotate(element: PsiElement, holder: AnnotationHolder) {
18+
if (javaClass in enabledAnnotators) {
19+
annotateInternal(element, holder)
20+
}
21+
}
22+
23+
protected abstract fun annotateInternal(element: PsiElement, holder: AnnotationHolder)
24+
25+
companion object {
26+
private val enabledAnnotators: MutableSet<Class<out FirebaseAnnotator>> = ContainerUtil.newConcurrentSet()
27+
28+
@TestOnly
29+
fun enableAnnotator(annotatorClass: Class<out FirebaseAnnotator>, parentDisposable: Disposable) {
30+
enabledAnnotators += annotatorClass
31+
Disposer.register(
32+
parentDisposable
33+
) { enabledAnnotators -= annotatorClass }
34+
}
35+
}
36+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package co.anbora.labs.firebase.ide.annotator
2+
3+
class HighlightingAnnotator {
4+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package co.anbora.labs.firebase.ide.annotator
2+
3+
import co.anbora.labs.firebase.ide.color.FirebaseColors
4+
import co.anbora.labs.firebase.lang.core.psi.FirebaseRulesIdentifierExpr
5+
import co.anbora.labs.firebase.lang.core.psi.FirebaseRulesMatchDef
6+
import co.anbora.labs.firebase.lang.core.psi.FirebaseRulesPathStatement
7+
import co.anbora.labs.firebase.lang.core.psi.FirebaseRulesVisitor
8+
import com.intellij.lang.annotation.AnnotationHolder
9+
import com.intellij.psi.PsiElement
10+
import com.intellij.psi.util.PsiTreeUtil
11+
12+
class PathVariableHighlightAnnotator : FirebaseAnnotator() {
13+
override fun annotateInternal(element: PsiElement, holder: AnnotationHolder) {
14+
val visitor = object : FirebaseRulesVisitor() {
15+
override fun visitMatchDef(o: FirebaseRulesMatchDef) {
16+
checkPathVariablesInMatchDef(holder, element)
17+
}
18+
}
19+
element.accept(visitor)
20+
}
21+
22+
private fun checkPathVariablesInMatchDef(holder: AnnotationHolder, element: PsiElement) {
23+
val color = FirebaseColors.NUMBERS
24+
val pathVariables = PsiTreeUtil.collectElementsOfType(element, FirebaseRulesPathStatement::class.java)
25+
.map { it.text }
26+
.map { it.replace("{", "")
27+
.replace("}", "")
28+
.replace("=", "")
29+
.replace("*", "")
30+
}.toSet()
31+
val variables = PsiTreeUtil.collectElementsOfType(element, FirebaseRulesIdentifierExpr::class.java)
32+
variables.forEach {
33+
if (pathVariables.contains(it.text)) {
34+
35+
}
36+
}
37+
}
38+
}

src/main/kotlin/co/anbora/labs/firebase/ide/color/FirebaseColors.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ enum class FirebaseColors(humanName: String, default: TextAttributesKey) {
1313
PATH_AND_STRING("Path and Strings", DefaultLanguageHighlighterColors.STRING),
1414
COMMENTS("Comments", DefaultLanguageHighlighterColors.LINE_COMMENT),
1515
CALL_FUNCTION("Functions", DefaultLanguageHighlighterColors.FUNCTION_CALL),
16-
NUMBERS("Numbers", DefaultLanguageHighlighterColors.NUMBER);
16+
NUMBERS("Numbers", DefaultLanguageHighlighterColors.NUMBER),
17+
SERVICE_NAME("Service name", DefaultLanguageHighlighterColors.NUMBER);
1718

1819
val textAttributesKey = TextAttributesKey.createTextAttributesKey("co.anbora.labs.firebase.$name", default)
1920
val attributesDescriptor = AttributesDescriptor(humanName, textAttributesKey)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package co.anbora.labs.firebase.ide.editor
2+
3+
import co.anbora.labs.firebase.lang.core.psi.FirebaseRulesTypes.STRING
4+
import com.intellij.codeInsight.editorActions.SimpleTokenSetQuoteHandler
5+
6+
class FirebaseQuoteHandler: SimpleTokenSetQuoteHandler(STRING)

src/main/kotlin/co/anbora/labs/firebase/ide/highlight/FirebaseSyntaxHighlighter.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@ class FirebaseSyntaxHighlighter: SyntaxHighlighterBase() {
2525
GET_KEYWORD, READ_KEYWORD, WRITE_KEYWORD,
2626
LIST_KEYWORD, CREATE_KEYWORD, UPDATE_KEYWORD,
2727
DELETE_KEYWORD, EXITS_KEYWORD -> FirebaseColors.PERMISSIONS
28-
PATH_VARIABLE, PATH_BUILT_IN, STRING -> FirebaseColors.PATH_AND_STRING
28+
PATH_VARIABLE, PATH_BUILT_IN, STRING, VERSIONS -> FirebaseColors.PATH_AND_STRING
2929
LINE_COMMENT, BLOCK_COMMENT -> FirebaseColors.COMMENTS
3030
CALL_EXPR -> FirebaseColors.CALL_FUNCTION
3131
NUMBER -> FirebaseColors.NUMBERS
32+
SERVICE_NAME -> FirebaseColors.NUMBERS
3233
TokenType.BAD_CHARACTER -> FirebaseColors.BAD_CHAR
3334
else -> null
3435
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package co.anbora.labs.firebase.ide.inspections
2+
3+
import co.anbora.labs.firebase.lang.core.psi.*
4+
import com.intellij.codeInspection.LocalInspectionTool
5+
import com.intellij.codeInspection.ProblemHighlightType
6+
import com.intellij.codeInspection.ProblemsHolder
7+
import com.intellij.psi.PsiElement
8+
import com.intellij.psi.PsiElementVisitor
9+
import com.intellij.psi.util.PsiTreeUtil
10+
11+
class DuplicateFunctionsDeclarationInspection : LocalInspectionTool() {
12+
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
13+
return object: PsiElementVisitor() {
14+
override fun visitElement(element: PsiElement) {
15+
super.visitElement(element)
16+
when (element) {
17+
is FirebaseFile -> checkFunctionSignature(element, holder)
18+
is FirebaseRulesFullPathStatement -> checkPathVariable(element, holder)
19+
}
20+
}
21+
}
22+
}
23+
}
24+
25+
private fun checkFunctionSignature(element: FirebaseFile, holder: ProblemsHolder) {
26+
val functions = PsiTreeUtil.collectElementsOfType(element, FirebaseRulesFunctionDef::class.java)
27+
val mapFunctions = HashMap<String, Unit>()
28+
functions.forEach {
29+
val key = it.identifierExpr?.text + "_" + it.functionParameterList?.functionParameterList?.size
30+
mapFunctions.compute(key) { _, v ->
31+
if (v != null) markDuplicate(it.identifierExpr ?: it, holder)
32+
}
33+
}
34+
}
35+
36+
private fun checkPathVariable(element: FirebaseRulesFullPathStatement, holder: ProblemsHolder) {
37+
val variables = PsiTreeUtil.collectElementsOfType(element, FirebaseRulesVariableInPath::class.java)
38+
val mapPathVariables = HashMap<String, Unit>()
39+
variables.forEach {
40+
val key = it.text
41+
mapPathVariables.compute(key) { _, v ->
42+
if (v != null) markDuplicate(it, holder)
43+
}
44+
}
45+
}
46+
47+
private fun markDuplicate(element: FirebaseElement, holder: ProblemsHolder) {
48+
holder.registerProblem(element, "Duplicate definitions with name `${element.text}`", ProblemHighlightType.ERROR)
49+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package co.anbora.labs.firebase.ide.inspections
2+
3+
import co.anbora.labs.firebase.lang.core.psi.FirebaseRulesConditionalBlock
4+
import co.anbora.labs.firebase.lang.core.psi.FirebaseRulesLiteralExpr
5+
import com.intellij.codeInspection.LocalInspectionTool
6+
import com.intellij.codeInspection.ProblemHighlightType
7+
import com.intellij.codeInspection.ProblemsHolder
8+
import com.intellij.psi.PsiElement
9+
import com.intellij.psi.PsiElementVisitor
10+
11+
private const val FIREBASE_TRUE = "true"
12+
13+
class FirebaseWeakRulesInspection: LocalInspectionTool() {
14+
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
15+
return object: PsiElementVisitor() {
16+
override fun visitElement(element: PsiElement) {
17+
super.visitElement(element)
18+
when (element) {
19+
is FirebaseRulesConditionalBlock -> checkWeakRule(element, holder)
20+
}
21+
}
22+
}
23+
}
24+
}
25+
26+
private fun checkWeakRule(element: FirebaseRulesConditionalBlock, holder: ProblemsHolder) {
27+
val decl = element.expression
28+
val literal = decl as? FirebaseRulesLiteralExpr
29+
val booleanStm = literal?.booleanStatement
30+
if (booleanStm?.text == FIREBASE_TRUE) {
31+
holder.registerProblem(booleanStm, "Weak rule, this is not recommended for production environments.", ProblemHighlightType.WEAK_WARNING)
32+
}
33+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package co.anbora.labs.firebase.lang.core.psi
2+
3+
import com.intellij.psi.PsiNameIdentifierOwner
4+
5+
interface FirebaseNameIdentifierOwner : FirebaseNamedElement,
6+
PsiNameIdentifierOwner
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package co.anbora.labs.firebase.lang.core.psi
2+
3+
import co.anbora.labs.firebase.lang.core.psi.FirebaseRulesTypes.IDENTIFIER
4+
import co.anbora.labs.firebase.lang.core.psi.ext.findLastChildByType
5+
import com.intellij.psi.NavigatablePsiElement
6+
import com.intellij.psi.PsiElement
7+
import com.intellij.psi.PsiNamedElement
8+
9+
interface FirebaseNamedElement : FirebaseElement,
10+
PsiNamedElement,
11+
NavigatablePsiElement {
12+
13+
val nameElement: PsiElement?
14+
get() = findLastChildByType(IDENTIFIER)
15+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package co.anbora.labs.firebase.lang.core.psi.ext
2+
3+
import com.intellij.psi.PsiElement
4+
import com.intellij.psi.tree.IElementType
5+
import com.intellij.psi.util.PsiUtilCore
6+
7+
val PsiElement.elementType: IElementType
8+
get() = PsiUtilCore.getElementType(this)
9+
10+
val PsiElement.childrenWithLeaves: Sequence<PsiElement>
11+
get() = generateSequence(this.firstChild) { it.nextSibling }
12+
13+
fun PsiElement.childrenByType(type: IElementType): Sequence<PsiElement> =
14+
childrenWithLeaves.filter { it.elementType == type }
15+
16+
fun PsiElement.findLastChildByType(type: IElementType): PsiElement? =
17+
childrenByType(type).lastOrNull()
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package co.anbora.labs.firebase.lang.core.psi.impl
2+
3+
import co.anbora.labs.firebase.lang.core.psi.FirebaseNameIdentifierOwner
4+
import com.intellij.lang.ASTNode
5+
import com.intellij.psi.PsiElement
6+
7+
abstract class FirebaseNameIdentifierOwnerImpl(
8+
node: ASTNode
9+
) : FirebaseNamedElementImpl(node), FirebaseNameIdentifierOwner {
10+
override fun getNameIdentifier(): PsiElement? = nameElement
11+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package co.anbora.labs.firebase.lang.core.psi.impl
2+
3+
import co.anbora.labs.firebase.lang.core.psi.FirebaseElementImpl
4+
import co.anbora.labs.firebase.lang.core.psi.FirebaseNamedElement
5+
import com.intellij.lang.ASTNode
6+
import com.intellij.psi.PsiElement
7+
8+
abstract class FirebaseNamedElementImpl(
9+
node: ASTNode
10+
) : FirebaseElementImpl(node), FirebaseNamedElement {
11+
override fun getName(): String? = nameElement?.text
12+
13+
override fun setName(name: String): PsiElement {
14+
//nameElement?.replace(MovePsiFactory(project).createIdentifier(name))
15+
return this
16+
}
17+
18+
override fun getNavigationElement(): PsiElement = nameElement ?: this
19+
20+
override fun getTextOffset(): Int = nameElement?.textOffset ?: super.getTextOffset()
21+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package co.anbora.labs.firebase.lang.core.psi.mixings
2+
3+
import co.anbora.labs.firebase.lang.core.psi.FirebaseRulesIdentifierExpr
4+
import co.anbora.labs.firebase.lang.core.psi.impl.FirebaseNameIdentifierOwnerImpl
5+
import com.intellij.lang.ASTNode
6+
7+
abstract class IdentifierMixing(node: ASTNode): FirebaseNameIdentifierOwnerImpl(node), FirebaseRulesIdentifierExpr {
8+
}

0 commit comments

Comments
 (0)