Repository: aws-cloudformation/cfn-lint

Issue:
!If seems to break rule E3002 for lists
`!If` seems to break the type checker for lists. It seems to work with other types like boolean, strings, and `!Ref 'AWS::NoValue'`.

I get the following findings using v0.3.0:
```
E3002 Property AttributeDefinitions should be of type List for resource Table

E3002 Property KeySchema should be of type List for resource Table
```

For this template: 
```
Conditions:
  HasSortKey: !Not [!Equals [!Ref SortKeyName, '']]
  HasTableName: !Not [!Equals [!Ref TableName, '']]
  HasAwsManagedEncryption: !Equals [!Ref Encryption, aws]
Resources:
  Table:
    Type: 'AWS::DynamoDB::Table'
    Properties:
      TableName: !If [HasTableName, !Ref TableName, !Ref 'AWS::NoValue']
      AttributeDefinitions: !If
      - HasSortKey
      - - AttributeName: !Ref PartitionKeyName
          AttributeType: !Ref PartitionKeyType
        - AttributeName: !Ref SortKeyName
          AttributeType: !Ref SortKeyType
      - - AttributeName: !Ref PartitionKeyName
          AttributeType: !Ref PartitionKeyType
      KeySchema: !If
      - HasSortKey
      - - AttributeName: !Ref PartitionKeyName
          KeyType: HASH
        - AttributeName: !Ref SortKeyName
          KeyType: RANGE
      - - AttributeName: !Ref PartitionKeyName
          KeyType: HASH
      ProvisionedThroughput:
        ReadCapacityUnits: !Ref MinReadCapacityUnits
        WriteCapacityUnits: !Ref MinWriteCapacityUnits
      SSESpecification:
        SSEEnabled: !If [HasAwsManagedEncryption, true, false]
```




Discussion:
Good find! 👍 

It seems that conditionals and lists don't like each other (yet). Let me check

Resolving patch:
diff --git a/src/cfnlint/rules/resources/properties/Properties.py b/src/cfnlint/rules/resources/properties/Properties.py
--- a/src/cfnlint/rules/resources/properties/Properties.py
+++ b/src/cfnlint/rules/resources/properties/Properties.py
@@ -73,6 +73,55 @@ def primitivetypecheck(self, value, primtype, proppath):
 
         return matches
 
+    def check_list_for_condition(self, text, prop, parenttype, resourcename, propspec, path):
+        """Checks lists that are a dict for conditions"""
+        matches = list()
+        if len(text[prop]) == 1:
+            for sub_key, sub_value in text[prop].items():
+                if sub_key in cfnlint.helpers.CONDITION_FUNCTIONS:
+                    if len(sub_value) == 3:
+                        if isinstance(sub_value[1], list):
+                            for index, item in enumerate(sub_value[1]):
+                                arrproppath = path[:]
+
+                                arrproppath.append(index)
+                                matches.extend(self.propertycheck(
+                                    item, propspec['ItemType'],
+                                    parenttype, resourcename, arrproppath, False))
+                        else:
+                            message = 'Property {0} should be of type List for resource {1}'
+                            matches.append(
+                                RuleMatch(
+                                    path,
+                                    message.format(prop, resourcename)))
+
+                        if isinstance(sub_value[2], list):
+                            for index, item in enumerate(sub_value[2]):
+                                arrproppath = path[:]
+
+                                arrproppath.append(index)
+                                matches.extend(self.propertycheck(
+                                    item, propspec['ItemType'],
+                                    parenttype, resourcename, arrproppath, False))
+                        else:
+                            message = 'Property {0} should be of type List for resource {1} at {2}'
+                            matches.append(
+                                RuleMatch(
+                                    path + [2],
+                                    message.format(prop, resourcename, ('/'.join(str(x) for x in path)))))
+
+                    else:
+                        message = 'Invalid !If condition specified at %s' % ('/'.join(map(str, path)))
+                        matches.append(RuleMatch(path, message))
+                else:
+                    message = 'Property is an object instead of List at %s' % ('/'.join(map(str, path)))
+                    matches.append(RuleMatch(path, message))
+        else:
+            message = 'Property is an object instead of List at %s' % ('/'.join(map(str, path)))
+            matches.append(RuleMatch(path, message))
+
+        return matches
+
     def propertycheck(self, text, proptype, parenttype, resourcename, path, root):
         """Check individual properties"""
 
