-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: external annotator for OpenFGA files
- Loading branch information
Showing
4 changed files
with
186 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
71 changes: 71 additions & 0 deletions
71
src/main/java/dev/openfga/intellijplugin/language/OpenFGAAnnotator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<String, List<? extends ParsingError>> { | ||
|
||
@Override | ||
public @Nullable String collectInformation(@NotNull PsiFile file) { | ||
return file.getText(); | ||
} | ||
|
||
@Override | ||
public @Nullable List<? extends ParsingError> 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<? extends ParsingError> 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; | ||
} | ||
} |
100 changes: 100 additions & 0 deletions
100
src/main/java/dev/openfga/intellijplugin/language/OpenFGAStoreAnnotator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<String, List<? extends ParsingError>> { | ||
|
||
@Override | ||
public @Nullable String collectInformation(@NotNull final PsiFile file) { | ||
|
||
final Pair<PsiElement, String> modelField = getModelField(file); | ||
|
||
if (ObjectUtils.isNotEmpty(modelField) && ObjectUtils.isNotEmpty(modelField.getSecond())) { | ||
return modelField.getSecond(); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
@Override | ||
public @Nullable List<? extends ParsingError> 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<? extends ParsingError> annotationResult, | ||
@NotNull final AnnotationHolder holder) { | ||
|
||
final @Nullable Pair<PsiElement, String> 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<PsiElement, String> getModelField(@NotNull final PsiFile file) { | ||
final Pair<PsiElement, String> field = YAMLUtil.getValue((YAMLFile) file, "model"); | ||
|
||
if (ObjectUtils.isNotEmpty(field)) { | ||
return field; | ||
} | ||
return null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters