We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
E3002 false positives with intrinsics returning complex types when referenced via select
### CloudFormation Lint Version

0.76.2

### What operating system are you using?

Fedora

### Describe the bug

When using intrinsic functions and !Select to retrieve complex (ie structured) data from custom resources for other resources E3002 incorrectly marks the intrinsic as invalid properties of the resource.

For example, given a custom resource which returns an AWS::Lambda::Function Code object:

```
"Code": {
  [
   "S3Bucket": "<name>",
   "S3Key": "<object-key>",
   "S3ObjectVersion": "<object-version>"
 ],
 [
   "S3Bucket": "<name 2>",
   "S3Key": "<object-key 2>",
   "S3ObjectVersion": "<object-version 2>"
 ]
} 
```

Then referencing this as the Code property for the Function resource via select eg

`Code: !Select [ 0, !GetAtt CustomResource.Output.Code ]`

cfn-lint raises an E3002 of the form:
E3002 Invalid Property Resources/Function/Properties/Code/Fn::Select

This is similar to https://github.com/aws-cloudformation/cfn-lint/issues/2537 which resolved a similar case.




### Expected behavior

E3002 should not be raised when referencing via !Select

### Reproduction template

```yaml
Parameters:
    RoleArn:
        Type: String
    FunctionArn:
        Type: String

Resources:

    GetCodeForLatestS3Object:
        Type: Custom::LambdaSource
        Properties:
            ServiceToken: !Ref FunctionArn
            S3Url: "s3://some-bucket/lambdazips/helper.zip"

    Helper:
        Type: 'AWS::Lambda::Function'
        Properties:
            Handler: 'helper.lambda_handler'
            Role: !Ref RoleArn
            Code: !Select [ 0, !GetAtt GetCodeForLatestS3Object.Code ]
```


