Skip to content

Commit

Permalink
GML: Lint property values
Browse files Browse the repository at this point in the history
  • Loading branch information
mattco98 committed Jan 27, 2024
1 parent 7d021cd commit 5ed9ef9
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 49 deletions.
57 changes: 29 additions & 28 deletions src/main/kotlin/me/mattco/serenityos/common/DSLAnnotator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,36 @@ import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement

abstract class DSLAnnotator : Annotator {
final override fun annotate(element: PsiElement, holder: AnnotationHolder) {
annotate(element, Holder(holder))
private lateinit var holder: AnnotationHolder

override fun annotate(element: PsiElement, holder: AnnotationHolder) {
this.holder = holder
annotate(element)
}

abstract fun annotate(element: PsiElement)

private fun newAnnotation(severity: HighlightSeverity, message: String? = null) = if (message == null) {
holder.newSilentAnnotation(severity)
} else holder.newAnnotation(severity, message)

fun PsiElement.highlight(attribute: TextAttributesKey) {
newAnnotation(HighlightSeverity.INFORMATION)
.range(this)
.textAttributes(attribute)
.create()
}

fun TextRange.highlight(attribute: TextAttributesKey) {
newAnnotation(HighlightSeverity.INFORMATION)
.range(this)
.textAttributes(attribute)
.create()
}

protected abstract fun annotate(element: PsiElement, holder: Holder)

data class Holder(private val holder: AnnotationHolder) {
private fun newAnnotation(severity: HighlightSeverity, message: String? = null) = if (message == null) {
holder.newSilentAnnotation(severity)
} else holder.newAnnotation(severity, message)

fun PsiElement.highlight(attribute: TextAttributesKey) {
newAnnotation(HighlightSeverity.INFORMATION)
.range(this)
.textAttributes(attribute)
.create()
}

fun TextRange.highlight(attribute: TextAttributesKey) {
newAnnotation(HighlightSeverity.INFORMATION)
.range(this)
.textAttributes(attribute)
.create()
}

fun PsiElement.highlightError(message: String) {
newAnnotation(HighlightSeverity.ERROR, message)
.range(this)
.create()
}
fun PsiElement.highlightError(message: String) {
newAnnotation(HighlightSeverity.ERROR, message)
.range(this)
.create()
}
}
Original file line number Diff line number Diff line change
@@ -1,30 +1,93 @@
package me.mattco.serenityos.gml.annotators

import ai.grazie.utils.dropPrefix
import com.intellij.lang.annotation.AnnotationHolder
import com.intellij.openapi.components.service
import com.intellij.psi.PsiElement
import me.mattco.serenityos.common.DSLAnnotator
import me.mattco.serenityos.common.ancestorOfType
import me.mattco.serenityos.gml.GMLService
import me.mattco.serenityos.gml.psi.api.GMLComponent
import me.mattco.serenityos.gml.psi.api.GMLComponentName
import me.mattco.serenityos.gml.psi.api.GMLPropertyIdentifier
import me.mattco.serenityos.gml.Type
import me.mattco.serenityos.gml.psi.api.*

class GMLErrorAnnotator : DSLAnnotator() {
override fun annotate(element: PsiElement, holder: Holder) = with(holder) {
override fun annotate(element: PsiElement) {
when (element) {
is GMLComponentName -> {
val name = element.text.dropPrefix("@")
if (element.project.service<GMLService>().lookupComponent(name) == null)
element.highlightError("Unknown component")
}
is GMLPropertyIdentifier -> {
val parentWidget = element.ancestorOfType<GMLComponent>() ?: return@with
val parentWidget = element.ancestorOfType<GMLComponent>() ?: return
val gmlService = element.project.service<GMLService>()
val component = gmlService.lookupComponent(parentWidget.identWithoutAt) ?: return@with
val component = gmlService.lookupComponent(parentWidget.identWithoutAt) ?: return
if (component.getProperty(element.identifier.text, gmlService) == null)
element.highlightError("Unknown property")
}
is GMLProperty -> {
val type = element.gmlProperty?.type ?: return
val value = element.value ?: return
lintType(type, value)?.let {
it.targetElement.highlightError(it.error)
}
}
}
}

private data class Lint(val targetElement: PsiElement, val error: String)

private fun lintType(type: Type, value: GMLValue): Lint? {
when (type) {
is Type.Array -> {
if (value.array == null)
return Lint(value, "Expected Array")

if (value.array!!.valueList.size !in type.min..type.max) {
if (type.min == type.max)
return Lint(value, "Expected Array length to have ${type.min} elements")
return Lint(value, "Expected Array length to have ${type.min}-${type.max} elements")
}

for (element in value.array!!.valueList) {
lintType(type.inner, element)?.let { return it }
}
}
Type.Bitmap -> if (value.string == null) return Lint(value, "Expected String")
Type.Bool -> if (value.boolean == null) return Lint(value, "Expected bool")
Type.Color -> if (value.string == null) return Lint(value, "Expected String")
Type.Double -> if (value.number == null) return Lint(value, "Expected double")
is Type.EnumType -> if (value.string == null) return Lint(value, "Expected String")
is Type.ErrorType -> return Lint(value, type.message)
is Type.Int -> {
if (value.number == null)
return Lint(value, "Expected ${type.presentation()}")
if (!type.signed && value.number!!.text.startsWith('-'))
return Lint(value, "Unsigned int cannot hold a negative number")
}
Type.Margins -> return lintType(Type.Array(1, 4, Type.Int(true)), value)
Type.String -> if (value.string == null) return Lint(value, "Expected String")
Type.UIDimension -> {
if (value.string != null) {
if (value.string!!.text !in uiDimEnumValues)
return Lint(value, "Invalid enum value, valid value are: $uiDimEnumValuesForDisplay")
} else if (value.number == null) {
return Lint(value, "Expected an i64 or one of: $uiDimEnumValuesForDisplay")
} else {
return lintType(Type.Int(false), value)
}
}
is Type.Variant -> {
if (type.types.all { lintType(it, value) != null })
return Lint(value, "Expected one of: ${type.presentation()}")
}
}

return null
}

companion object {
private val uiDimEnumValues = setOf("grow", "opportunistic_grow", "fit", "shrink")
private val uiDimEnumValuesForDisplay = uiDimEnumValues.joinToString { "\"$it\"" }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import me.mattco.serenityos.gml.psi.api.GMLProperty
import me.mattco.serenityos.gml.psi.api.GMLValue

class GMLSyntaxAnnotator : DSLAnnotator(), DumbAware {
override fun annotate(element: PsiElement, holder: Holder) = with(holder) {
override fun annotate(element: PsiElement) {
when (element) {
is GMLValue -> {
if (element.string != null) {
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/me/mattco/serenityos/gml/typing.kt
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ sealed interface Type {
override fun presentation() = "Margins"
}

class Array(private val min: kotlin.Int, private val max: kotlin.Int, private val inner: Type) : Type {
class Array(val min: kotlin.Int, val max: kotlin.Int, val inner: Type) : Type {
override fun presentation(): kotlin.String = buildString {
append(inner.presentation().let {
if (inner is Variant) "($it)" else it
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,12 @@ import com.intellij.psi.PsiWhiteSpace
import me.mattco.serenityos.common.DSLAnnotator
import me.mattco.serenityos.common.prevSiblings
import me.mattco.serenityos.idl.psi.IDLDeclaration
import me.mattco.serenityos.idl.psi.api.IDLCallbackInterface
import me.mattco.serenityos.idl.psi.api.IDLExtendedAttributeList
import me.mattco.serenityos.idl.psi.api.IDLIdent
import me.mattco.serenityos.idl.psi.api.IDLInterface
import me.mattco.serenityos.idl.psi.api.IDLInterfaceMixin
import me.mattco.serenityos.idl.psi.api.*
import java.net.MalformedURLException
import java.net.URL

class IDLErrorAnnotator : DSLAnnotator() {
override fun annotate(element: PsiElement, holder: Holder) = with(holder) {
override fun annotate(element: PsiElement) {
if (element is IDLIdent) {
if (element.reference != null && element.reference?.resolve() == null) {
element.highlightError("Unknown type ${element.text}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import me.mattco.serenityos.idl.Highlights
import me.mattco.serenityos.idl.psi.api.*

class IDLSyntaxAnnotator : DSLAnnotator(), DumbAware {
override fun annotate(element: PsiElement, holder: Holder) = with(holder) {
override fun annotate(element: PsiElement) {
when (element) {
is IDLImportPath ->
TextRange(element.openAngle.startOffset, element.closeAngle.endOffset).highlight(Highlights.IMPORT_PATH)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,10 @@ import com.intellij.openapi.project.DumbAware
import com.intellij.psi.PsiElement
import me.mattco.serenityos.common.DSLAnnotator
import me.mattco.serenityos.common.findChildrenOfType
import me.mattco.serenityos.ipc.psi.api.IPCAttributeList
import me.mattco.serenityos.ipc.psi.api.IPCEndpoint
import me.mattco.serenityos.ipc.psi.api.IPCEndpointFunction
import me.mattco.serenityos.ipc.psi.api.IPCIncludePath
import me.mattco.serenityos.ipc.psi.api.IPCType
import me.mattco.serenityos.ipc.psi.api.*

class IPCSyntaxAnnotator : DSLAnnotator(), DumbAware {
override fun annotate(element: PsiElement, holder: Holder) = with(holder) {
override fun annotate(element: PsiElement) {
when (element) {
is IPCEndpoint -> element.identifier.highlight(Highlights.ENDPOINT_NAME)
is IPCEndpointFunction -> {
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/grammar/SerenityOS GML.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Property ::= PropertyIdentifier COLON Value {
private Property_recover ::= !(IDENTIFIER | CLOSE_CURLY | AT)

PropertyIdentifier ::= IDENTIFIER

Value ::=
BOOLEAN
| NUMBER
Expand Down

0 comments on commit 5ed9ef9

Please sign in to comment.