linter: duplicate-object-keys
diff --git a/linter/src/main/java/org/apache/royale/linter/LINTER.java b/linter/src/main/java/org/apache/royale/linter/LINTER.java
index ba4de97..03d6c05 100644
--- a/linter/src/main/java/org/apache/royale/linter/LINTER.java
+++ b/linter/src/main/java/org/apache/royale/linter/LINTER.java
@@ -54,6 +54,7 @@
import org.apache.royale.linter.rules.ConstantNameRule;
import org.apache.royale.linter.rules.ConstructorDispatchEventRule;
import org.apache.royale.linter.rules.ConstructorReturnTypeRule;
+import org.apache.royale.linter.rules.DuplicateObjectKeysRule;
import org.apache.royale.linter.rules.DynamicClassRule;
import org.apache.royale.linter.rules.EmptyCommentRule;
import org.apache.royale.linter.rules.EmptyFunctionBodyRule;
@@ -262,6 +263,9 @@
if (configuration.getConstructorDispatchEvent()) {
rules.add(new ConstructorDispatchEventRule());
}
+ if (configuration.getDuplicateObjectKeys()) {
+ rules.add(new DuplicateObjectKeysRule());
+ }
if (configuration.getDynamicClass()) {
rules.add(new DynamicClassRule());
}
diff --git a/linter/src/main/java/org/apache/royale/linter/config/Configuration.java b/linter/src/main/java/org/apache/royale/linter/config/Configuration.java
index 5d19751..7aac037 100644
--- a/linter/src/main/java/org/apache/royale/linter/config/Configuration.java
+++ b/linter/src/main/java/org/apache/royale/linter/config/Configuration.java
@@ -280,6 +280,22 @@
}
//
+ // 'duplicate-object-keys' option
+ //
+
+ private boolean duplicateObjectKeys = false;
+
+ public boolean getDuplicateObjectKeys() {
+ return duplicateObjectKeys;
+ }
+
+ @Config
+ @Mapping("duplicate-object-keys")
+ public void setDuplicateObjectKeys(ConfigurationValue cv, boolean b) {
+ this.duplicateObjectKeys = b;
+ }
+
+ //
// 'constructor-dispatch-event' option
//
diff --git a/linter/src/main/java/org/apache/royale/linter/rules/DuplicateObjectKeysRule.java b/linter/src/main/java/org/apache/royale/linter/rules/DuplicateObjectKeysRule.java
new file mode 100644
index 0000000..d1fef4d
--- /dev/null
+++ b/linter/src/main/java/org/apache/royale/linter/rules/DuplicateObjectKeysRule.java
@@ -0,0 +1,104 @@
+////////////////////////////////////////////////////////////////////////////////
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////////
+
+package org.apache.royale.linter.rules;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.royale.compiler.internal.tree.as.ContainerNode;
+import org.apache.royale.compiler.problems.CompilerProblem;
+import org.apache.royale.compiler.problems.ICompilerProblem;
+import org.apache.royale.compiler.tree.ASTNodeID;
+import org.apache.royale.compiler.tree.as.IASNode;
+import org.apache.royale.compiler.tree.as.IExpressionNode;
+import org.apache.royale.compiler.tree.as.IIdentifierNode;
+import org.apache.royale.compiler.tree.as.ILiteralContainerNode;
+import org.apache.royale.compiler.tree.as.ILiteralNode;
+import org.apache.royale.compiler.tree.as.ILiteralNode.LiteralType;
+import org.apache.royale.compiler.tree.as.IObjectLiteralValuePairNode;
+import org.apache.royale.linter.LinterRule;
+import org.apache.royale.linter.NodeVisitor;
+import org.apache.royale.linter.TokenQuery;
+
+/**
+ * Check that each key in an object literal is unique.
+ */
+public class DuplicateObjectKeysRule extends LinterRule {
+ @Override
+ public Map<ASTNodeID, NodeVisitor> getNodeVisitors() {
+ Map<ASTNodeID, NodeVisitor> result = new HashMap<>();
+ result.put(ASTNodeID.ObjectLiteralExpressionID, (node, tokenQuery, problems) -> {
+ checkLiteralContainerNode((ILiteralContainerNode) node, tokenQuery, problems);
+ });
+ return result;
+ }
+
+ private void checkLiteralContainerNode(ILiteralContainerNode objectLiteralNode, TokenQuery tokenQuery, Collection<ICompilerProblem> problems) {
+ if (!LiteralType.OBJECT.equals(objectLiteralNode.getLiteralType())) {
+ return;
+ }
+ ContainerNode contentsNode = objectLiteralNode.getContentsNode();
+ if (contentsNode == null) {
+ return;
+ }
+ Set<String> keyNames = new HashSet<>();
+ for (int i = 0; i < contentsNode.getChildCount(); i++) {
+ IASNode child = contentsNode.getChild(i);
+ if (!(child instanceof IObjectLiteralValuePairNode)) {
+ continue;
+ }
+ String keyName = null;
+ IObjectLiteralValuePairNode valuePairNode = (IObjectLiteralValuePairNode) child;
+ IExpressionNode nameNode = valuePairNode.getNameNode();
+ if (nameNode instanceof IIdentifierNode) {
+ IIdentifierNode identifierNode = (IIdentifierNode) nameNode;
+ keyName = identifierNode.getName();
+ } else if (nameNode instanceof ILiteralNode) {
+ ILiteralNode literalNode = (ILiteralNode) nameNode;
+ if (!LiteralType.STRING.equals(literalNode.getLiteralType())) {
+ continue;
+ }
+ keyName = literalNode.getValue();
+ }
+ if (keyName != null) {
+ if (keyNames.contains(keyName)) {
+ problems.add(new DuplicateObjectKeysLinterProblem(nameNode, keyName));
+ } else {
+ keyNames.add(keyName);
+ }
+ }
+ }
+ }
+
+ public static class DuplicateObjectKeysLinterProblem extends CompilerProblem {
+ public static final String DESCRIPTION = "Object literal contains duplicate key '${keyName}'";
+
+ public DuplicateObjectKeysLinterProblem(IExpressionNode node, String keyName)
+ {
+ super(node);
+ this.keyName = keyName;
+ }
+
+ public String keyName;
+ }
+}