diff --git a/build.gradle.kts b/build.gradle.kts index 73d0c63..110d584 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,10 +15,13 @@ repositories { } dependencies { + implementation("org.antlr:antlr4:4.13.1") implementation("dev.openfga:openfga-sdk:0.4.2") implementation("org.dmfs:oauth2-essentials:0.22.1") implementation("org.dmfs:httpurlconnection-executor:1.22.1") + implementation(files("libs/java-0.0.1.jar")) + testImplementation("junit:junit:4.13.2") } @@ -28,7 +31,10 @@ intellij { version.set("2023.3") type.set("IC") // Target IDE Platform - plugins.set(listOf("org.intellij.intelliLang", "org.jetbrains.plugins.yaml")) + plugins.set(listOf( + "org.intellij.intelliLang", + "org.jetbrains.plugins.yaml", + "com.intellij.java")) } grammarKit { diff --git a/src/main/java/dev/openfga/intellijplugin/language/OpenFGAAnnotator.java b/src/main/java/dev/openfga/intellijplugin/language/OpenFGAAnnotator.java new file mode 100644 index 0000000..490733f --- /dev/null +++ b/src/main/java/dev/openfga/intellijplugin/language/OpenFGAAnnotator.java @@ -0,0 +1,71 @@ +package dev.openfga.intellijplugin.language; + +import com.intellij.lang.annotation.AnnotationHolder; +import com.intellij.lang.annotation.ExternalAnnotator; +import com.intellij.lang.annotation.HighlightSeverity; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiFile; +import dev.openfga.language.errors.DslErrorsException; +import dev.openfga.language.errors.ParsingError; +import dev.openfga.language.errors.StartEnd; +import dev.openfga.language.validation.DslValidator; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.util.List; + +public class OpenFGAAnnotator extends ExternalAnnotator> { + + @Override + public @Nullable String collectInformation(@NotNull PsiFile file) { + return file.getText(); + } + + @Override + public @Nullable List doAnnotate(@NotNull final String collectedInfo) { + try { + DslValidator.validate(collectedInfo); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (DslErrorsException e) { + return e.getErrors(); + } + + return null; + } + + @Override + public void apply(@NotNull final PsiFile file, + final List annotationResult, + @NotNull final AnnotationHolder holder) { + + final String fileContents = file.getText(); + + for (ParsingError error : annotationResult) { + final StartEnd startEndLine = error.getLine(); + final StartEnd startEndColumn = error.getColumn(); + + int offsetStart = getOffsetFromRange(fileContents, startEndLine.getStart(), startEndColumn.getStart()); + int offsetEnd = getOffsetFromRange(fileContents, startEndLine.getEnd(), startEndColumn.getEnd()); + + holder.newAnnotation(HighlightSeverity.ERROR, error.getFullMessage()) + .range(new TextRange(offsetStart, offsetEnd)) + .create(); + } + } + + private static int getOffsetFromRange(@NotNull final String doc, int line, int character) { + int offset = 0; + + final String[] lines = doc.split("\n"); + + for (int i = 0; i < line; i++) { + offset += lines[i].length() + 1; + } + + offset += character; + + return offset; + } +} diff --git a/src/main/java/dev/openfga/intellijplugin/language/OpenFGAStoreAnnotator.java b/src/main/java/dev/openfga/intellijplugin/language/OpenFGAStoreAnnotator.java new file mode 100644 index 0000000..3af4d58 --- /dev/null +++ b/src/main/java/dev/openfga/intellijplugin/language/OpenFGAStoreAnnotator.java @@ -0,0 +1,100 @@ +package dev.openfga.intellijplugin.language; + +import com.intellij.lang.annotation.AnnotationHolder; +import com.intellij.lang.annotation.ExternalAnnotator; +import com.intellij.lang.annotation.HighlightSeverity; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import dev.openfga.language.errors.DslErrorsException; +import dev.openfga.language.errors.ParsingError; +import dev.openfga.language.errors.StartEnd; +import dev.openfga.language.validation.DslValidator; +import org.apache.commons.lang3.ObjectUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.yaml.YAMLUtil; +import org.jetbrains.yaml.psi.YAMLFile; + +import java.io.IOException; +import java.util.List; + +public class OpenFGAStoreAnnotator extends ExternalAnnotator> { + + @Override + public @Nullable String collectInformation(@NotNull final PsiFile file) { + + final Pair modelField = getModelField(file); + + if (ObjectUtils.isNotEmpty(modelField) && ObjectUtils.isNotEmpty(modelField.getSecond())) { + return modelField.getSecond(); + } + + return null; + } + + @Override + public @Nullable List doAnnotate(@NotNull final String collectedInfo) { + try { + DslValidator.validate(collectedInfo); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (DslErrorsException e) { + return e.getErrors(); + } + + return null; + } + + // Parsing is difficult: both the trimmed string (model) and the string retaining whitespace (originalString) + // The model is the clean string, whereas the originalString is that from the YAML with extra whitespace + // First the clean string is validated, then the original string is used to determine correct offsets + @Override + public void apply(@NotNull final PsiFile file, + final List annotationResult, + @NotNull final AnnotationHolder holder) { + + final @Nullable Pair fileContents = getModelField(file); + + final PsiElement key = fileContents.getFirst(); + final String model = fileContents.getSecond(); + final String originalString = key.getText().split("\n", 2)[1]; + // Key offset and newline + int offset = key.getFirstChild().getTextRange().getEndOffset() + 1; + + for (ParsingError error : annotationResult) { + final StartEnd startEndLine = error.getLine(); + final StartEnd startEndColumn = error.getColumn(); + + int offsetStart = getOffsetFromRange(originalString, startEndLine.getStart(), startEndColumn.getStart(), offset) + 2; + int offsetEnd = getOffsetFromRange(originalString, startEndLine.getEnd(), startEndColumn.getEnd(), offset) + 2; + + holder.newAnnotation(HighlightSeverity.ERROR, error.getMessage()) + .range(new TextRange(offsetStart, offsetEnd)) + .create(); + } + } + + private static int getOffsetFromRange(@NotNull final String doc, int line, int character, int offset) { + final String[] lines = doc.split("\n"); + + for (int i = 0; i < line; i++) { + final String replacementLine = lines[i].replaceFirst("^ ", ""); + offset += replacementLine.length() + (lines[i].length() - replacementLine.length()) + 1; + } + + offset += character; + + return offset; + } + + private static @Nullable Pair getModelField(@NotNull final PsiFile file) { + final Pair field = YAMLUtil.getValue((YAMLFile) file, "model"); + + if (ObjectUtils.isNotEmpty(field)) { + return field; + } + return null; + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index b2dda45..d0209c9 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -41,6 +41,14 @@ + + + +