We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Errors when using Fn::Transform
*cfn-lint version: (`0.20.2`)*

Given the following valid template, I see 2 errors:

```yaml
Parameters:
  ImageId:
    Type: AWS::EC2::Image::Id
  InstanceType:
    Type: String

Resources:
  LaunchConfiguration:
    Type: AWS::AutoScaling::LaunchConfiguration
    Properties:
      ImageId: !Ref ImageId
      InstanceType: !Ref InstanceType
      UserData:
        'Fn::Base64':
          'Fn::Sub':
            'Fn::Transform': # Returns a string that contains Fn::Sub tokens like ${AWS::Region}
              Name: DynamicUserData
```
* `E1019 Sub should be a string or array of 2 items for Resources/LaunchConfiguration/Properties/UserData/Fn::Base64/Fn::Sub` - `Fn::Transform` can return a string or a template, so `Fn::Sub` should be forgiving of it.
* `E0002 Unknown exception while processing rule W2001: expected string or bytes-like object` Same root cause, but fails in a different way due to assumption that it complies with E1019 in a specific way.




--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/cfnlint/rules/functions/Sub.py`
Content:
```
1 """
2   Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 
4   Permission is hereby granted, free of charge, to any person obtaining a copy of this
5   software and associated documentation files (the "Software"), to deal in the Software
6   without restriction, including without limitation the rights to use, copy, modify,
7   merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
8   permit persons to whom the Software is furnished to do so.
9 
10   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
11   INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
12   PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
13   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
14   OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
15   SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16 """
17 import six
18 from cfnlint import CloudFormationLintRule
19 from cfnlint import RuleMatch
20 
21 
22 class Sub(CloudFormationLintRule):
23     """Check if Sub values are correct"""
24     id = 'E1019'
25     shortdesc = 'Sub validation of parameters'
26     description = 'Making sure the sub function is properly configured'
27     source_url = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html'
28     tags = ['functions', 'sub']
29 
30     def _test_string(self, cfn, sub_string, parameters, tree):
31         """Test if a string has appropriate parameters"""
32 
33         matches = []
34         string_params = cfn.get_sub_parameters(sub_string)
35 
36         for string_param in string_params:
37             if isinstance(string_param, (six.string_types)):
38                 matches.extend(self._test_parameter(string_param, cfn, parameters, tree))
39 
40         return matches
41 
42     def _get_parameters(self, cfn):
43         """Get all Parameter Names"""
44         results = {}
45         parameters = cfn.template.get('Parameters', {})
46         if isinstance(parameters, dict):
47             for param_name, param_values in parameters.items():
48                 # This rule isn't here to check the Types but we need
49                 # something valid if it doesn't exist
50                 results[param_name] = param_values.get('Type', 'String')
51 
52         return results
53 
54     def _test_parameters(self, parameters, cfn, tree):
55         """Check parameters for appropriate configuration"""
56 
57         supported_functions = [
58             'Fn::Base64',
59             'Fn::FindInMap',
60             'Fn::GetAtt',
61             'Fn::GetAZs',
62             'Fn::ImportValue',
63             'Fn::If',
64             'Fn::Join',
65             'Fn::Select',
66             'Fn::Sub',
67             'Ref'
68         ]
69 
70         matches = []
71         for parameter_name, parameter_value_obj in parameters.items():
72             param_tree = tree[:] + [parameter_name]
73             if isinstance(parameter_value_obj, dict):
74                 if len(parameter_value_obj) == 1:
75                     for key, value in parameter_value_obj.items():
76                         if key not in supported_functions:
77                             message = 'Sub parameter should use a valid function for {0}'
78                             matches.append(RuleMatch(
79                                 param_tree, message.format('/'.join(map(str, tree)))))
80                         elif key in ['Ref']:
81                             matches.extend(self._test_parameter(value, cfn, {}, tree))
82                         elif key in ['Fn::GetAtt']:
83                             if isinstance(value, list):
84                                 matches.extend(self._test_parameter('.'.join(value), cfn, {}, tree))
85                             elif isinstance(value, six.string_types):
86                                 matches.extend(self._test_parameter(value, cfn, {}, tree))
87                 else:
88                     message = 'Sub parameter should be an object of 1 for {0}'
89                     matches.append(RuleMatch(
90                         param_tree, message.format('/'.join(map(str, tree)))))
91             elif not isinstance(parameter_value_obj, six.string_types):
92                 message = 'Sub parameter should be an object of 1 or string for {0}'
93                 matches.append(RuleMatch(
94                     param_tree, message.format('/'.join(map(str, tree)))))
95 
96         return matches
97 
98     def _test_parameter(self, parameter, cfn, parameters, tree):
99         """ Test a parameter """
100 
101         matches = []
102         get_atts = cfn.get_valid_getatts()
103 
104         valid_pseudo_params = [
105             'AWS::Region',
106             'AWS::StackName',
107             'AWS::URLSuffix',
108             'AWS::StackId',
109             'AWS::Region',
110             'AWS::Partition',
111             'AWS::NotificationARNs',
112             'AWS::AccountId'
113         ]
114 
115         odd_list_params = [
116             'CommaDelimitedList',
117             'AWS::SSM::Parameter::Value<CommaDelimitedList>',
118         ]
119 
120         valid_params = valid_pseudo_params
121         valid_params.extend(cfn.get_resource_names())
122         template_parameters = self._get_parameters(cfn)
123 
124         for key, _ in parameters.items():
125             valid_params.append(key)
126 
127         if parameter not in valid_params:
128             found = False
129             if parameter in template_parameters:
130                 found = True
131                 if (
132                         template_parameters.get(parameter) in odd_list_params or
133                         template_parameters.get(parameter).startswith('AWS::SSM::Parameter::Value<List') or
134                         template_parameters.get(parameter).startswith('List')):
135                     message = 'Fn::Sub cannot use list {0} at {1}'
136                     matches.append(RuleMatch(
137                         tree, message.format(parameter, '/'.join(map(str, tree)))))
138             for resource, attributes in get_atts.items():
139                 for attribute_name, attribute_values in attributes.items():
140                     if resource == parameter.split('.')[0] and attribute_name == '*':
141                         if attribute_values.get('Type') == 'List':
142                             message = 'Fn::Sub cannot use list {0} at {1}'
143                             matches.append(RuleMatch(
144                                 tree, message.format(parameter, '/'.join(map(str, tree)))))
145                         found = True
146                     elif (resource == parameter.split('.')[0] and
147                           attribute_name == '.'.join(parameter.split('.')[1:])):
148                         if attribute_values.get('Type') == 'List':
149                             message = 'Fn::Sub cannot use list {0} at {1}'
150                             matches.append(RuleMatch(
151                                 tree, message.format(parameter, '/'.join(map(str, tree)))))
152                         found = True
153             if not found:
154                 message = 'Parameter {0} for Fn::Sub not found at {1}'
155                 matches.append(RuleMatch(
156                     tree, message.format(parameter, '/'.join(map(str, tree)))))
157 
158         return matches
159 
160     def match(self, cfn):
161         """Check CloudFormation Join"""
162 
163         matches = []
164 
165         sub_objs = cfn.search_deep_keys('Fn::Sub')
166 
167         for sub_obj in sub_objs:
168             sub_value_obj = sub_obj[-1]
169             tree = sub_obj[:-1]
170             if isinstance(sub_value_obj, six.string_types):
171                 matches.extend(self._test_string(cfn, sub_value_obj, {}, tree))
172             elif isinstance(sub_value_obj, list):
173                 if len(sub_value_obj) == 2:
174                     sub_string = sub_value_obj[0]
175                     parameters = sub_value_obj[1]
176                     if not isinstance(sub_string, six.string_types):
177                         message = 'Subs first element should be of type string for {0}'
178                         matches.append(RuleMatch(
179                             tree + [0], message.format('/'.join(map(str, tree)))))
180                     if not isinstance(parameters, dict):
181                         message = 'Subs second element should be an object for {0}'
182                         matches.append(RuleMatch(
183                             tree + [1], message.format('/'.join(map(str, tree)))))
184                     else:
185                         matches.extend(self._test_string(cfn, sub_string, parameters, tree + [0]))
186                         matches.extend(self._test_parameters(parameters, cfn, tree))
187                 else:
188                     message = 'Sub should be an array of 2 for {0}'
189                     matches.append(RuleMatch(
190                         tree, message.format('/'.join(map(str, tree)))))
191             else:
192                 message = 'Sub should be a string or array of 2 items for {0}'
193                 matches.append(RuleMatch(
194                     tree, message.format('/'.join(map(str, tree)))))
195 
196         return matches
197
```

Path: `src/cfnlint/rules/parameters/Used.py`
Content:
```
1 """
2   Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 
4   Permission is hereby granted, free of charge, to any person obtaining a copy of this
5   software and associated documentation files (the "Software"), to deal in the Software
6   without restriction, including without limitation the rights to use, copy, modify,
7   merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
8   permit persons to whom the Software is furnished to do so.
9 
10   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
11   INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
12   PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
13   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
14   OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
15   SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16 """
17 from __future__ import unicode_literals
18 import re
19 import six
20 from cfnlint import CloudFormationLintRule
21 from cfnlint import RuleMatch
22 
23 
24 class Used(CloudFormationLintRule):
25     """Check if Parameters are used"""
26     id = 'W2001'
27     shortdesc = 'Check if Parameters are Used'
28     description = 'Making sure the parameters defined are used'
29     source_url = 'https://github.com/aws-cloudformation/cfn-python-lint'
30     tags = ['parameters']
31 
32     def searchstring(self, string, parameter):
33         """Search string for tokenized fields"""
34         regex = re.compile(r'\${(%s)}' % parameter)
35         return regex.findall(string)
36 
37     def isparaminref(self, subs, parameter):
38         """Search sub strings for parameters"""
39         for sub in subs:
40             if isinstance(sub, (six.text_type, six.string_types)):
41                 if self.searchstring(sub, parameter):
42                     return True
43 
44         return False
45 
46     def match(self, cfn):
47         """Check CloudFormation Parameters"""
48 
49         matches = []
50 
51         reftrees = cfn.transform_pre.get('Ref')
52         subtrees = cfn.transform_pre.get('Fn::Sub')
53         refs = []
54         for reftree in reftrees:
55             refs.append(reftree[-1])
56         subs = []
57         for subtree in subtrees:
58             if isinstance(subtree[-1], list):
59                 subs.extend(cfn.get_sub_parameters(subtree[-1][0]))
60             else:
61                 subs.extend(cfn.get_sub_parameters(subtree[-1]))
62 
63         for paramname, _ in cfn.get_parameters().items():
64             if paramname not in refs:
65                 if paramname not in subs:
66                     message = 'Parameter {0} not used.'
67                     matches.append(RuleMatch(
68                         ['Parameters', paramname],
69                         message.format(paramname)
70                     ))
71 
72         return matches
73
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/cfnlint/rules/functions/Sub.py b/src/cfnlint/rules/functions/Sub.py
--- a/src/cfnlint/rules/functions/Sub.py
+++ b/src/cfnlint/rules/functions/Sub.py
@@ -188,6 +188,17 @@
                     message = 'Sub should be an array of 2 for {0}'
                     matches.append(RuleMatch(
                         tree, message.format('/'.join(map(str, tree)))))
