Skip to content

Commit

Permalink
initial import
Browse files Browse the repository at this point in the history
  • Loading branch information
mvanholsteijn committed Jul 28, 2018
0 parents commit e4251ce
Show file tree
Hide file tree
Showing 21 changed files with 1,129 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.gradle/
.idea/
build/

*.iml
20 changes: 20 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
The MIT License (MIT)

Copyright (c) 2017 Piotr Wielgolaski

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Intellij plugin for validating AWS CloudFormation templates using cfn-lint


### Install plugin

Download JAR file from [releases](https://github.com/binxio/cfn-lint-plugin/releases) section. Then follow JetBrains [Installing Plugin from Disk](https://www.jetbrains.com/help/idea/managing-plugins.html#installing-plugins-from-disk) instructions.


## Credits
Inspired by [shellcheck-plugin](https://github.com/pwielgolaski/shellcheck)
28 changes: 28 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
plugins {
id 'java'
id 'org.jetbrains.intellij' version '0.3.5'
id 'io.freefair.git-version' version '2.5.9'
}

group 'io.binx.cfnlint.plugin'

sourceCompatibility = 1.8

repositories {
mavenCentral()
}

dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
}

intellij {
version '2018.1.4'
}
patchPluginXml {
changeNotes """
first release. work in progress.
"""
}

apply plugin: "io.freefair.git-version"
2 changes: 2 additions & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
rootProject.name = 'cfn-lint-plugin'

27 changes: 27 additions & 0 deletions src/main/java/io/binx/cfnlint/plugin/AnnotationResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.binx.cfnlint.plugin;

import io.binx.cfnlint.plugin.utils.CheckResult;

import java.util.Collections;
import java.util.List;
import java.util.Optional;

class AnnotationResult {

private final CheckAnnotationInput input;
private final CheckResult result;

AnnotationResult(CheckAnnotationInput input, CheckResult result) {
this.input = input;
this.result = result;
}

public List<CheckResult.Issue> getIssues() {
return Optional.ofNullable(result).map(CheckResult::getIssues).orElse(Collections.emptyList());
}

public CheckAnnotationInput getInput() {
return input;
}

}
34 changes: 34 additions & 0 deletions src/main/java/io/binx/cfnlint/plugin/Bundle.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.binx.cfnlint.plugin;

import com.intellij.CommonBundle;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.PropertyKey;

import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.util.ResourceBundle;

public final class Bundle {



public static String message(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, @NotNull Object... params) {
return CommonBundle.message(getBundle(), key, params);
}

@NonNls
private static final String BUNDLE = "io.binx.cfnlint.plugin.Bundle";
private static Reference<ResourceBundle> ourBundle;
private Bundle() {
}

private static ResourceBundle getBundle() {
ResourceBundle bundle = com.intellij.reference.SoftReference.dereference(ourBundle);
if (bundle == null) {
bundle = ResourceBundle.getBundle(BUNDLE);
ourBundle = new SoftReference<>(bundle);
}
return bundle;
}
}
32 changes: 32 additions & 0 deletions src/main/java/io/binx/cfnlint/plugin/CheckAnnotationInput.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.binx.cfnlint.plugin;

import com.intellij.psi.PsiFile;

class CheckAnnotationInput {
private final CheckProjectComponent component;
private final PsiFile psiFile;
private final String fileContent;

CheckAnnotationInput(CheckProjectComponent component, PsiFile psiFile, String fileContent) {
this.component = component;
this.psiFile = psiFile;
this.fileContent = fileContent;
}

CheckProjectComponent getComponent() {
return component;
}

String getCwd() {
return psiFile.getProject().getBasePath();
}

String getFilePath() {
return psiFile.getVirtualFile().getPath();
}

String getFileContent() {
return fileContent;
}

}
166 changes: 166 additions & 0 deletions src/main/java/io/binx/cfnlint/plugin/CheckExternalAnnotator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package io.binx.cfnlint.plugin;

import com.intellij.lang.annotation.Annotation;
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.lang.annotation.ExternalAnnotator;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.MultiplePsiFilesPerDocumentFileViewProvider;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.util.DocumentUtil;
import io.binx.cfnlint.plugin.utils.CheckResult;
import io.binx.cfnlint.plugin.utils.CheckRunner;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

public class CheckExternalAnnotator extends
ExternalAnnotator<CheckAnnotationInput, AnnotationResult> {

private static final Logger LOG = Logger.getInstance(CheckExternalAnnotator.class);

@Nullable
@Override
public CheckAnnotationInput collectInformation(@NotNull PsiFile file, @NotNull Editor editor, boolean hasErrors) {
return collectInformation(file);
}

@Nullable
@Override
public CheckAnnotationInput collectInformation(@NotNull PsiFile file) {
if (file.getContext() != null) {
return null;
}
VirtualFile virtualFile = file.getVirtualFile();
if (virtualFile == null || !virtualFile.isInLocalFileSystem()) {
return null;
}
if (file.getViewProvider() instanceof MultiplePsiFilesPerDocumentFileViewProvider) {
return null;
}
CheckProjectComponent component = file.getProject().getComponent(CheckProjectComponent.class);
if (!component.isSettingsValid() || !component.isEnabled() || !isFile(file)) {
return null;
}
FileDocumentManager fileDocumentManager = FileDocumentManager.getInstance();
boolean fileModified = fileDocumentManager.isFileModified(virtualFile);
return new CheckAnnotationInput(component, file, fileModified ? file.getText() : null);
}

@Nullable
@Override
public AnnotationResult doAnnotate(CheckAnnotationInput input) {
CheckProjectComponent component = input.getComponent();
try {
CheckResult result = CheckRunner.runCheck(component.getSettings().executable, input.getCwd(), input.getFilePath(), input.getFileContent());

if (StringUtils.isNotEmpty(result.getErrorOutput())) {
component.showInfoNotification(result.getErrorOutput(), NotificationType.WARNING);
return null;
}
return new AnnotationResult(input, result);
} catch (Exception e) {
LOG.error("Error running inspection: ", e);
component.showInfoNotification("Error running inspection: " + e.getMessage(), NotificationType.ERROR);
}
return null;
}

@Override
public void apply(@NotNull PsiFile file, AnnotationResult annotationResult, @NotNull AnnotationHolder holder) {
if (annotationResult == null) {
return;
}
Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file);
if (document == null) {
return;
}

CheckProjectComponent component = annotationResult.getInput().getComponent();
for (CheckResult.Issue issue : annotationResult.getIssues()) {
HighlightSeverity severity = getHighlightSeverity(issue, component.getSettings().treatAllIssuesAsWarnings);
createAnnotation(holder, document, issue, severity, component);
}
}

private static HighlightSeverity getHighlightSeverity(CheckResult.Issue issue, boolean treatAsWarnings) {
switch (issue.level.toLowerCase()) {
case "error":
return treatAsWarnings ? HighlightSeverity.WARNING : HighlightSeverity.ERROR;
case "warning":
return HighlightSeverity.WARNING;
case "info":
return HighlightSeverity.INFORMATION;
default:
return HighlightSeverity.INFORMATION;
}
}

@Nullable
private Annotation createAnnotation(@NotNull AnnotationHolder holder, @NotNull Document document, @NotNull CheckResult.Issue issue,
@NotNull HighlightSeverity severity,
CheckProjectComponent component) {
int errorLine = issue.location.start.lineNumber - 1;
boolean showErrorOnWholeLine = component.getSettings().highlightWholeLine;

if (errorLine < 0 || errorLine >= document.getLineCount()) {
return null;
}

int lineStartOffset = document.getLineStartOffset(errorLine);
int lineEndOffset = document.getLineEndOffset(errorLine);

int errorLineStartOffset = appendNormalizeColumn(document, lineStartOffset, lineEndOffset, issue.location.start.columnNumber - 1);
if (errorLineStartOffset == -1) {
return null;
}

TextRange range;
if (showErrorOnWholeLine) {
int start = DocumentUtil.getFirstNonSpaceCharOffset(document, lineStartOffset, lineEndOffset);
range = new TextRange(start, lineEndOffset);
} else {
range = new TextRange(errorLineStartOffset, errorLineStartOffset + 1);
}

Annotation annotation = holder.createAnnotation(severity, range, ": " + issue.getFormattedMessage());
if (annotation != null) {
annotation.setAfterEndOfLine(errorLineStartOffset == lineEndOffset);
}
return annotation;
}

private int appendNormalizeColumn(@NotNull Document document, int startOffset, int endOffset, int column) {
CharSequence text = document.getImmutableCharSequence();
int col = 0;
for (int i = startOffset; i < endOffset; i++) {
char c = text.charAt(i);
col += (c == '\t' ? 8 : 1);
if (col > column) {
return i;
}
}
return startOffset;
}

private static boolean isFile(PsiFile file) {
// TODO move to settings?
List<String> acceptedExtensions = Arrays.asList("yml", "yaml", "json");
boolean isCloudFormation = file.getFileType().getName().equals("CloudFormation");
String fileExtension = Optional.ofNullable(file.getVirtualFile()).map(VirtualFile::getExtension).orElse("");
return isCloudFormation || acceptedExtensions.contains(fileExtension);
}
}


Loading

0 comments on commit e4251ce

Please sign in to comment.