Repository: aws-cloudformation/cfn-lint

Issue:
Identify capitalisation errors in property names
*cfn-lint version: 0.62.0

* Feature request:
  * It can be hard to identify captalisation errors in cfn property names. It would be useful if cfn-lint would call out these things. An example is:

```
TransformFunction:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        Zipfile: |
```

In this case, Zipfile should be ZipFile... This took me several hours to identify :/

The cfn-lint did pick up the error as:

E3002 Invalid Property Resources/TransformFunction/Properties/Code/Zipfile

Feature request is to call it out more clearly as a capitalisation error and that it might be ZipFile.

This is not the first time this has happened. There are a number of Property names that have unusual capitalisations.



Discussion:

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
@@ -2,6 +2,7 @@
 Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 SPDX-License-Identifier: MIT-0
 """
+from difflib import SequenceMatcher
 from cfnlint.rules import CloudFormationLintRule
 from cfnlint.rules import RuleMatch
 import cfnlint.helpers
@@ -264,8 +265,16 @@ def propertycheck(self, text, proptype, parenttype, resourcename, path, root):
                 elif len(text) == 1 and prop in 'Ref' and text.get(prop) == 'AWS::NoValue':
                     pass
                 elif not supports_additional_properties:
-                    message = 'Invalid Property %s' % ('/'.join(map(str, proppath)))
-                    matches.append(RuleMatch(proppath, message))
+                    close_match = False
+                    for key in resourcespec.keys():
+                        if SequenceMatcher(a=prop,b=key).ratio() > 0.8:
+                            message = 'Invalid Property %s. Did you mean %s?' % ('/'.join(map(str, proppath)), key)
+                            matches.append(RuleMatch(proppath, message))
+                            close_match = True
+                            break
+                    if not close_match:
+                        message = 'Invalid Property %s' % ('/'.join(map(str, proppath)))
+                        matches.append(RuleMatch(proppath, message))
             else:
                 if 'Type' in resourcespec[prop]:
                     if resourcespec[prop]['Type'] == 'List':


Test patch:
diff --git a/test/integration/test_directives.py b/test/integration/test_directives.py
--- a/test/integration/test_directives.py
+++ b/test/integration/test_directives.py
@@ -36,7 +36,7 @@ class TestDirectives(BaseCliTestCase):
                             'LineNumber': 17
                         }
                     },
-                    'Message': 'Invalid Property Resources/myBucketFail/Properties/BucketName1',
+                    'Message': 'Invalid Property Resources/myBucketFail/Properties/BucketName1. Did you mean BucketName?',
                     'Rule': {
                         'Description': 'Making sure that resources properties are properly configured',
                         'Id': 'E3002',
diff --git a/test/integration/test_mandatory_checks.py b/test/integration/test_mandatory_checks.py
--- a/test/integration/test_mandatory_checks.py
+++ b/test/integration/test_mandatory_checks.py
@@ -26,7 +26,7 @@ class TestDirectives(BaseCliTestCase):
                         ],
                         "Start": {"ColumnNumber": 7, "LineNumber": 17},
                     },
-                    "Message": "Invalid Property Resources/myBucketFail/Properties/BucketName1",
+                    "Message": "Invalid Property Resources/myBucketFail/Properties/BucketName1. Did you mean BucketName?",
                     "Rule": {
                         "Description": "Making sure that resources properties are properly configured",
                         "Id": "E3002",
@@ -131,7 +131,7 @@ class TestDirectives(BaseCliTestCase):
                         ],
                         'Start': {'ColumnNumber': 7, 'LineNumber': 13}
                     },
-                    'Message': 'Invalid Property Resources/myBucketPass/Properties/BucketName1',
+                    'Message': 'Invalid Property Resources/myBucketPass/Properties/BucketName1. Did you mean BucketName?',
                     'Rule': {
                         'Description': 'Making sure that resources properties are properly configured',
                         'Id': 'E3002',