+            elif isinstance(sub_value_obj, dict):
+                if len(sub_value_obj) == 1:
+                    for key, _ in sub_value_obj.items():
+                        if not key == 'Fn::Transform':
+                            message = 'Sub should be a string or array of 2 items for {0}'
+                            matches.append(RuleMatch(
+                                tree, message.format('/'.join(map(str, tree)))))
+                else:
+                    message = 'Sub should be a string or array of 2 items for {0}'
+                    matches.append(RuleMatch(
+                        tree, message.format('/'.join(map(str, tree)))))
             else:
                 message = 'Sub should be a string or array of 2 items for {0}'
                 matches.append(RuleMatch(
diff --git a/src/cfnlint/rules/parameters/Used.py b/src/cfnlint/rules/parameters/Used.py
--- a/src/cfnlint/rules/parameters/Used.py
+++ b/src/cfnlint/rules/parameters/Used.py
@@ -57,7 +57,7 @@
         for subtree in subtrees:
             if isinstance(subtree[-1], list):
                 subs.extend(cfn.get_sub_parameters(subtree[-1][0]))
-            else:
+            elif isinstance(subtree[-1], six.string_types):
                 subs.extend(cfn.get_sub_parameters(subtree[-1]))
 
         for paramname, _ in cfn.get_parameters().items():

{"golden_diff": "diff --git a/src/cfnlint/rules/functions/Sub.py b/src/cfnlint/rules/functions/Sub.py\n--- a/src/cfnlint/rules/functions/Sub.py\n+++ b/src/cfnlint/rules/functions/Sub.py\n@@ -188,6 +188,17 @@\n                     message = 'Sub should be an array of 2 for {0}'\n                     matches.append(RuleMatch(\n                         tree, message.format('/'.join(map(str, tree)))))\n+            elif isinstance(sub_value_obj, dict):\n+                if len(sub_value_obj) == 1:\n+                    for key, _ in sub_value_obj.items():\n+                        if not key == 'Fn::Transform':\n+                            message = 'Sub should be a string or array of 2 items for {0}'\n+                            matches.append(RuleMatch(\n+                                tree, message.format('/'.join(map(str, tree)))))\n+                else:\n+                    message = 'Sub should be a string or array of 2 items for {0}'\n+                    matches.append(RuleMatch(\n+                        tree, message.format('/'.join(map(str, tree)))))\n             else:\n                 message = 'Sub should be a string or array of 2 items for {0}'\n                 matches.append(RuleMatch(\ndiff --git a/src/cfnlint/rules/parameters/Used.py b/src/cfnlint/rules/parameters/Used.py\n--- a/src/cfnlint/rules/parameters/Used.py\n+++ b/src/cfnlint/rules/parameters/Used.py\n@@ -57,7 +57,7 @@\n         for subtree in subtrees:\n             if isinstance(subtree[-1], list):\n                 subs.extend(cfn.get_sub_parameters(subtree[-1][0]))\n-            else:\n+            elif isinstance(subtree[-1], six.string_types):\n                 subs.extend(cfn.get_sub_parameters(subtree[-1]))\n \n         for paramname, _ in cfn.get_parameters().items():\n", "issue": "Errors when using Fn::Transform\n*cfn-lint version: (`0.20.2`)*\r\n\r\nGiven the following valid template, I see 2 errors:\r\n\r\n```yaml\r\nParameters:\r\n  ImageId:\r\n    Type: AWS::EC2::Image::Id\r\n  InstanceType:\r\n    Type: String\r\n\r\nResources:\r\n  LaunchConfiguration:\r\n    Type: AWS::AutoScaling::LaunchConfiguration\r\n    Properties:\r\n      ImageId: !Ref ImageId\r\n      InstanceType: !Ref InstanceType\r\n      UserData:\r\n        'Fn::Base64':\r\n          'Fn::Sub':\r\n            'Fn::Transform': # Returns a string that contains Fn::Sub tokens like ${AWS::Region}\r\n              Name: DynamicUserData\r\n```\r\n* `E1019 Sub should be a string or array of 2 items for Resources/LaunchConfiguration/Properties/UserData/Fn::Base64/Fn::Sub` - `Fn::Transform` can return a string or a template, so `Fn::Sub` should be forgiving of it.\r\n* `E0002 Unknown exception while processing rule W2001: expected string or bytes-like object` Same root cause, but fails in a different way due to assumption that it complies with E1019 in a specific way.\r\n\r\n\r\n\n", "before_files": [{"content": "\"\"\"\n  Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n\n  Permission is hereby granted, free of charge, to any person obtaining a copy of this\n  software and associated documentation files (the \"Software\"), to deal in the Software\n  without restriction, including without limitation the rights to use, copy, modify,\n  merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n  permit persons to whom the Software is furnished to do so.\n\n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n  PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\"\"\"\nimport six\nfrom cfnlint import CloudFormationLintRule\nfrom cfnlint import RuleMatch\n\n\nclass Sub(CloudFormationLintRule):\n    \"\"\"Check if Sub values are correct\"\"\"\n    id = 'E1019'\n    shortdesc = 'Sub validation of parameters'\n    description = 'Making sure the sub function is properly configured'\n    source_url = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html'\n    tags = ['functions', 'sub']\n\n    def _test_string(self, cfn, sub_string, parameters, tree):\n        \"\"\"Test if a string has appropriate parameters\"\"\"\n\n        matches = []\n        string_params = cfn.get_sub_parameters(sub_string)\n\n        for string_param in string_params:\n            if isinstance(string_param, (six.string_types)):\n                matches.extend(self._test_parameter(string_param, cfn, parameters, tree))\n\n        return matches\n\n    def _get_parameters(self, cfn):\n        \"\"\"Get all Parameter Names\"\"\"\n        results = {}\n        parameters = cfn.template.get('Parameters', {})\n        if isinstance(parameters, dict):\n            for param_name, param_values in parameters.items():\n                # This rule isn't here to check the Types but we need\n                # something valid if it doesn't exist\n                results[param_name] = param_values.get('Type', 'String')\n\n        return results\n\n    def _test_parameters(self, parameters, cfn, tree):\n        \"\"\"Check parameters for appropriate configuration\"\"\"\n\n        supported_functions = [\n            'Fn::Base64',\n            'Fn::FindInMap',\n            'Fn::GetAtt',\n            'Fn::GetAZs',\n            'Fn::ImportValue',\n            'Fn::If',\n            'Fn::Join',\n            'Fn::Select',\n            'Fn::Sub',\n            'Ref'\n        ]\n\n        matches = []\n        for parameter_name, parameter_value_obj in parameters.items():\n            param_tree = tree[:] + [parameter_name]\n            if isinstance(parameter_value_obj, dict):\n                if len(parameter_value_obj) == 1:\n                    for key, value in parameter_value_obj.items():\n                        if key not in supported_functions:\n                            message = 'Sub parameter should use a valid function for {0}'\n                            matches.append(RuleMatch(\n                                param_tree, message.format('/'.join(map(str, tree)))))\n                        elif key in ['Ref']:\n                            matches.extend(self._test_parameter(value, cfn, {}, tree))\n                        elif key in ['Fn::GetAtt']:\n                            if isinstance(value, list):\n                                matches.extend(self._test_parameter('.'.join(value), cfn, {}, tree))\n                            elif isinstance(value, six.string_types):\n                                matches.extend(self._test_parameter(value, cfn, {}, tree))\n                else:\n                    message = 'Sub parameter should be an object of 1 for {0}'\n                    matches.append(RuleMatch(\n                        param_tree, message.format('/'.join(map(str, tree)))))\n            elif not isinstance(parameter_value_obj, six.string_types):\n                message = 'Sub parameter should be an object of 1 or string for {0}'\n                matches.append(RuleMatch(\n                    param_tree, message.format('/'.join(map(str, tree)))))\n\n        return matches\n\n    def _test_parameter(self, parameter, cfn, parameters, tree):\n        \"\"\" Test a parameter \"\"\"\n\n        matches = []\n        get_atts = cfn.get_valid_getatts()\n\n        valid_pseudo_params = [\n            'AWS::Region',\n            'AWS::StackName',\n            'AWS::URLSuffix',\n            'AWS::StackId',\n            'AWS::Region',\n            'AWS::Partition',\n            'AWS::NotificationARNs',\n            'AWS::AccountId'\n        ]\n\n        odd_list_params = [\n            'CommaDelimitedList',\n            'AWS::SSM::Parameter::Value<CommaDelimitedList>',\n        ]\n\n        valid_params = valid_pseudo_params\n        valid_params.extend(cfn.get_resource_names())\n        template_parameters = self._get_parameters(cfn)\n\n        for key, _ in parameters.items():\n            valid_params.append(key)\n\n        if parameter not in valid_params:\n            found = False\n            if parameter in template_parameters:\n                found = True\n                if (\n                        template_parameters.get(parameter) in odd_list_params or\n                        template_parameters.get(parameter).startswith('AWS::SSM::Parameter::Value<List') or\n                        template_parameters.get(parameter).startswith('List')):\n                    message = 'Fn::Sub cannot use list {0} at {1}'\n                    matches.append(RuleMatch(\n                        tree, message.format(parameter, '/'.join(map(str, tree)))))\n            for resource, attributes in get_atts.items():\n                for attribute_name, attribute_values in attributes.items():\n                    if resource == parameter.split('.')[0] and attribute_name == '*':\n                        if attribute_values.get('Type') == 'List':\n                            message = 'Fn::Sub cannot use list {0} at {1}'\n                            matches.append(RuleMatch(\n                                tree, message.format(parameter, '/'.join(map(str, tree)))))\n                        found = True\n                    elif (resource == parameter.split('.')[0] and\n                          attribute_name == '.'.join(parameter.split('.')[1:])):\n                        if attribute_values.get('Type') == 'List':\n                            message = 'Fn::Sub cannot use list {0} at {1}'\n                            matches.append(RuleMatch(\n                                tree, message.format(parameter, '/'.join(map(str, tree)))))\n                        found = True\n            if not found:\n                message = 'Parameter {0} for Fn::Sub not found at {1}'\n                matches.append(RuleMatch(\n                    tree, message.format(parameter, '/'.join(map(str, tree)))))\n\n        return matches\n\n    def match(self, cfn):\n        \"\"\"Check CloudFormation Join\"\"\"\n\n        matches = []\n\n        sub_objs = cfn.search_deep_keys('Fn::Sub')\n\n        for sub_obj in sub_objs:\n            sub_value_obj = sub_obj[-1]\n            tree = sub_obj[:-1]\n            if isinstance(sub_value_obj, six.string_types):\n                matches.extend(self._test_string(cfn, sub_value_obj, {}, tree))\n            elif isinstance(sub_value_obj, list):\n                if len(sub_value_obj) == 2:\n                    sub_string = sub_value_obj[0]\n                    parameters = sub_value_obj[1]\n                    if not isinstance(sub_string, six.string_types):\n                        message = 'Subs first element should be of type string for {0}'\n                        matches.append(RuleMatch(\n                            tree + [0], message.format('/'.join(map(str, tree)))))\n                    if not isinstance(parameters, dict):\n                        message = 'Subs second element should be an object for {0}'\n                        matches.append(RuleMatch(\n                            tree + [1], message.format('/'.join(map(str, tree)))))\n                    else:\n                        matches.extend(self._test_string(cfn, sub_string, parameters, tree + [0]))\n                        matches.extend(self._test_parameters(parameters, cfn, tree))\n                else:\n                    message = 'Sub should be an array of 2 for {0}'\n                    matches.append(RuleMatch(\n                        tree, message.format('/'.join(map(str, tree)))))\n            else:\n                message = 'Sub should be a string or array of 2 items for {0}'\n                matches.append(RuleMatch(\n                    tree, message.format('/'.join(map(str, tree)))))\n\n        return matches\n", "path": "src/cfnlint/rules/functions/Sub.py"}, {"content": "\"\"\"\n  Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n\n  Permission is hereby granted, free of charge, to any person obtaining a copy of this\n  software and associated documentation files (the \"Software\"), to deal in the Software\n  without restriction, including without limitation the rights to use, copy, modify,\n  merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n  permit persons to whom the Software is furnished to do so.\n\n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n  PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\"\"\"\nfrom __future__ import unicode_literals\nimport re\nimport six\nfrom cfnlint import CloudFormationLintRule\nfrom cfnlint import RuleMatch\n\n\nclass Used(CloudFormationLintRule):\n    \"\"\"Check if Parameters are used\"\"\"\n    id = 'W2001'\n    shortdesc = 'Check if Parameters are Used'\n    description = 'Making sure the parameters defined are used'\n    source_url = 'https://github.com/aws-cloudformation/cfn-python-lint'\n    tags = ['parameters']\n\n    def searchstring(self, string, parameter):\n        \"\"\"Search string for tokenized fields\"\"\"\n        regex = re.compile(r'\\${(%s)}' % parameter)\n        return regex.findall(string)\n\n    def isparaminref(self, subs, parameter):\n        \"\"\"Search sub strings for parameters\"\"\"\n        for sub in subs:\n            if isinstance(sub, (six.text_type, six.string_types)):\n                if self.searchstring(sub, parameter):\n                    return True\n\n        return False\n\n    def match(self, cfn):\n        \"\"\"Check CloudFormation Parameters\"\"\"\n\n        matches = []\n\n        reftrees = cfn.transform_pre.get('Ref')\n        subtrees = cfn.transform_pre.get('Fn::Sub')\n        refs = []\n        for reftree in reftrees:\n            refs.append(reftree[-1])\n        subs = []\n        for subtree in subtrees:\n            if isinstance(subtree[-1], list):\n                subs.extend(cfn.get_sub_parameters(subtree[-1][0]))\n            else:\n                subs.extend(cfn.get_sub_parameters(subtree[-1]))\n\n        for paramname, _ in cfn.get_parameters().items():\n            if paramname not in refs:\n                if paramname not in subs:\n                    message = 'Parameter {0} not used.'\n                    matches.append(RuleMatch(\n                        ['Parameters', paramname],\n                        message.format(paramname)\n                    ))\n\n        return matches\n", "path": "src/cfnlint/rules/parameters/Used.py"}], "after_files": [{"content": "\"\"\"\n  Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n\n  Permission is hereby granted, free of charge, to any person obtaining a copy of this\n  software and associated documentation files (the \"Software\"), to deal in the Software\n  without restriction, including without limitation the rights to use, copy, modify,\n  merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n  permit persons to whom the Software is furnished to do so.\n\n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n  PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\"\"\"\nimport six\nfrom cfnlint import CloudFormationLintRule\nfrom cfnlint import RuleMatch\n\n\nclass Sub(CloudFormationLintRule):\n    \"\"\"Check if Sub values are correct\"\"\"\n    id = 'E1019'\n    shortdesc = 'Sub validation of parameters'\n    description = 'Making sure the sub function is properly configured'\n    source_url = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html'\n    tags = ['functions', 'sub']\n\n    def _test_string(self, cfn, sub_string, parameters, tree):\n        \"\"\"Test if a string has appropriate parameters\"\"\"\n\n        matches = []\n        string_params = cfn.get_sub_parameters(sub_string)\n\n        for string_param in string_params:\n            if isinstance(string_param, (six.string_types)):\n                matches.extend(self._test_parameter(string_param, cfn, parameters, tree))\n\n        return matches\n\n    def _get_parameters(self, cfn):\n        \"\"\"Get all Parameter Names\"\"\"\n        results = {}\n        parameters = cfn.template.get('Parameters', {})\n        if isinstance(parameters, dict):\n            for param_name, param_values in parameters.items():\n                # This rule isn't here to check the Types but we need\n                # something valid if it doesn't exist\n                results[param_name] = param_values.get('Type', 'String')\n\n        return results\n\n    def _test_parameters(self, parameters, cfn, tree):\n        \"\"\"Check parameters for appropriate configuration\"\"\"\n\n        supported_functions = [\n            'Fn::Base64',\n            'Fn::FindInMap',\n            'Fn::GetAtt',\n            'Fn::GetAZs',\n            'Fn::ImportValue',\n            'Fn::If',\n            'Fn::Join',\n            'Fn::Select',\n            'Fn::Sub',\n            'Ref'\n        ]\n\n        matches = []\n        for parameter_name, parameter_value_obj in parameters.items():\n            param_tree = tree[:] + [parameter_name]\n            if isinstance(parameter_value_obj, dict):\n                if len(parameter_value_obj) == 1:\n                    for key, value in parameter_value_obj.items():\n                        if key not in supported_functions:\n                            message = 'Sub parameter should use a valid function for {0}'\n                            matches.append(RuleMatch(\n                                param_tree, message.format('/'.join(map(str, tree)))))\n                        elif key in ['Ref']:\n                            matches.extend(self._test_parameter(value, cfn, {}, tree))\n                        elif key in ['Fn::GetAtt']:\n                            if isinstance(value, list):\n                                matches.extend(self._test_parameter('.'.join(value), cfn, {}, tree))\n                            elif isinstance(value, six.string_types):\n                                matches.extend(self._test_parameter(value, cfn, {}, tree))\n                else:\n                    message = 'Sub parameter should be an object of 1 for {0}'\n                    matches.append(RuleMatch(\n                        param_tree, message.format('/'.join(map(str, tree)))))\n            elif not isinstance(parameter_value_obj, six.string_types):\n                message = 'Sub parameter should be an object of 1 or string for {0}'\n                matches.append(RuleMatch(\n                    param_tree, message.format('/'.join(map(str, tree)))))\n\n        return matches\n\n    def _test_parameter(self, parameter, cfn, parameters, tree):\n        \"\"\" Test a parameter \"\"\"\n\n        matches = []\n        get_atts = cfn.get_valid_getatts()\n\n        valid_pseudo_params = [\n            'AWS::Region',\n            'AWS::StackName',\n            'AWS::URLSuffix',\n            'AWS::StackId',\n            'AWS::Region',\n            'AWS::Partition',\n            'AWS::NotificationARNs',\n            'AWS::AccountId'\n        ]\n\n        odd_list_params = [\n            'CommaDelimitedList',\n            'AWS::SSM::Parameter::Value<CommaDelimitedList>',\n        ]\n\n        valid_params = valid_pseudo_params\n        valid_params.extend(cfn.get_resource_names())\n        template_parameters = self._get_parameters(cfn)\n\n        for key, _ in parameters.items():\n            valid_params.append(key)\n\n        if parameter not in valid_params:\n            found = False\n            if parameter in template_parameters:\n                found = True\n                if (\n                        template_parameters.get(parameter) in odd_list_params or\n                        template_parameters.get(parameter).startswith('AWS::SSM::Parameter::Value<List') or\n                        template_parameters.get(parameter).startswith('List')):\n                    message = 'Fn::Sub cannot use list {0} at {1}'\n                    matches.append(RuleMatch(\n                        tree, message.format(parameter, '/'.join(map(str, tree)))))\n            for resource, attributes in get_atts.items():\n                for attribute_name, attribute_values in attributes.items():\n                    if resource == parameter.split('.')[0] and attribute_name == '*':\n                        if attribute_values.get('Type') == 'List':\n                            message = 'Fn::Sub cannot use list {0} at {1}'\n                            matches.append(RuleMatch(\n                                tree, message.format(parameter, '/'.join(map(str, tree)))))\n                        found = True\n                    elif (resource == parameter.split('.')[0] and\n                          attribute_name == '.'.join(parameter.split('.')[1:])):\n                        if attribute_values.get('Type') == 'List':\n                            message = 'Fn::Sub cannot use list {0} at {1}'\n                            matches.append(RuleMatch(\n                                tree, message.format(parameter, '/'.join(map(str, tree)))))\n                        found = True\n            if not found:\n                message = 'Parameter {0} for Fn::Sub not found at {1}'\n                matches.append(RuleMatch(\n                    tree, message.format(parameter, '/'.join(map(str, tree)))))\n\n        return matches\n\n    def match(self, cfn):\n        \"\"\"Check CloudFormation Join\"\"\"\n\n        matches = []\n\n        sub_objs = cfn.search_deep_keys('Fn::Sub')\n\n        for sub_obj in sub_objs:\n            sub_value_obj = sub_obj[-1]\n            tree = sub_obj[:-1]\n            if isinstance(sub_value_obj, six.string_types):\n                matches.extend(self._test_string(cfn, sub_value_obj, {}, tree))\n            elif isinstance(sub_value_obj, list):\n                if len(sub_value_obj) == 2:\n                    sub_string = sub_value_obj[0]\n                    parameters = sub_value_obj[1]\n                    if not isinstance(sub_string, six.string_types):\n                        message = 'Subs first element should be of type string for {0}'\n                        matches.append(RuleMatch(\n                            tree + [0], message.format('/'.join(map(str, tree)))))\n                    if not isinstance(parameters, dict):\n                        message = 'Subs second element should be an object for {0}'\n                        matches.append(RuleMatch(\n                            tree + [1], message.format('/'.join(map(str, tree)))))\n                    else:\n                        matches.extend(self._test_string(cfn, sub_string, parameters, tree + [0]))\n                        matches.extend(self._test_parameters(parameters, cfn, tree))\n                else:\n                    message = 'Sub should be an array of 2 for {0}'\n                    matches.append(RuleMatch(\n                        tree, message.format('/'.join(map(str, tree)))))\n            elif isinstance(sub_value_obj, dict):\n                if len(sub_value_obj) == 1:\n                    for key, _ in sub_value_obj.items():\n                        if not key == 'Fn::Transform':\n                            message = 'Sub should be a string or array of 2 items for {0}'\n                            matches.append(RuleMatch(\n                                tree, message.format('/'.join(map(str, tree)))))\n                else:\n                    message = 'Sub should be a string or array of 2 items for {0}'\n                    matches.append(RuleMatch(\n                        tree, message.format('/'.join(map(str, tree)))))\n            else:\n                message = 'Sub should be a string or array of 2 items for {0}'\n                matches.append(RuleMatch(\n                    tree, message.format('/'.join(map(str, tree)))))\n\n        return matches\n", "path": "src/cfnlint/rules/functions/Sub.py"}, {"content": "\"\"\"\n  Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n\n  Permission is hereby granted, free of charge, to any person obtaining a copy of this\n  software and associated documentation files (the \"Software\"), to deal in the Software\n  without restriction, including without limitation the rights to use, copy, modify,\n  merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n  permit persons to whom the Software is furnished to do so.\n\n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\n  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n  PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT\n  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\"\"\"\nfrom __future__ import unicode_literals\nimport re\nimport six\nfrom cfnlint import CloudFormationLintRule\nfrom cfnlint import RuleMatch\n\n\nclass Used(CloudFormationLintRule):\n    \"\"\"Check if Parameters are used\"\"\"\n    id = 'W2001'\n    shortdesc = 'Check if Parameters are Used'\n    description = 'Making sure the parameters defined are used'\n    source_url = 'https://github.com/aws-cloudformation/cfn-python-lint'\n    tags = ['parameters']\n\n    def searchstring(self, string, parameter):\n        \"\"\"Search string for tokenized fields\"\"\"\n        regex = re.compile(r'\\${(%s)}' % parameter)\n        return regex.findall(string)\n\n    def isparaminref(self, subs, parameter):\n        \"\"\"Search sub strings for parameters\"\"\"\n        for sub in subs:\n            if isinstance(sub, (six.text_type, six.string_types)):\n                if self.searchstring(sub, parameter):\n                    return True\n\n        return False\n\n    def match(self, cfn):\n        \"\"\"Check CloudFormation Parameters\"\"\"\n\n        matches = []\n\n        reftrees = cfn.transform_pre.get('Ref')\n        subtrees = cfn.transform_pre.get('Fn::Sub')\n        refs = []\n        for reftree in reftrees:\n            refs.append(reftree[-1])\n        subs = []\n        for subtree in subtrees:\n            if isinstance(subtree[-1], list):\n                subs.extend(cfn.get_sub_parameters(subtree[-1][0]))\n            elif isinstance(subtree[-1], six.string_types):\n                subs.extend(cfn.get_sub_parameters(subtree[-1]))\n\n        for paramname, _ in cfn.get_parameters().items():\n            if paramname not in refs:\n                if paramname not in subs:\n                    message = 'Parameter {0} not used.'\n                    matches.append(RuleMatch(\n                        ['Parameters', paramname],\n                        message.format(paramname)\n                    ))\n\n        return matches\n", "path": "src/cfnlint/rules/parameters/Used.py"}]}