diff --git a/plugins/yaml/resources/META-INF/plugin.xml b/plugins/yaml/resources/META-INF/plugin.xml
index ad9cb6939fa9..3da6e351d351 100644
--- a/plugins/yaml/resources/META-INF/plugin.xml
+++ b/plugins/yaml/resources/META-INF/plugin.xml
@@ -44,6 +44,11 @@
key="inspections.recursive.alias.name" level="ERROR"
groupKey="inspections.group.name" enabledByDefault="true"
implementationClass="org.jetbrains.yaml.inspections.YAMLRecursiveAliasInspection"/>
+
diff --git a/plugins/yaml/resources/inspectionDescriptions/YAMLDuplicatedKeys.html b/plugins/yaml/resources/inspectionDescriptions/YAMLDuplicatedKeys.html
new file mode 100644
index 000000000000..258a76ce753a
--- /dev/null
+++ b/plugins/yaml/resources/inspectionDescriptions/YAMLDuplicatedKeys.html
@@ -0,0 +1,6 @@
+
+
+This inspection detects duplicated keys in YAML files and highlights them in the editor.
+
+
+
\ No newline at end of file
diff --git a/plugins/yaml/resources/messages/YAMLBundle.properties b/plugins/yaml/resources/messages/YAMLBundle.properties
index 5aac709b4fe8..9564a3f1083a 100644
--- a/plugins/yaml/resources/messages/YAMLBundle.properties
+++ b/plugins/yaml/resources/messages/YAMLBundle.properties
@@ -23,8 +23,8 @@ YamlMissingKeysInspectionBase.add.missing.keys.quickfix.name=Add missing keys
YamlNonEditableKeysInspectionBase.noneditable.key=Key ''{0}'' is not modifiable by user
YamlDeprecatedKeysInspectionBase.deprecated.key=Key ''{0}'' is deprecated
-YamlDuplicatedKeysInspectionBase.duplicated.key=Key ''{0}'' is duplicated
-YamlDuplicatedKeysInspectionBase.remove.key.quickfix.name=Remove key
+YAMLDuplicatedKeysInspection.duplicated.key=Key ''{0}'' is duplicated
+YAMLDuplicatedKeysInspection.remove.key.quickfix.name=Remove key
YamlNonEditableKeyInspectionBase.strip.noneditable.keys.quickfix.name=Remove all non-editable keys from the file
YamlUnknownValuesInspectionBase.error.value.is.required=Value is required
@@ -69,3 +69,4 @@ inspections.unresolved.alias.name=Unresolved alias
inspections.unresolved.alias.message=Can''t resolve alias {0}
inspections.recursive.alias.name=Recursive alias
inspections.recursive.alias.message=Alias can't be recursive
+inspections.duplicated.keys.name=Duplicated YAML keys
\ No newline at end of file
diff --git a/plugins/yaml/src/org/jetbrains/yaml/meta/impl/YamlDuplicatedKeysInspectionBase.java b/plugins/yaml/src/org/jetbrains/yaml/inspections/YAMLDuplicatedKeysInspection.java
similarity index 73%
rename from plugins/yaml/src/org/jetbrains/yaml/meta/impl/YamlDuplicatedKeysInspectionBase.java
rename to plugins/yaml/src/org/jetbrains/yaml/inspections/YAMLDuplicatedKeysInspection.java
index cbac7ba6d29f..acd66c6a9fc7 100644
--- a/plugins/yaml/src/org/jetbrains/yaml/meta/impl/YamlDuplicatedKeysInspectionBase.java
+++ b/plugins/yaml/src/org/jetbrains/yaml/inspections/YAMLDuplicatedKeysInspection.java
@@ -1,43 +1,37 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
-package org.jetbrains.yaml.meta.impl;
+package org.jetbrains.yaml.inspections;
import com.intellij.codeInspection.*;
-import com.intellij.openapi.progress.ProgressIndicatorProvider;
import com.intellij.openapi.project.Project;
-import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.SmartPointerManager;
import com.intellij.psi.SmartPsiElementPointer;
import com.intellij.util.containers.MultiMap;
-import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.yaml.YAMLBundle;
import org.jetbrains.yaml.psi.YAMLKeyValue;
import org.jetbrains.yaml.psi.YAMLMapping;
+import org.jetbrains.yaml.psi.YamlPsiElementVisitor;
import java.util.Collection;
import java.util.Map;
-@ApiStatus.Experimental
-public abstract class YamlDuplicatedKeysInspectionBase extends LocalInspectionTool {
-
+public class YAMLDuplicatedKeysInspection extends LocalInspectionTool {
@NotNull
@Override
public final PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
- return new PsiElementVisitor() {
+ return new YamlPsiElementVisitor() {
@Override
- public void visitElement(PsiElement element) {
- ProgressIndicatorProvider.checkCanceled();
-
- if (!(element instanceof YAMLMapping)) {
- return;
- }
-
+ public void visitMapping(@NotNull YAMLMapping mapping) {
MultiMap occurrences = new MultiMap<>();
- for (YAMLKeyValue keyValue : ((YAMLMapping)element).getKeyValues()) {
+ for (YAMLKeyValue keyValue : mapping.getKeyValues()) {
final String keyName = keyValue.getKeyText().trim();
+ // http://yaml.org/type/merge.html
+ if (keyName.equals("<<")) {
+ continue;
+ }
if (!keyName.isEmpty()) {
occurrences.putValue(keyName, keyValue);
}
@@ -48,7 +42,7 @@ public abstract class YamlDuplicatedKeysInspectionBase extends LocalInspectionTo
entry.getValue().forEach((duplicatedKey) -> {
assert duplicatedKey.getKey() != null;
holder.registerProblem(duplicatedKey.getKey(),
- YAMLBundle.message("YamlDuplicatedKeysInspectionBase.duplicated.key", entry.getKey()),
+ YAMLBundle.message("YAMLDuplicatedKeysInspection.duplicated.key", entry.getKey()),
ProblemHighlightType.GENERIC_ERROR_OR_WARNING, new RemoveDuplicatedKeyQuickFix(duplicatedKey));
});
}
@@ -68,7 +62,7 @@ public abstract class YamlDuplicatedKeysInspectionBase extends LocalInspectionTo
@NotNull
@Override
public String getFamilyName() {
- return YAMLBundle.message("YamlDuplicatedKeysInspectionBase.remove.key.quickfix.name");
+ return YAMLBundle.message("YAMLDuplicatedKeysInspection.remove.key.quickfix.name");
}
@Override
diff --git a/plugins/yaml/testSrc/org/jetbrains/yaml/highlighting/YAMLHighlightingTest.java b/plugins/yaml/testSrc/org/jetbrains/yaml/highlighting/YAMLHighlightingTest.java
index a7b31ee700cf..3d2adcba47d5 100644
--- a/plugins/yaml/testSrc/org/jetbrains/yaml/highlighting/YAMLHighlightingTest.java
+++ b/plugins/yaml/testSrc/org/jetbrains/yaml/highlighting/YAMLHighlightingTest.java
@@ -3,6 +3,7 @@ package org.jetbrains.yaml.highlighting;
import com.intellij.openapi.application.ex.PathManagerEx;
import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase;
+import org.jetbrains.yaml.inspections.YAMLDuplicatedKeysInspection;
import org.jetbrains.yaml.inspections.YAMLRecursiveAliasInspection;
import org.jetbrains.yaml.inspections.YAMLUnresolvedAliasInspection;
@@ -34,6 +35,11 @@ public class YAMLHighlightingTest extends LightPlatformCodeInsightFixtureTestCas
doTest();
}
+ public void testDuplicatedKeys() {
+ myFixture.enableInspections(YAMLDuplicatedKeysInspection.class);
+ doTest();
+ }
+
private void doTest() {
myFixture.testHighlighting(true, false, false, getTestName(true) + ".yml");
}
diff --git a/plugins/yaml/testSrc/org/jetbrains/yaml/highlighting/data/duplicatedKeys.yml b/plugins/yaml/testSrc/org/jetbrains/yaml/highlighting/data/duplicatedKeys.yml
new file mode 100644
index 000000000000..0778da02ae0e
--- /dev/null
+++ b/plugins/yaml/testSrc/org/jetbrains/yaml/highlighting/data/duplicatedKeys.yml
@@ -0,0 +1,10 @@
+map1: &an1
+ testKey: hello
+ anotherKey: world
+ testKey: duplicate
+map2: &an2
+ someKey: some value from map 2
+map3:
+ <<: *an1
+ <<: *an2
+ ownKey: some value from map 3
\ No newline at end of file
diff --git a/plugins/yaml/testSrc/org/jetbrains/yaml/intention/YAMLIntentionTest.java b/plugins/yaml/testSrc/org/jetbrains/yaml/intention/YAMLIntentionTest.java
new file mode 100644
index 000000000000..d07f31d7b8c7
--- /dev/null
+++ b/plugins/yaml/testSrc/org/jetbrains/yaml/intention/YAMLIntentionTest.java
@@ -0,0 +1,41 @@
+// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
+package org.jetbrains.yaml.intention;
+
+import com.intellij.codeInsight.intention.IntentionAction;
+import com.intellij.openapi.application.ex.PathManagerEx;
+import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.yaml.inspections.YAMLDuplicatedKeysInspection;
+
+import java.util.List;
+import java.util.Optional;
+
+public class YAMLIntentionTest extends LightPlatformCodeInsightFixtureTestCase {
+ @Override
+ protected String getTestDataPath() {
+ return PathManagerEx.getCommunityHomePath() + "/plugins/yaml/testSrc/org/jetbrains/yaml/intention/data/";
+ }
+
+ public void testDeleteDuplicatedKey() {
+ myFixture.enableInspections(YAMLDuplicatedKeysInspection.class);
+ doTest("Remove key");
+ }
+
+ @SuppressWarnings("SameParameterValue")
+ private void doTest(@NotNull String intentionName) {
+ String testName = getTestName(true);
+ myFixture.configureByFile(testName + ".yml");
+ IntentionAction quickFix = findIntention(intentionName);
+ myFixture.launchAction(quickFix);
+ myFixture.checkResultByFile(testName + ".txt");
+ }
+
+ @NotNull
+ private IntentionAction findIntention(@NotNull String name) {
+ List intentions = myFixture.getAvailableIntentions();
+ Optional intention =
+ intentions.stream().filter(it -> it.getText().contains(name)).findFirst();
+ assert intention.isPresent();
+ return intention.get();
+ }
+}
diff --git a/plugins/yaml/testSrc/org/jetbrains/yaml/intention/data/deleteDuplicatedKey.txt b/plugins/yaml/testSrc/org/jetbrains/yaml/intention/data/deleteDuplicatedKey.txt
new file mode 100644
index 000000000000..a9761163150f
--- /dev/null
+++ b/plugins/yaml/testSrc/org/jetbrains/yaml/intention/data/deleteDuplicatedKey.txt
@@ -0,0 +1,67 @@
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+ annotations:
+ deployment.kubernetes.io/revision: "1"
+ labels:
+ app: psi-agent
+ version: 2017.2.2057
+ name: psi-agent
+ namespace: default
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: psi-agent
+ version: 2017.2.2057
+ strategy:
+ rollingUpdate:
+ maxSurge: abc
+ maxUnavailable: 1
+ type: RollingUpdate
+ template:
+ metadata:
+ labels:
+ app: psi-agent
+ version: 2017.2.2057
+ name: psi-agent
+ spec:
+ containers:
+ - image: jetbrains/upsource-psi-agent:2017.2.2057
+ imagePullPolicy: IfNotPresent
+ name: psi-agent
+ resources: {}
+ securityContext:
+ privileged: false
+ terminationMessagePath: /dev/termination-log
+ terminationMessagePolicy: File
+ env:
+ - name: HUB_URL
+ value: http://hub-service:8088/hub
+ - name: UPSOURCE_SERVICE_ID
+ value: d574afce-a315-4bb2-b2a7-6c726b3e1080
+ - name: UPSOURCE_SERVICE_SECRET
+ value: lxnbMouMvGId
+ - name: UPSOURCE_URL
+ value: http://35.195.40.139
+ - name: UPSOURCE_SERVICE_PORT
+ value: "10061"
+ - name: UPSOURCE_STATISTICS_UPLOAD
+ value: "false"
+ - name: CASSANDRA_HOSTS
+ value: cassandra
+ - name: CASSANDRA_PORT
+ value: "9042"
+ - name: UPSOURCE_DATABASE
+ value: datastax
+ - name: UPSOURCE_SUBNET
+ value: 10.60.0.0/16
+ - name: UPSOURCE_PSI_BROKER_PORT
+ value: "11111"
+ - name: UPSOURCE_PSI_BROKER_HOST
+ value: psi-broker
+ dnsPolicy: ClusterFirst
+ restartPolicy: Always
+ schedulerName: default-scheduler
+ securityContext: {}
+ terminationGracePeriodSeconds: 30
diff --git a/plugins/yaml/testSrc/org/jetbrains/yaml/intention/data/deleteDuplicatedKey.yml b/plugins/yaml/testSrc/org/jetbrains/yaml/intention/data/deleteDuplicatedKey.yml
new file mode 100644
index 000000000000..211da8132303
--- /dev/null
+++ b/plugins/yaml/testSrc/org/jetbrains/yaml/intention/data/deleteDuplicatedKey.yml
@@ -0,0 +1,68 @@
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+ annotations:
+ deployment.kubernetes.io/revision: "1"
+ labels:
+ app: psi-agent
+ version: 2017.2.2057
+ name: psi-agent
+ namespace: default
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: psi-agent
+ version: 2017.2.2057
+ strategy:
+ rollingUpdate:
+ maxSurge: abc
+ maxUnavailable: 1
+ type: RollingUpdate
+ replicas : 2
+ template:
+ metadata:
+ labels:
+ app: psi-agent
+ version: 2017.2.2057
+ name: psi-agent
+ spec:
+ containers:
+ - image: jetbrains/upsource-psi-agent:2017.2.2057
+ imagePullPolicy: IfNotPresent
+ name: psi-agent
+ resources: {}
+ securityContext:
+ privileged: false
+ terminationMessagePath: /dev/termination-log
+ terminationMessagePolicy: File
+ env:
+ - name: HUB_URL
+ value: http://hub-service:8088/hub
+ - name: UPSOURCE_SERVICE_ID
+ value: d574afce-a315-4bb2-b2a7-6c726b3e1080
+ - name: UPSOURCE_SERVICE_SECRET
+ value: lxnbMouMvGId
+ - name: UPSOURCE_URL
+ value: http://35.195.40.139
+ - name: UPSOURCE_SERVICE_PORT
+ value: "10061"
+ - name: UPSOURCE_STATISTICS_UPLOAD
+ value: "false"
+ - name: CASSANDRA_HOSTS
+ value: cassandra
+ - name: CASSANDRA_PORT
+ value: "9042"
+ - name: UPSOURCE_DATABASE
+ value: datastax
+ - name: UPSOURCE_SUBNET
+ value: 10.60.0.0/16
+ - name: UPSOURCE_PSI_BROKER_PORT
+ value: "11111"
+ - name: UPSOURCE_PSI_BROKER_HOST
+ value: psi-broker
+ dnsPolicy: ClusterFirst
+ restartPolicy: Always
+ schedulerName: default-scheduler
+ securityContext: {}
+ terminationGracePeriodSeconds: 30