--- 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/decode/node.py`
Content:
```
1 """
2 Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 SPDX-License-Identifier: MIT-0
4 """
5 import logging
6 from copy import deepcopy
7 
8 import regex as re
9 
10 LOGGER = logging.getLogger(__name__)
11 
12 
13 class TemplateAttributeError(AttributeError):
14     """Custom error to capture Attribute Errors in the Template"""
15 
16 
17 def create_str_node_class(cls):
18     """
19     Create string node class
20     """
21 
22     class node_class(cls):
23         """Node class created based on the input class"""
24 
25         def __init__(self, x, start_mark, end_mark):
26             try:
27                 cls.__init__(self, x)
28             except TypeError:
29                 cls.__init__(self)
30             self.start_mark = start_mark
31             self.end_mark = end_mark
32 
33         # pylint: disable=bad-classmethod-argument, unused-argument
34         def __new__(self, x, start_mark, end_mark):
35             return cls.__new__(self, x)
36 
37         def __getattr__(self, name):
38             raise TemplateAttributeError(f"{self.__class__.__name__}.{name} is invalid")
39 
40         def __deepcopy__(self, memo):
41             result = str_node(self, self.start_mark, self.end_mark)
42             memo[id(self)] = result
43             return result
44 
45         def __copy__(self):
46             return self
47 
48     node_class.__name__ = f"{cls.__name__}_node"
49     return node_class
50 
51 
52 def create_dict_node_class(cls):
53     """
54     Create dynamic node class
55     """
56 
57     class node_class(cls):
58         """Node class created based on the input class"""
59 
60         def __init__(self, x, start_mark, end_mark):
61             try:
62                 cls.__init__(self, x)
63             except TypeError:
64                 cls.__init__(self)
65             self.start_mark = start_mark
66             self.end_mark = end_mark
67             self.condition_functions = ["Fn::If"]
68 
69         def __deepcopy__(self, memo):
70             result = dict_node(self, self.start_mark, self.end_mark)
71             memo[id(self)] = result
72             for k, v in self.items():
73                 result[deepcopy(k)] = deepcopy(v, memo)
74 
75             return result
76 
77         def __copy__(self):
78             return self
79 
80         def is_function_returning_object(self, mappings=None):
81             """
82             Check if an object is using a function that could return an object
83             Return True when
84                 Fn::Select:
85                 - 0  # or any number
86                 - !FindInMap [mapname, key, value] # or any mapname, key, value
87             Otherwise False
88             """
89             mappings = mappings or {}
90             if len(self) == 1:
91                 for k, v in self.items():
92                     if k in ["Fn::Select"]:
93                         if isinstance(v, list):
94                             if len(v) == 2:
95                                 p_v = v[1]
96                                 if isinstance(p_v, dict):
97                                     if len(p_v) == 1:
98                                         for l_k in p_v.keys():
99                                             if l_k == "Fn::FindInMap":
100                                                 return True
101 
102             return False
103 
104         def get(self, key, default=None):
105             """Override the default get"""
106             if isinstance(default, dict):
107                 default = dict_node(default, self.start_mark, self.end_mark)
108             return super().get(key, default)
109 
110         def get_safe(self, key, default=None, path=None, type_t=()):
111             """
112             Get values in format
113             """
114             path = path or []
115 
116             if default == {}:
117                 default = dict_node({}, self.start_mark, self.end_mark)
118             value = self.get(key, default)
119             if value is None and default is None:
120                 # if default is None and value is None return empty list
121                 return []
122 
123             # if the value is the default make sure that the default value is of type_t when specified
124             if bool(type_t) and value == default and not isinstance(default, type_t):
125                 raise ValueError('"default" type should be of "type_t"')
126 
127             # when not a dict see if if the value is of the right type
128             results = []
129             if not isinstance(value, (dict)):
130                 if isinstance(value, type_t) or not type_t:
131                     return [(value, (path[:] + [key]))]
132             else:
133                 for sub_v, sub_path in value.items_safe(path + [key]):
134                     if isinstance(sub_v, type_t) or not type_t:
135                         results.append((sub_v, sub_path))
136 
137             return results
138 
139         def clean(self):
140             """Clean object to remove any Ref AWS::NoValue"""
141             result = dict_node({}, self.start_mark, self.end_mark)
142             for k, v in self.items():
143                 if isinstance(v, dict) and len(v) == 1:
144                     if v.get("Ref") == "AWS::NoValue":
145                         continue
146                 result[k] = v
147             return result
148 
149         def items_safe(self, path=None, type_t=()):
150             """Get items while handling IFs"""
151             path = path or []
152             if len(self) == 1:
153                 for k, v in self.items():
154                     if k == "Fn::If":
155                         if isinstance(v, list):
156                             if len(v) == 3:
157                                 for i, if_v in enumerate(v[1:]):
158                                     if isinstance(if_v, dict):
159                                         # yield from if_v.items_safe(path[:] + [k, i - 1])
160                                         # Python 2.7 support
161                                         for items, p in if_v.items_safe(
162                                             path[:] + [k, i + 1]
163                                         ):
164                                             if isinstance(items, type_t) or not type_t:
165                                                 yield items, p
166                                     elif isinstance(if_v, list):
167                                         if isinstance(if_v, type_t) or not type_t:
168                                             yield if_v, path[:] + [k, i + 1]
169                                     else:
170                                         if isinstance(if_v, type_t) or not type_t:
171                                             yield if_v, path[:] + [k, i + 1]
172                     elif not (k == "Ref" and v == "AWS::NoValue"):
173                         if isinstance(self, type_t) or not type_t:
174                             yield self.clean(), path[:]
175             else:
176                 if isinstance(self, type_t) or not type_t:
177                     yield self.clean(), path[:]
178 
179         def __getattr__(self, name):
180             raise TemplateAttributeError(f"{self.__class__.__name__}.{name} is invalid")
181 
182     node_class.__name__ = f"{cls.__name__}_node"
183     return node_class
184 
185 
186 def create_intrinsic_node_class(cls):
187     """
188     Create dynamic sub class
189     """
190 
191     class intrinsic_class(cls):
192         """Node class created based on the input class"""
193 
194         def is_valid(self):
195             raise TemplateAttributeError("intrisnic class shouldn't be directly used")
196 
197     intrinsic_class.__name__ = f"{cls.__name__}_intrinsic"
198     return intrinsic_class
199 
200 
201 def create_sub_node_class(cls):
202     """
203     Create dynamic sub class
204     """
205 
206     class sub_class(cls):
207         """Node class created based on the input class"""
208 
209         def __init__(self, x, start_mark, end_mark):
210             cls.__init__(self, x, start_mark, end_mark)
211             self.__cache_is_valid = False
212             self.__cache_sub_string = ""
213             self.__cache_sub_string_vars = set()
214             self.__cache_sub_vars = {}
215             self.__setup()
216 
217         def __setup_list_sub_string(self, s):
218             self.__cache_sub_string = s
219             regex = re.compile(r"\${[^!].*?}")
220             string_params = regex.findall(s)
221 
222             for string_param in string_params:
223                 self.__cache_sub_string_vars.add(string_param[2:-1].strip())
224 
225         def __setup_list(self, v):
226             if len(v) == 2:
227                 if not isinstance(v[0], str):
228                     return
229                 self.__setup_list_sub_string(v[0])
230                 if not isinstance(v[1], dict):
231                     return
232                 self.__cache_sub_vars = v[1]
233                 self.__cache_is_valid = True
234 
235         def __setup(self):
236             if len(self) == 1:
237                 for k, v in self.items():
238                     if k == "Fn::Sub":
239                         if isinstance(v, str):
240                             self.__setup_list_sub_string(v)
241                             self.__cache_is_valid = True
242                         elif isinstance(v, list):
243                             self.__setup_list(v)
244 
245         def get_defined_vars(self):
246             # Returns that are in the second part of a list Fn::Sub
247             # This function will not return implied variables from a String Ref and GetAtt
248             if self.is_valid():
249                 return self.__cache_sub_vars
250 
251             return {}
252 
253         def get_string_vars(self):
254             # Returns all variables in the Sub String
255             if self.is_valid():
256                 return self.__cache_sub_string_vars
257 
258             return set()
259 
260         def get_string(self):
261             # Returns the sub string as it was when it was decoded
262             if self.is_valid():
263                 return self.__cache_sub_string
264 
265             return ""
266 
267         def is_valid(self):
268             return self.__cache_is_valid
269 
270     sub_class.__name__ = f"{cls.__name__}_sub"
271     return sub_class
272 
273 
274 def create_dict_list_class(cls):
275     """
276     Create dynamic list class
277     """
278 
279     class node_class(cls):
280         """Node class created based on the input class"""
281 
282         def __init__(self, x, start_mark, end_mark):
283             try:
284                 cls.__init__(self, x)
285             except TypeError:
286                 cls.__init__(self)
287             self.start_mark = start_mark
288             self.end_mark = end_mark
289             self.condition_functions = ["Fn::If"]
290 
291         def __deepcopy__(self, memo):
292             result = list_node([], self.start_mark, self.end_mark)
293             memo[id(self)] = result
294             for _, v in enumerate(self):
295                 result.append(deepcopy(v, memo))
296 
297             return result
298 
299         def __copy__(self):
300             return self
301 
302         def items_safe(self, path=None, type_t=()):
303             """Get items while handling IFs"""
304             path = path or []
305             for i, v in enumerate(self):
306                 if isinstance(v, dict):
307                     for items, p in v.items_safe(path[:] + [i]):
308                         if isinstance(items, type_t) or not type_t:
309                             yield items, p
310                 else:
311                     if isinstance(v, type_t) or not type_t:
312                         yield v, path[:] + [i]
313 
314         def __getattr__(self, name):
315             raise TemplateAttributeError(f"{self.__class__.__name__}.{name} is invalid")
316 
317     node_class.__name__ = f"{cls.__name__}_node"
318     return node_class
319 
320 
321 str_node = create_str_node_class(str)
322 dict_node = create_dict_node_class(dict)
323 list_node = create_dict_list_class(list)
324 intrinsic_node = create_intrinsic_node_class(dict_node)
325 sub_node = create_sub_node_class(intrinsic_node)
326
```
--- 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/decode/node.py b/src/cfnlint/decode/node.py
--- a/src/cfnlint/decode/node.py
+++ b/src/cfnlint/decode/node.py
@@ -96,7 +96,7 @@
                                 if isinstance(p_v, dict):
                                     if len(p_v) == 1:
                                         for l_k in p_v.keys():
-                                            if l_k == "Fn::FindInMap":
+                                            if l_k in ["Fn::FindInMap", "Fn::GetAtt"]:
                                                 return True
 
             return False

{"golden_diff": "diff --git a/src/cfnlint/decode/node.py b/src/cfnlint/decode/node.py\n--- a/src/cfnlint/decode/node.py\n+++ b/src/cfnlint/decode/node.py\n@@ -96,7 +96,7 @@\n                                 if isinstance(p_v, dict):\n                                     if len(p_v) == 1:\n                                         for l_k in p_v.keys():\n-                                            if l_k == \"Fn::FindInMap\":\n+                                            if l_k in [\"Fn::FindInMap\", \"Fn::GetAtt\"]:\n                                                 return True\n \n             return False\n", "issue": "E3002 false positives with intrinsics returning complex types when referenced via select\n### CloudFormation Lint Version\n\n0.76.2\n\n### What operating system are you using?\n\nFedora\n\n### Describe the bug\n\nWhen using intrinsic functions and !Select to retrieve complex (ie structured) data from custom resources for other resources E3002 incorrectly marks the intrinsic as invalid properties of the resource.\r\n\r\nFor example, given a custom resource which returns an AWS::Lambda::Function Code object:\r\n\r\n```\r\n\"Code\": {\r\n  [\r\n   \"S3Bucket\": \"<name>\",\r\n   \"S3Key\": \"<object-key>\",\r\n   \"S3ObjectVersion\": \"<object-version>\"\r\n ],\r\n [\r\n   \"S3Bucket\": \"<name 2>\",\r\n   \"S3Key\": \"<object-key 2>\",\r\n   \"S3ObjectVersion\": \"<object-version 2>\"\r\n ]\r\n} \r\n```\r\n\r\nThen referencing this as the Code property for the Function resource via select eg\r\n\r\n`Code: !Select [ 0, !GetAtt CustomResource.Output.Code ]`\r\n\r\ncfn-lint raises an E3002 of the form:\r\nE3002 Invalid Property Resources/Function/Properties/Code/Fn::Select\r\n\r\nThis is similar to https://github.com/aws-cloudformation/cfn-lint/issues/2537 which resolved a similar case.\r\n\r\n\r\n\n\n### Expected behavior\n\nE3002 should not be raised when referencing via !Select\n\n### Reproduction template\n\n```yaml\r\nParameters:\r\n    RoleArn:\r\n        Type: String\r\n    FunctionArn:\r\n        Type: String\r\n\r\nResources:\r\n\r\n    GetCodeForLatestS3Object:\r\n        Type: Custom::LambdaSource\r\n        Properties:\r\n            ServiceToken: !Ref FunctionArn\r\n            S3Url: \"s3://some-bucket/lambdazips/helper.zip\"\r\n\r\n    Helper:\r\n        Type: 'AWS::Lambda::Function'\r\n        Properties:\r\n            Handler: 'helper.lambda_handler'\r\n            Role: !Ref RoleArn\r\n            Code: !Select [ 0, !GetAtt GetCodeForLatestS3Object.Code ]\r\n```\r\n\n", "before_files": [{"content": "\"\"\"\nCopyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\nSPDX-License-Identifier: MIT-0\n\"\"\"\nimport logging\nfrom copy import deepcopy\n\nimport regex as re\n\nLOGGER = logging.getLogger(__name__)\n\n\nclass TemplateAttributeError(AttributeError):\n    \"\"\"Custom error to capture Attribute Errors in the Template\"\"\"\n\n\ndef create_str_node_class(cls):\n    \"\"\"\n    Create string node class\n    \"\"\"\n\n    class node_class(cls):\n        \"\"\"Node class created based on the input class\"\"\"\n\n        def __init__(self, x, start_mark, end_mark):\n            try:\n                cls.__init__(self, x)\n            except TypeError:\n                cls.__init__(self)\n            self.start_mark = start_mark\n            self.end_mark = end_mark\n\n        # pylint: disable=bad-classmethod-argument, unused-argument\n        def __new__(self, x, start_mark, end_mark):\n            return cls.__new__(self, x)\n\n        def __getattr__(self, name):\n            raise TemplateAttributeError(f\"{self.__class__.__name__}.{name} is invalid\")\n\n        def __deepcopy__(self, memo):\n            result = str_node(self, self.start_mark, self.end_mark)\n            memo[id(self)] = result\n            return result\n\n        def __copy__(self):\n            return self\n\n    node_class.__name__ = f\"{cls.__name__}_node\"\n    return node_class\n\n\ndef create_dict_node_class(cls):\n    \"\"\"\n    Create dynamic node class\n    \"\"\"\n\n    class node_class(cls):\n        \"\"\"Node class created based on the input class\"\"\"\n\n        def __init__(self, x, start_mark, end_mark):\n            try:\n                cls.__init__(self, x)\n            except TypeError:\n                cls.__init__(self)\n            self.start_mark = start_mark\n            self.end_mark = end_mark\n            self.condition_functions = [\"Fn::If\"]\n\n        def __deepcopy__(self, memo):\n            result = dict_node(self, self.start_mark, self.end_mark)\n            memo[id(self)] = result\n            for k, v in self.items():\n                result[deepcopy(k)] = deepcopy(v, memo)\n\n            return result\n\n        def __copy__(self):\n            return self\n\n        def is_function_returning_object(self, mappings=None):\n            \"\"\"\n            Check if an object is using a function that could return an object\n            Return True when\n                Fn::Select:\n                - 0  # or any number\n                - !FindInMap [mapname, key, value] # or any mapname, key, value\n            Otherwise False\n            \"\"\"\n            mappings = mappings or {}\n            if len(self) == 1:\n                for k, v in self.items():\n                    if k in [\"Fn::Select\"]:\n                        if isinstance(v, list):\n                            if len(v) == 2:\n                                p_v = v[1]\n                                if isinstance(p_v, dict):\n                                    if len(p_v) == 1:\n                                        for l_k in p_v.keys():\n                                            if l_k == \"Fn::FindInMap\":\n                                                return True\n\n            return False\n\n        def get(self, key, default=None):\n            \"\"\"Override the default get\"\"\"\n            if isinstance(default, dict):\n                default = dict_node(default, self.start_mark, self.end_mark)\n            return super().get(key, default)\n\n        def get_safe(self, key, default=None, path=None, type_t=()):\n            \"\"\"\n            Get values in format\n            \"\"\"\n            path = path or []\n\n            if default == {}:\n                default = dict_node({}, self.start_mark, self.end_mark)\n            value = self.get(key, default)\n            if value is None and default is None:\n                # if default is None and value is None return empty list\n                return []\n\n            # if the value is the default make sure that the default value is of type_t when specified\n            if bool(type_t) and value == default and not isinstance(default, type_t):\n                raise ValueError('\"default\" type should be of \"type_t\"')\n\n            # when not a dict see if if the value is of the right type\n            results = []\n            if not isinstance(value, (dict)):\n                if isinstance(value, type_t) or not type_t:\n                    return [(value, (path[:] + [key]))]\n            else:\n                for sub_v, sub_path in value.items_safe(path + [key]):\n                    if isinstance(sub_v, type_t) or not type_t:\n                        results.append((sub_v, sub_path))\n\n            return results\n\n        def clean(self):\n            \"\"\"Clean object to remove any Ref AWS::NoValue\"\"\"\n            result = dict_node({}, self.start_mark, self.end_mark)\n            for k, v in self.items():\n                if isinstance(v, dict) and len(v) == 1:\n                    if v.get(\"Ref\") == \"AWS::NoValue\":\n                        continue\n                result[k] = v\n            return result\n\n        def items_safe(self, path=None, type_t=()):\n            \"\"\"Get items while handling IFs\"\"\"\n            path = path or []\n            if len(self) == 1:\n                for k, v in self.items():\n                    if k == \"Fn::If\":\n                        if isinstance(v, list):\n                            if len(v) == 3:\n                                for i, if_v in enumerate(v[1:]):\n                                    if isinstance(if_v, dict):\n                                        # yield from if_v.items_safe(path[:] + [k, i - 1])\n                                        # Python 2.7 support\n                                        for items, p in if_v.items_safe(\n                                            path[:] + [k, i + 1]\n                                        ):\n                                            if isinstance(items, type_t) or not type_t:\n                                                yield items, p\n                                    elif isinstance(if_v, list):\n                                        if isinstance(if_v, type_t) or not type_t:\n                                            yield if_v, path[:] + [k, i + 1]\n                                    else:\n                                        if isinstance(if_v, type_t) or not type_t:\n                                            yield if_v, path[:] + [k, i + 1]\n                    elif not (k == \"Ref\" and v == \"AWS::NoValue\"):\n                        if isinstance(self, type_t) or not type_t:\n                            yield self.clean(), path[:]\n            else:\n                if isinstance(self, type_t) or not type_t:\n                    yield self.clean(), path[:]\n\n        def __getattr__(self, name):\n            raise TemplateAttributeError(f\"{self.__class__.__name__}.{name} is invalid\")\n\n    node_class.__name__ = f\"{cls.__name__}_node\"\n    return node_class\n\n\ndef create_intrinsic_node_class(cls):\n    \"\"\"\n    Create dynamic sub class\n    \"\"\"\n\n    class intrinsic_class(cls):\n        \"\"\"Node class created based on the input class\"\"\"\n\n        def is_valid(self):\n            raise TemplateAttributeError(\"intrisnic class shouldn't be directly used\")\n\n    intrinsic_class.__name__ = f\"{cls.__name__}_intrinsic\"\n    return intrinsic_class\n\n\ndef create_sub_node_class(cls):\n    \"\"\"\n    Create dynamic sub class\n    \"\"\"\n\n    class sub_class(cls):\n        \"\"\"Node class created based on the input class\"\"\"\n\n        def __init__(self, x, start_mark, end_mark):\n            cls.__init__(self, x, start_mark, end_mark)\n            self.__cache_is_valid = False\n            self.__cache_sub_string = \"\"\n            self.__cache_sub_string_vars = set()\n            self.__cache_sub_vars = {}\n            self.__setup()\n\n        def __setup_list_sub_string(self, s):\n            self.__cache_sub_string = s\n            regex = re.compile(r\"\\${[^!].*?}\")\n            string_params = regex.findall(s)\n\n            for string_param in string_params:\n                self.__cache_sub_string_vars.add(string_param[2:-1].strip())\n\n        def __setup_list(self, v):\n            if len(v) == 2:\n                if not isinstance(v[0], str):\n                    return\n                self.__setup_list_sub_string(v[0])\n                if not isinstance(v[1], dict):\n                    return\n                self.__cache_sub_vars = v[1]\n                self.__cache_is_valid = True\n\n        def __setup(self):\n            if len(self) == 1:\n                for k, v in self.items():\n                    if k == \"Fn::Sub\":\n                        if isinstance(v, str):\n                            self.__setup_list_sub_string(v)\n                            self.__cache_is_valid = True\n                        elif isinstance(v, list):\n                            self.__setup_list(v)\n\n        def get_defined_vars(self):\n            # Returns that are in the second part of a list Fn::Sub\n            # This function will not return implied variables from a String Ref and GetAtt\n            if self.is_valid():\n                return self.__cache_sub_vars\n\n            return {}\n\n        def get_string_vars(self):\n            # Returns all variables in the Sub String\n            if self.is_valid():\n                return self.__cache_sub_string_vars\n\n            return set()\n\n        def get_string(self):\n            # Returns the sub string as it was when it was decoded\n            if self.is_valid():\n                return self.__cache_sub_string\n\n            return \"\"\n\n        def is_valid(self):\n            return self.__cache_is_valid\n\n    sub_class.__name__ = f\"{cls.__name__}_sub\"\n    return sub_class\n\n\ndef create_dict_list_class(cls):\n    \"\"\"\n    Create dynamic list class\n    \"\"\"\n\n    class node_class(cls):\n        \"\"\"Node class created based on the input class\"\"\"\n\n        def __init__(self, x, start_mark, end_mark):\n            try:\n                cls.__init__(self, x)\n            except TypeError:\n                cls.__init__(self)\n            self.start_mark = start_mark\n            self.end_mark = end_mark\n            self.condition_functions = [\"Fn::If\"]\n\n        def __deepcopy__(self, memo):\n            result = list_node([], self.start_mark, self.end_mark)\n            memo[id(self)] = result\n            for _, v in enumerate(self):\n                result.append(deepcopy(v, memo))\n\n            return result\n\n        def __copy__(self):\n            return self\n\n        def items_safe(self, path=None, type_t=()):\n            \"\"\"Get items while handling IFs\"\"\"\n            path = path or []\n            for i, v in enumerate(self):\n                if isinstance(v, dict):\n                    for items, p in v.items_safe(path[:] + [i]):\n                        if isinstance(items, type_t) or not type_t:\n                            yield items, p\n                else:\n                    if isinstance(v, type_t) or not type_t:\n                        yield v, path[:] + [i]\n\n        def __getattr__(self, name):\n            raise TemplateAttributeError(f\"{self.__class__.__name__}.{name} is invalid\")\n\n    node_class.__name__ = f\"{cls.__name__}_node\"\n    return node_class\n\n\nstr_node = create_str_node_class(str)\ndict_node = create_dict_node_class(dict)\nlist_node = create_dict_list_class(list)\nintrinsic_node = create_intrinsic_node_class(dict_node)\nsub_node = create_sub_node_class(intrinsic_node)\n", "path": "src/cfnlint/decode/node.py"}], "after_files": [{"content": "\"\"\"\nCopyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\nSPDX-License-Identifier: MIT-0\n\"\"\"\nimport logging\nfrom copy import deepcopy\n\nimport regex as re\n\nLOGGER = logging.getLogger(__name__)\n\n\nclass TemplateAttributeError(AttributeError):\n    \"\"\"Custom error to capture Attribute Errors in the Template\"\"\"\n\n\ndef create_str_node_class(cls):\n    \"\"\"\n    Create string node class\n    \"\"\"\n\n    class node_class(cls):\n        \"\"\"Node class created based on the input class\"\"\"\n\n        def __init__(self, x, start_mark, end_mark):\n            try:\n                cls.__init__(self, x)\n            except TypeError:\n                cls.__init__(self)\n            self.start_mark = start_mark\n            self.end_mark = end_mark\n\n        # pylint: disable=bad-classmethod-argument, unused-argument\n        def __new__(self, x, start_mark, end_mark):\n            return cls.__new__(self, x)\n\n        def __getattr__(self, name):\n            raise TemplateAttributeError(f\"{self.__class__.__name__}.{name} is invalid\")\n\n        def __deepcopy__(self, memo):\n            result = str_node(self, self.start_mark, self.end_mark)\n            memo[id(self)] = result\n            return result\n\n        def __copy__(self):\n            return self\n\n    node_class.__name__ = f\"{cls.__name__}_node\"\n    return node_class\n\n\ndef create_dict_node_class(cls):\n    \"\"\"\n    Create dynamic node class\n    \"\"\"\n\n    class node_class(cls):\n        \"\"\"Node class created based on the input class\"\"\"\n\n        def __init__(self, x, start_mark, end_mark):\n            try:\n                cls.__init__(self, x)\n            except TypeError:\n                cls.__init__(self)\n            self.start_mark = start_mark\n            self.end_mark = end_mark\n            self.condition_functions = [\"Fn::If\"]\n\n        def __deepcopy__(self, memo):\n            result = dict_node(self, self.start_mark, self.end_mark)\n            memo[id(self)] = result\n            for k, v in self.items():\n                result[deepcopy(k)] = deepcopy(v, memo)\n\n            return result\n\n        def __copy__(self):\n            return self\n\n        def is_function_returning_object(self, mappings=None):\n            \"\"\"\n            Check if an object is using a function that could return an object\n            Return True when\n                Fn::Select:\n                - 0  # or any number\n                - !FindInMap [mapname, key, value] # or any mapname, key, value\n            Otherwise False\n            \"\"\"\n            mappings = mappings or {}\n            if len(self) == 1:\n                for k, v in self.items():\n                    if k in [\"Fn::Select\"]:\n                        if isinstance(v, list):\n                            if len(v) == 2:\n                                p_v = v[1]\n                                if isinstance(p_v, dict):\n                                    if len(p_v) == 1:\n                                        for l_k in p_v.keys():\n                                            if l_k in [\"Fn::FindInMap\", \"Fn::GetAtt\"]:\n                                                return True\n\n            return False\n\n        def get(self, key, default=None):\n            \"\"\"Override the default get\"\"\"\n            if isinstance(default, dict):\n                default = dict_node(default, self.start_mark, self.end_mark)\n            return super().get(key, default)\n\n        def get_safe(self, key, default=None, path=None, type_t=()):\n            \"\"\"\n            Get values in format\n            \"\"\"\n            path = path or []\n\n            if default == {}:\n                default = dict_node({}, self.start_mark, self.end_mark)\n            value = self.get(key, default)\n            if value is None and default is None:\n                # if default is None and value is None return empty list\n                return []\n\n            # if the value is the default make sure that the default value is of type_t when specified\n            if bool(type_t) and value == default and not isinstance(default, type_t):\n                raise ValueError('\"default\" type should be of \"type_t\"')\n\n            # when not a dict see if if the value is of the right type\n            results = []\n            if not isinstance(value, (dict)):\n                if isinstance(value, type_t) or not type_t:\n                    return [(value, (path[:] + [key]))]\n            else:\n                for sub_v, sub_path in value.items_safe(path + [key]):\n                    if isinstance(sub_v, type_t) or not type_t:\n                        results.append((sub_v, sub_path))\n\n            return results\n\n        def clean(self):\n            \"\"\"Clean object to remove any Ref AWS::NoValue\"\"\"\n            result = dict_node({}, self.start_mark, self.end_mark)\n            for k, v in self.items():\n                if isinstance(v, dict) and len(v) == 1:\n                    if v.get(\"Ref\") == \"AWS::NoValue\":\n                        continue\n                result[k] = v\n            return result\n\n        def items_safe(self, path=None, type_t=()):\n            \"\"\"Get items while handling IFs\"\"\"\n            path = path or []\n            if len(self) == 1:\n                for k, v in self.items():\n                    if k == \"Fn::If\":\n                        if isinstance(v, list):\n                            if len(v) == 3:\n                                for i, if_v in enumerate(v[1:]):\n                                    if isinstance(if_v, dict):\n                                        # yield from if_v.items_safe(path[:] + [k, i - 1])\n                                        # Python 2.7 support\n                                        for items, p in if_v.items_safe(\n                                            path[:] + [k, i + 1]\n                                        ):\n                                            if isinstance(items, type_t) or not type_t:\n                                                yield items, p\n                                    elif isinstance(if_v, list):\n                                        if isinstance(if_v, type_t) or not type_t:\n                                            yield if_v, path[:] + [k, i + 1]\n                                    else:\n                                        if isinstance(if_v, type_t) or not type_t:\n                                            yield if_v, path[:] + [k, i + 1]\n                    elif not (k == \"Ref\" and v == \"AWS::NoValue\"):\n                        if isinstance(self, type_t) or not type_t:\n                            yield self.clean(), path[:]\n            else:\n                if isinstance(self, type_t) or not type_t:\n                    yield self.clean(), path[:]\n\n        def __getattr__(self, name):\n            raise TemplateAttributeError(f\"{self.__class__.__name__}.{name} is invalid\")\n\n    node_class.__name__ = f\"{cls.__name__}_node\"\n    return node_class\n\n\ndef create_intrinsic_node_class(cls):\n    \"\"\"\n    Create dynamic sub class\n    \"\"\"\n\n    class intrinsic_class(cls):\n        \"\"\"Node class created based on the input class\"\"\"\n\n        def is_valid(self):\n            raise TemplateAttributeError(\"intrisnic class shouldn't be directly used\")\n\n    intrinsic_class.__name__ = f\"{cls.__name__}_intrinsic\"\n    return intrinsic_class\n\n\ndef create_sub_node_class(cls):\n    \"\"\"\n    Create dynamic sub class\n    \"\"\"\n\n    class sub_class(cls):\n        \"\"\"Node class created based on the input class\"\"\"\n\n        def __init__(self, x, start_mark, end_mark):\n            cls.__init__(self, x, start_mark, end_mark)\n            self.__cache_is_valid = False\n            self.__cache_sub_string = \"\"\n            self.__cache_sub_string_vars = set()\n            self.__cache_sub_vars = {}\n            self.__setup()\n\n        def __setup_list_sub_string(self, s):\n            self.__cache_sub_string = s\n            regex = re.compile(r\"\\${[^!].*?}\")\n            string_params = regex.findall(s)\n\n            for string_param in string_params:\n                self.__cache_sub_string_vars.add(string_param[2:-1].strip())\n\n        def __setup_list(self, v):\n            if len(v) == 2:\n                if not isinstance(v[0], str):\n                    return\n                self.__setup_list_sub_string(v[0])\n                if not isinstance(v[1], dict):\n                    return\n                self.__cache_sub_vars = v[1]\n                self.__cache_is_valid = True\n\n        def __setup(self):\n            if len(self) == 1:\n                for k, v in self.items():\n                    if k == \"Fn::Sub\":\n                        if isinstance(v, str):\n                            self.__setup_list_sub_string(v)\n                            self.__cache_is_valid = True\n                        elif isinstance(v, list):\n                            self.__setup_list(v)\n\n        def get_defined_vars(self):\n            # Returns that are in the second part of a list Fn::Sub\n            # This function will not return implied variables from a String Ref and GetAtt\n            if self.is_valid():\n                return self.__cache_sub_vars\n\n            return {}\n\n        def get_string_vars(self):\n            # Returns all variables in the Sub String\n            if self.is_valid():\n                return self.__cache_sub_string_vars\n\n            return set()\n\n        def get_string(self):\n            # Returns the sub string as it was when it was decoded\n            if self.is_valid():\n                return self.__cache_sub_string\n\n            return \"\"\n\n        def is_valid(self):\n            return self.__cache_is_valid\n\n    sub_class.__name__ = f\"{cls.__name__}_sub\"\n    return sub_class\n\n\ndef create_dict_list_class(cls):\n    \"\"\"\n    Create dynamic list class\n    \"\"\"\n\n    class node_class(cls):\n        \"\"\"Node class created based on the input class\"\"\"\n\n        def __init__(self, x, start_mark, end_mark):\n            try:\n                cls.__init__(self, x)\n            except TypeError:\n                cls.__init__(self)\n            self.start_mark = start_mark\n            self.end_mark = end_mark\n            self.condition_functions = [\"Fn::If\"]\n\n        def __deepcopy__(self, memo):\n            result = list_node([], self.start_mark, self.end_mark)\n            memo[id(self)] = result\n            for _, v in enumerate(self):\n                result.append(deepcopy(v, memo))\n\n            return result\n\n        def __copy__(self):\n            return self\n\n        def items_safe(self, path=None, type_t=()):\n            \"\"\"Get items while handling IFs\"\"\"\n            path = path or []\n            for i, v in enumerate(self):\n                if isinstance(v, dict):\n                    for items, p in v.items_safe(path[:] + [i]):\n                        if isinstance(items, type_t) or not type_t:\n                            yield items, p\n                else:\n                    if isinstance(v, type_t) or not type_t:\n                        yield v, path[:] + [i]\n\n        def __getattr__(self, name):\n            raise TemplateAttributeError(f\"{self.__class__.__name__}.{name} is invalid\")\n\n    node_class.__name__ = f\"{cls.__name__}_node\"\n    return node_class\n\n\nstr_node = create_str_node_class(str)\ndict_node = create_dict_node_class(dict)\nlist_node = create_dict_list_class(list)\nintrinsic_node = create_intrinsic_node_class(dict_node)\nsub_node = create_sub_node_class(intrinsic_node)\n", "path": "src/cfnlint/decode/node.py"}]}