We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
False positive: Sub is required if a variable is used in a string in parameter descriptions
*cfn-lint version: 0.26.0*

*Description of issue.*
Parameter descriptions fail E1029 if they contain text which looks like variable substitution:

e.g.

```yaml
  MyContentBucket:
    Description: "Bucket name for content (usually ${VPCName}-my-content), use 'none' to disable creation"
    Type: String
```

Gives an error:

    [E1029: Sub is required if a variable is used in a string] (Found an embedded parameter outside of an "Fn::Sub" at Parameters/MyContentBucket/Description)


--- 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/SubNeeded.py`
Content:
```
1 """
2 Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 SPDX-License-Identifier: MIT-0
4 """
5 import re
6 from cfnlint.rules import CloudFormationLintRule
7 from cfnlint.rules import RuleMatch
8 
9 
10 class SubNeeded(CloudFormationLintRule):
11     """Check if a substitution string exists without a substitution function"""
12     id = 'E1029'
13     shortdesc = 'Sub is required if a variable is used in a string'
14     description = 'If a substitution variable exists in a string but isn\'t wrapped with the Fn::Sub function the deployment will fail.'
15     source_url = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html'
16     tags = ['functions', 'sub']
17 
18     # Free-form text properties to exclude from this rule
19     # content is part of AWS::CloudFormation::Init
20     excludes = ['UserData', 'ZipFile', 'Condition', 'AWS::CloudFormation::Init',
21                 'CloudWatchAlarmDefinition', 'TopicRulePayload']
22     api_excludes = ['Uri', 'Body']
23 
24     # IAM Policy has special variables that don't require !Sub, Check for these
25     # https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_variables.html
26     # https://docs.aws.amazon.com/iot/latest/developerguide/basic-policy-variables.html
27     # https://docs.aws.amazon.com/iot/latest/developerguide/thing-policy-variables.html
28     # https://docs.aws.amazon.com/transfer/latest/userguide/users.html#users-policies-scope-down
29     # https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_iam-condition-keys.html
30     resource_excludes = ['${aws:CurrentTime}', '${aws:EpochTime}',
31                          '${aws:TokenIssueTime}', '${aws:principaltype}',
32                          '${aws:SecureTransport}', '${aws:SourceIp}',
33                          '${aws:UserAgent}', '${aws:userid}',
34                          '${aws:username}', '${ec2:SourceInstanceARN}',
35                          '${iot:Connection.Thing.ThingName}',
36                          '${iot:Connection.Thing.ThingTypeName}',
37                          '${iot:Connection.Thing.IsAttached}',
38                          '${iot:ClientId}', '${transfer:HomeBucket}',
39                          '${transfer:HomeDirectory}', '${transfer:HomeFolder}',
40                          '${transfer:UserName}', '${redshift:DbUser}',
41                          '${cognito-identity.amazonaws.com:aud}',
42                          '${cognito-identity.amazonaws.com:sub}',
43                          '${cognito-identity.amazonaws.com:amr}']
44 
45     # https://docs.aws.amazon.com/redshift/latest/mgmt/redshift-iam-access-control-identity-based.html
46     condition_excludes = [
47         '${redshift:DbUser}',
48     ]
49 
50     def _match_values(self, searchRegex, cfnelem, path):
51         """Recursively search for values matching the searchRegex"""
52         values = []
53         if isinstance(cfnelem, dict):
54             for key in cfnelem:
55                 pathprop = path[:]
56                 pathprop.append(key)
57                 values.extend(self._match_values(searchRegex, cfnelem[key], pathprop))
58         elif isinstance(cfnelem, list):
59             for index, item in enumerate(cfnelem):
60                 pathprop = path[:]
61                 pathprop.append(index)
62                 values.extend(self._match_values(searchRegex, item, pathprop))
63         else:
64             # Leaf node
65             if isinstance(cfnelem, str) and re.match(searchRegex, cfnelem):
66                 # Get all variables as seperate paths
67                 regex = re.compile(r'(\$\{.*?\.?.*?})')
68                 for variable in re.findall(regex, cfnelem):
69                     values.append(path + [variable])
70 
71         return values
72 
73     def match_values(self, searchRegex, cfn):
74         """
75             Search for values in all parts of the templates that match the searchRegex
76         """
77         results = []
78         results.extend(self._match_values(searchRegex, cfn.template, []))
79         # Globals are removed during a transform.  They need to be checked manually
80         results.extend(self._match_values(searchRegex, cfn.template.get('Globals', {}), []))
81         return results
82 
83     def _api_exceptions(self, value):
84         """ Key value exceptions """
85         parameter_search = re.compile(r'^\$\{stageVariables\..*\}$')
86         return re.match(parameter_search, value)
87 
88     def match(self, cfn):
89         """Basic Rule Matching"""
90 
91         matches = []
92 
93         # Generic regex to match a string containing at least one ${parameter}
94         parameter_search = re.compile(r'^.*(\$\{.*\}.*(\$\{.*\}.*)*)$')
95 
96         # Get a list of paths to every leaf node string containing at least one ${parameter}
97         parameter_string_paths = self.match_values(parameter_search, cfn)
98 
99         # We want to search all of the paths to check if each one contains an 'Fn::Sub'
100         for parameter_string_path in parameter_string_paths:
101             # Exxclude the special IAM variables
102             variable = parameter_string_path[-1]
103 
104             if 'Resource' in parameter_string_path:
105                 if variable in self.resource_excludes:
106                     continue
107             if 'Condition' in parameter_string_path:
108                 if variable in self.condition_excludes:
109                     continue
110 
111             # Exclude literals (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html)
112             if variable.startswith('${!'):
113                 continue
114 
115             found_sub = False
116             # Does the path contain an 'Fn::Sub'?
117             for step in parameter_string_path:
118                 if step in self.api_excludes:
119                     if self._api_exceptions(parameter_string_path[-1]):
120                         found_sub = True
121                 elif step == 'Fn::Sub' or step in self.excludes:
122                     found_sub = True
123 
124             # If we didn't find an 'Fn::Sub' it means a string containing a ${parameter} may not be evaluated correctly
125             if not found_sub:
126                 # Remove the last item (the variable) to prevent multiple errors on 1 line errors
127                 path = parameter_string_path[:-1]
128                 message = 'Found an embedded parameter outside of an "Fn::Sub" at {}'.format(
129                     '/'.join(map(str, path)))
130                 matches.append(RuleMatch(path, message))
131 
132         return matches
133
```
--- 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/SubNeeded.py b/src/cfnlint/rules/functions/SubNeeded.py
--- a/src/cfnlint/rules/functions/SubNeeded.py
+++ b/src/cfnlint/rules/functions/SubNeeded.py
@@ -98,6 +98,8 @@
 
         # We want to search all of the paths to check if each one contains an 'Fn::Sub'
         for parameter_string_path in parameter_string_paths:
+            if parameter_string_path[0] in ['Parameters']:
+                continue
             # Exxclude the special IAM variables
             variable = parameter_string_path[-1]

{"golden_diff": "diff --git a/src/cfnlint/rules/functions/SubNeeded.py b/src/cfnlint/rules/functions/SubNeeded.py\n--- a/src/cfnlint/rules/functions/SubNeeded.py\n+++ b/src/cfnlint/rules/functions/SubNeeded.py\n@@ -98,6 +98,8 @@\n \n         # We want to search all of the paths to check if each one contains an 'Fn::Sub'\n         for parameter_string_path in parameter_string_paths:\n+            if parameter_string_path[0] in ['Parameters']:\n+                continue\n             # Exxclude the special IAM variables\n             variable = parameter_string_path[-1]\n", "issue": "False positive: Sub is required if a variable is used in a string in parameter descriptions\n*cfn-lint version: 0.26.0*\r\n\r\n*Description of issue.*\r\nParameter descriptions fail E1029 if they contain text which looks like variable substitution:\r\n\r\ne.g.\r\n\r\n```yaml\r\n  MyContentBucket:\r\n    Description: \"Bucket name for content (usually ${VPCName}-my-content), use 'none' to disable creation\"\r\n    Type: String\r\n```\r\n\r\nGives an error:\r\n\r\n    [E1029: Sub is required if a variable is used in a string] (Found an embedded parameter outside of an \"Fn::Sub\" at Parameters/MyContentBucket/Description)\r\n\n", "before_files": [{"content": "\"\"\"\nCopyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.\nSPDX-License-Identifier: MIT-0\n\"\"\"\nimport re\nfrom cfnlint.rules import CloudFormationLintRule\nfrom cfnlint.rules import RuleMatch\n\n\nclass SubNeeded(CloudFormationLintRule):\n    \"\"\"Check if a substitution string exists without a substitution function\"\"\"\n    id = 'E1029'\n    shortdesc = 'Sub is required if a variable is used in a string'\n    description = 'If a substitution variable exists in a string but isn\\'t wrapped with the Fn::Sub function the deployment will fail.'\n    source_url = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html'\n    tags = ['functions', 'sub']\n\n    # Free-form text properties to exclude from this rule\n    # content is part of AWS::CloudFormation::Init\n    excludes = ['UserData', 'ZipFile', 'Condition', 'AWS::CloudFormation::Init',\n                'CloudWatchAlarmDefinition', 'TopicRulePayload']\n    api_excludes = ['Uri', 'Body']\n\n    # IAM Policy has special variables that don't require !Sub, Check for these\n    # https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_variables.html\n    # https://docs.aws.amazon.com/iot/latest/developerguide/basic-policy-variables.html\n    # https://docs.aws.amazon.com/iot/latest/developerguide/thing-policy-variables.html\n    # https://docs.aws.amazon.com/transfer/latest/userguide/users.html#users-policies-scope-down\n    # https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_iam-condition-keys.html\n    resource_excludes = ['${aws:CurrentTime}', '${aws:EpochTime}',\n                         '${aws:TokenIssueTime}', '${aws:principaltype}',\n                         '${aws:SecureTransport}', '${aws:SourceIp}',\n                         '${aws:UserAgent}', '${aws:userid}',\n                         '${aws:username}', '${ec2:SourceInstanceARN}',\n                         '${iot:Connection.Thing.ThingName}',\n                         '${iot:Connection.Thing.ThingTypeName}',\n                         '${iot:Connection.Thing.IsAttached}',\n                         '${iot:ClientId}', '${transfer:HomeBucket}',\n                         '${transfer:HomeDirectory}', '${transfer:HomeFolder}',\n                         '${transfer:UserName}', '${redshift:DbUser}',\n                         '${cognito-identity.amazonaws.com:aud}',\n                         '${cognito-identity.amazonaws.com:sub}',\n                         '${cognito-identity.amazonaws.com:amr}']\n\n    # https://docs.aws.amazon.com/redshift/latest/mgmt/redshift-iam-access-control-identity-based.html\n    condition_excludes = [\n        '${redshift:DbUser}',\n    ]\n\n    def _match_values(self, searchRegex, cfnelem, path):\n        \"\"\"Recursively search for values matching the searchRegex\"\"\"\n        values = []\n        if isinstance(cfnelem, dict):\n            for key in cfnelem:\n                pathprop = path[:]\n                pathprop.append(key)\n                values.extend(self._match_values(searchRegex, cfnelem[key], pathprop))\n        elif isinstance(cfnelem, list):\n            for index, item in enumerate(cfnelem):\n                pathprop = path[:]\n                pathprop.append(index)\n                values.extend(self._match_values(searchRegex, item, pathprop))\n        else:\n            # Leaf node\n            if isinstance(cfnelem, str) and re.match(searchRegex, cfnelem):\n                # Get all variables as seperate paths\n                regex = re.compile(r'(\\$\\{.*?\\.?.*?})')\n                for variable in re.findall(regex, cfnelem):\n                    values.append(path + [variable])\n\n        return values\n\n    def match_values(self, searchRegex, cfn):\n        \"\"\"\n            Search for values in all parts of the templates that match the searchRegex\n        \"\"\"\n        results = []\n        results.extend(self._match_values(searchRegex, cfn.template, []))\n        # Globals are removed during a transform.  They need to be checked manually\n        results.extend(self._match_values(searchRegex, cfn.template.get('Globals', {}), []))\n        return results\n\n    def _api_exceptions(self, value):\n        \"\"\" Key value exceptions \"\"\"\n        parameter_search = re.compile(r'^\\$\\{stageVariables\\..*\\}$')\n        return re.match(parameter_search, value)\n\n    def match(self, cfn):\n        \"\"\"Basic Rule Matching\"\"\"\n\n        matches = []\n\n        # Generic regex to match a string containing at least one ${parameter}\n        parameter_search = re.compile(r'^.*(\\$\\{.*\\}.*(\\$\\{.*\\}.*)*)$')\n\n        # Get a list of paths to every leaf node string containing at least one ${parameter}\n        parameter_string_paths = self.match_values(parameter_search, cfn)\n\n        # We want to search all of the paths to check if each one contains an 'Fn::Sub'\n        for parameter_string_path in parameter_string_paths:\n            # Exxclude the special IAM variables\n            variable = parameter_string_path[-1]\n\n            if 'Resource' in parameter_string_path:\n                if variable in self.resource_excludes:\n                    continue\n            if 'Condition' in parameter_string_path:\n                if variable in self.condition_excludes:\n                    continue\n\n            # Exclude literals (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html)\n            if variable.startswith('${!'):\n                continue\n\n            found_sub = False\n            # Does the path contain an 'Fn::Sub'?\n            for step in parameter_string_path:\n                if step in self.api_excludes:\n                    if self._api_exceptions(parameter_string_path[-1]):\n                        found_sub = True\n                elif step == 'Fn::Sub' or step in self.excludes:\n                    found_sub = True\n\n            # If we didn't find an 'Fn::Sub' it means a string containing a ${parameter} may not be evaluated correctly\n            if not found_sub:\n                # Remove the last item (the variable) to prevent multiple errors on 1 line errors\n                path = parameter_string_path[:-1]\n                message = 'Found an embedded parameter outside of an \"Fn::Sub\" at {}'.format(\n                    '/'.join(map(str, path)))\n                matches.append(RuleMatch(path, message))\n\n        return matches\n", "path": "src/cfnlint/rules/functions/SubNeeded.py"}], "after_files": [{"content": "\"\"\"\nCopyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.\nSPDX-License-Identifier: MIT-0\n\"\"\"\nimport re\nfrom cfnlint.rules import CloudFormationLintRule\nfrom cfnlint.rules import RuleMatch\n\n\nclass SubNeeded(CloudFormationLintRule):\n    \"\"\"Check if a substitution string exists without a substitution function\"\"\"\n    id = 'E1029'\n    shortdesc = 'Sub is required if a variable is used in a string'\n    description = 'If a substitution variable exists in a string but isn\\'t wrapped with the Fn::Sub function the deployment will fail.'\n    source_url = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html'\n    tags = ['functions', 'sub']\n\n    # Free-form text properties to exclude from this rule\n    # content is part of AWS::CloudFormation::Init\n    excludes = ['UserData', 'ZipFile', 'Condition', 'AWS::CloudFormation::Init',\n                'CloudWatchAlarmDefinition', 'TopicRulePayload']\n    api_excludes = ['Uri', 'Body']\n\n    # IAM Policy has special variables that don't require !Sub, Check for these\n    # https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_variables.html\n    # https://docs.aws.amazon.com/iot/latest/developerguide/basic-policy-variables.html\n    # https://docs.aws.amazon.com/iot/latest/developerguide/thing-policy-variables.html\n    # https://docs.aws.amazon.com/transfer/latest/userguide/users.html#users-policies-scope-down\n    # https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_iam-condition-keys.html\n    resource_excludes = ['${aws:CurrentTime}', '${aws:EpochTime}',\n                         '${aws:TokenIssueTime}', '${aws:principaltype}',\n                         '${aws:SecureTransport}', '${aws:SourceIp}',\n                         '${aws:UserAgent}', '${aws:userid}',\n                         '${aws:username}', '${ec2:SourceInstanceARN}',\n                         '${iot:Connection.Thing.ThingName}',\n                         '${iot:Connection.Thing.ThingTypeName}',\n                         '${iot:Connection.Thing.IsAttached}',\n                         '${iot:ClientId}', '${transfer:HomeBucket}',\n                         '${transfer:HomeDirectory}', '${transfer:HomeFolder}',\n                         '${transfer:UserName}', '${redshift:DbUser}',\n                         '${cognito-identity.amazonaws.com:aud}',\n                         '${cognito-identity.amazonaws.com:sub}',\n                         '${cognito-identity.amazonaws.com:amr}']\n\n    # https://docs.aws.amazon.com/redshift/latest/mgmt/redshift-iam-access-control-identity-based.html\n    condition_excludes = [\n        '${redshift:DbUser}',\n    ]\n\n    def _match_values(self, searchRegex, cfnelem, path):\n        \"\"\"Recursively search for values matching the searchRegex\"\"\"\n        values = []\n        if isinstance(cfnelem, dict):\n            for key in cfnelem:\n                pathprop = path[:]\n                pathprop.append(key)\n                values.extend(self._match_values(searchRegex, cfnelem[key], pathprop))\n        elif isinstance(cfnelem, list):\n            for index, item in enumerate(cfnelem):\n                pathprop = path[:]\n                pathprop.append(index)\n                values.extend(self._match_values(searchRegex, item, pathprop))\n        else:\n            # Leaf node\n            if isinstance(cfnelem, str) and re.match(searchRegex, cfnelem):\n                # Get all variables as seperate paths\n                regex = re.compile(r'(\\$\\{.*?\\.?.*?})')\n                for variable in re.findall(regex, cfnelem):\n                    values.append(path + [variable])\n\n        return values\n\n    def match_values(self, searchRegex, cfn):\n        \"\"\"\n            Search for values in all parts of the templates that match the searchRegex\n        \"\"\"\n        results = []\n        results.extend(self._match_values(searchRegex, cfn.template, []))\n        # Globals are removed during a transform.  They need to be checked manually\n        results.extend(self._match_values(searchRegex, cfn.template.get('Globals', {}), []))\n        return results\n\n    def _api_exceptions(self, value):\n        \"\"\" Key value exceptions \"\"\"\n        parameter_search = re.compile(r'^\\$\\{stageVariables\\..*\\}$')\n        return re.match(parameter_search, value)\n\n    def match(self, cfn):\n        \"\"\"Basic Rule Matching\"\"\"\n\n        matches = []\n\n        # Generic regex to match a string containing at least one ${parameter}\n        parameter_search = re.compile(r'^.*(\\$\\{.*\\}.*(\\$\\{.*\\}.*)*)$')\n\n        # Get a list of paths to every leaf node string containing at least one ${parameter}\n        parameter_string_paths = self.match_values(parameter_search, cfn)\n\n        # We want to search all of the paths to check if each one contains an 'Fn::Sub'\n        for parameter_string_path in parameter_string_paths:\n            if parameter_string_path[0] in ['Parameters']:\n                continue\n            # Exxclude the special IAM variables\n            variable = parameter_string_path[-1]\n\n            if 'Resource' in parameter_string_path:\n                if variable in self.resource_excludes:\n                    continue\n            if 'Condition' in parameter_string_path:\n                if variable in self.condition_excludes:\n                    continue\n\n            # Exclude literals (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html)\n            if variable.startswith('${!'):\n                continue\n\n            found_sub = False\n            # Does the path contain an 'Fn::Sub'?\n            for step in parameter_string_path:\n                if step in self.api_excludes:\n                    if self._api_exceptions(parameter_string_path[-1]):\n                        found_sub = True\n                elif step == 'Fn::Sub' or step in self.excludes:\n                    found_sub = True\n\n            # If we didn't find an 'Fn::Sub' it means a string containing a ${parameter} may not be evaluated correctly\n            if not found_sub:\n                # Remove the last item (the variable) to prevent multiple errors on 1 line errors\n                path = parameter_string_path[:-1]\n                message = 'Found an embedded parameter outside of an \"Fn::Sub\" at {}'.format(\n                    '/'.join(map(str, path)))\n                matches.append(RuleMatch(path, message))\n\n        return matches\n", "path": "src/cfnlint/rules/functions/SubNeeded.py"}]}