Repository: aws-cloudformation/cfn-lint

Issue:
Linter Errors in YAML Parsing should be in linter Message
# Version
cfn-lint 0.28.2

# Description of issue.
YAML Parsing errors should be included in the `Message` field rather than the generic `Expecting Value` message for rule `E0000`. The error messages from the `ScannerError` is much more accurate and descriptive than `Expecting Value`.
Currently, specifying an "invalid token" (by putting `\t` before a token) will trigger this error, and is difficult to find because the Linter error points to the error being with the first character of the YAML file.

To find the error in my template file, I had to make use of the python debugger and step through the `cfn-lint` and `pyyaml` code to get to a place where I could see the output of the exception, and then grepped the `cfn-lint` code for the specific error type.

# Example Template File
In this example template file, the error is on line 6, column 14 `Default:\tprivate`

    AWSTemplateFormatVersion: '2010-09-09'
    Description: "CloudFormation Stack for the Networking components within an AZ"
    Parameters:
      vpcId:
        Type: String
        Default: 	private
    
      AvailabilityZone:
        Description: 'Zero-indexed integer representing the AZ. For example, for AZ 2 within a region this will be 1'
        Type: Integer
        Default: 0
    
      PublicSubnetCIDR:
        Description: CIDR value for the public subnet within this AZ; must not overlap with any other subnets in this VPC
        Type: String
        Default: 10.192.0.0/20
        AllowedPattern: '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$'
    
      PrivateSubnetCIDR:
        Description: CIDR value for the private subnet within this AZ; must not overlap with any other subnets in this VPC
        Type: String
        Default: 10.192.30.0/20
        AllowedPattern: '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$'
    
    Resources:
      cfPublicSubnet:
        Type: 'AWS::EC2::Subnet'
        Properties:
          AvailabilityZone: !Select [ !Ref AvailabilityZone, !GetAZs '' ]
          CidrBlock: !Ref PublicSubnetCIDR
          MapPublicIpOnLaunch: true
          VpcId: !Ref vpcId
          Tags:
            - Key: Name
              Value: !Sub cf-poc-public-subnet-az${AvailabilityZone}
    
      cfPrivateSubnet:
        Type: 'AWS::EC2::Subnet'
        Properties:
          AvailabilityZone: !Select [ !Ref AvailabilityZone, !GetAZs '' ]
          CidrBlock: !Ref PrivateSubnetCIDR
          VpcId: !Ref vpcId
          Tags:
            - Key: Name
              Value: !Sub "cf-poc-private-subnet-az${AvailabilityZone}"
    
      # NAT Gateway for Public Subnet
      cfNatGateway:
        Type: 'AWS::EC2::NatGateway'
        Properties:
          AllocationId: !GetAtt cfNatElasticIp.AllocationId
          SubnetId: !Ref cfPublicSubnet
          Tags:
            - Key: Name
              Value: !Sub "cf-poc-nat-gateway-az${AvailabilityZone}"
    
      # Elastic IPs for the NAT Gateways
      cfNatElasticIp:
        Type: 'AWS::EC2::EIP'
        Properties:
          Domain: vpc
          Tags:
            - Key: Name
              Value: !Sub "cf-poc-nat-eip-az${AvailabilityZone}"
    
    Outputs:
      cfPublicSubnet:
        Value: !Ref cfPublicSubnet
    
      cfPrivateSubnet:
        Value: !Ref cfPrivateSubnet
    
      cfNatGateway:
        Value: !Ref cfNatGateway

# Additional Info
The `ScannerError`, when allowed to print, outputs the following:

    yaml.scanner.ScannerError: while scanning for the next token
    found character '\t' that cannot start any token
      in "<unicode string>", line 6, column 14:
            Default: 	private

which is much more accurate and informative than:

    [
        {
            "Filename": "/home/eurythmia/CloudFormation/PoC-cf-AzNetwork.yaml",
            "Level": "Error",
            "Location": {
                "End": {
                    "ColumnNumber": 2,
                    "LineNumber": 1
                },
                "Path": null,
                "Start": {
                    "ColumnNumber": 1,
                    "LineNumber": 1
                }
            },
            "Message": "Expecting value",
            "Rule": {
                "Description": "Checks for Null values and Duplicate values in resources",
                "Id": "E0000",
                "ShortDescription": "Parsing error found when parsing the template",
                "Source": "https://github.com/aws-cloudformation/cfn-python-lint"
            }
        }
    ]