@@ -124,6 +173,12 @@ def propertycheck(self, text, proptype, parenttype, resourcename, path, root):
                                     matches.extend(self.propertycheck(
                                         item, resourcespec[prop]['ItemType'],
                                         parenttype, resourcename, arrproppath, False))
+                            elif (isinstance(text[prop], dict)):
+                                # A list can be be specific as a Conditional
+                                matches.extend(
+                                    self.check_list_for_condition(
+                                        text, prop, parenttype, resourcename, resourcespec[prop], proppath)
+                                )
                             else:
                                 message = 'Property {0} should be of type List for resource {1}'
                                 matches.append(


Test patch:
diff --git a/test/rules/resources/properties/test_properties.py b/test/rules/resources/properties/test_properties.py
--- a/test/rules/resources/properties/test_properties.py
+++ b/test/rules/resources/properties/test_properties.py
@@ -39,7 +39,7 @@ def test_file_negative(self):
 
     def test_file_negative_2(self):
         """Failure test"""
-        self.helper_file_negative('templates/bad/object_should_be_list.yaml', 2)
+        self.helper_file_negative('templates/bad/object_should_be_list.yaml', 4)
 
     def test_file_negative_3(self):
         """Failure test"""
diff --git a/test/templates/bad/object_should_be_list.yaml b/test/templates/bad/object_should_be_list.yaml
--- a/test/templates/bad/object_should_be_list.yaml
+++ b/test/templates/bad/object_should_be_list.yaml
@@ -651,3 +651,23 @@ Resources:
       Cooldown: '60'
       ScalingAdjustment: '1'
     Type: AWS::AutoScaling::ScalingPolicy
+  Table:
+    Type: 'AWS::DynamoDB::Table'
+    Properties:
+      TableName: !If [HasTableName, !Ref TableName, !Ref 'AWS::NoValue']
+      AttributeDefinitions: !If
+      - HasSortKey
+      - - AttributeName: !Ref PartitionKeyName
+          AttributeType: !Ref PartitionKeyType
+        - AttributeName: !Ref SortKeyName
+          AttributeType: !Ref SortKeyType
+      - "String"
+      KeySchema: !If
+      - HasSortKey
+      - - AttributeName: !Ref PartitionKeyName
+          KeyType: HASH
+        - AttributeName: !Ref SortKeyName
+          KeyType: RANGE
+      - - AttributeName: !Ref PartitionKeyName
+          KeyType: HASH
+        - "String2"
diff --git a/test/templates/good/resource_properties.yaml b/test/templates/good/resource_properties.yaml
--- a/test/templates/good/resource_properties.yaml
+++ b/test/templates/good/resource_properties.yaml
@@ -561,3 +561,25 @@ Resources:
       Parameters:
         sql_mode: "NO_AUTO_CREATE_USER"
         another_param: "ANOTHER_PARAMETER"
+  Table:
+    Type: 'AWS::DynamoDB::Table'
+    Properties:
+      TableName: !If [HasTableName, !Ref TableName, !Ref 'AWS::NoValue']
+      AttributeDefinitions: !If
+      - HasSortKey
+      - - AttributeName: !Ref PartitionKeyName
+          AttributeType: !Ref PartitionKeyType
+        - AttributeName: !Ref SortKeyName
+          AttributeType: !Ref SortKeyType
+      - - AttributeName: !Ref PartitionKeyName
+          AttributeType: !Ref PartitionKeyType
+      KeySchema: !If
+      - HasSortKey
+      - - AttributeName: !Ref PartitionKeyName
+          KeyType: HASH
+        - AttributeName: !Ref SortKeyName
+          KeyType: RANGE
+      - - AttributeName: !Ref PartitionKeyName
+          KeyType: HASH
+        - AttributeName: !Ref SortKeyName
+          KeyType: RANGE