This occurs in the code [somewhere around here](https://github.com/aws-cloudformation/cfn-python-lint/blob/master/src/cfnlint/decode/__init__.py#L58), by catching the error for this specific error type. In principle, I'm not against catching the error to provide the user with information, but in this case we should be creating Linter warnings using the information _from_ the exception.


Discussion:
I also just encountered this issue. Makes finding the problem a real pain but thanks to your question I could just search for `\t`s. Thank you.

Yea we do a few extra parsing tries when we run into the `\t` issue.  The result currently is a masked error message.  Making a few changes that should bring back the original YAML error message in this scenario.  This work for you?
```
E0000 found character '\t' that cannot start any token
template.yaml:6:14
```
As long as the error wasn't related to `\t` this is what is shown today.
```
E0000 could not find expected ':'
template.yaml:4:1
```

> Yea we do a few extra parsing tries when we run into the `\t` issue. The result currently is a masked error message. Making a few changes that should bring back the original YAML error message in this scenario. This work for you?
> 
> ```
> E0000 found character '\t' that cannot start any token
> template.yaml:6:14
> ```
> 
> As long as the error wasn't related to `\t` this is what is shown today.
> 
> ```
> E0000 could not find expected ':'
> template.yaml:4:1
> ```

Yeah; that looks like a reasonable solution, thanks :-)

Resolving patch:
diff --git a/src/cfnlint/decode/__init__.py b/src/cfnlint/decode/__init__.py
--- a/src/cfnlint/decode/__init__.py
+++ b/src/cfnlint/decode/__init__.py
@@ -65,7 +65,16 @@ def decode(filename, ignore_bad_template):
                 json_err.match.filename = filename
                 matches = [json_err.match]
             except JSONDecodeError as json_err:
-                matches = [create_match_json_parser_error(json_err, filename)]
+                if hasattr(json_err, 'message'):
+                    if json_err.message == 'No JSON object could be decoded':  # pylint: disable=no-member
+                        matches = [create_match_yaml_parser_error(err, filename)]
+                    else:
+                        matches = [create_match_json_parser_error(json_err, filename)]
+                if hasattr(json_err, 'msg'):
+                    if json_err.msg == 'Expecting value':  # pylint: disable=no-member
+                        matches = [create_match_yaml_parser_error(err, filename)]
+                    else:
+                        matches = [create_match_json_parser_error(json_err, filename)]
             except Exception as json_err:  # pylint: disable=W0703
                 if ignore_bad_template:
                     LOGGER.info('Template %s is malformed: %s',
diff --git a/src/cfnlint/rules/__init__.py b/src/cfnlint/rules/__init__.py
--- a/src/cfnlint/rules/__init__.py
+++ b/src/cfnlint/rules/__init__.py
@@ -422,7 +422,7 @@ class ParseError(CloudFormationLintRule):
     """Parse Lint Rule"""
     id = 'E0000'
     shortdesc = 'Parsing error found when parsing the template'
-    description = 'Checks for Null values and Duplicate values in resources'
+    description = 'Checks for JSON/YAML formatting errors in your template'
     source_url = 'https://github.com/aws-cloudformation/cfn-python-lint'
     tags = ['base']
 


Test patch:
diff --git a/test/unit/module/maintenance/test_update_documentation.py b/test/unit/module/maintenance/test_update_documentation.py
--- a/test/unit/module/maintenance/test_update_documentation.py
+++ b/test/unit/module/maintenance/test_update_documentation.py
@@ -74,7 +74,7 @@ class TestRuleWarning(CloudFormationLintRule):
                 call('| Rule ID  | Title | Description | Config<br />(Name:Type:Default) | Source | Tags |\n'),
                 call('| -------- | ----- | ----------- | ---------- | ------ | ---- |\n'),
                 call(
-                    '| E0000<a name="E0000"></a> | Parsing error found when parsing the template | Checks for Null values and Duplicate values in resources |  | [Source]() | `base` |\n'),
+                    '| E0000<a name="E0000"></a> | Parsing error found when parsing the template | Checks for JSON/YAML formatting errors in your template |  | [Source]() | `base` |\n'),
                 call(
                     '| E0001<a name="E0001"></a> | Error found when transforming the template | Errors found when performing transformation on the template |  | [Source]() | `base`,`transform` |\n'),
                 call(