Skip to content

Commit

Permalink
Return atwo errors when a duplicate is found (#1900)
Browse files Browse the repository at this point in the history
  • Loading branch information
kddejong committed Feb 16, 2021
1 parent b20e59a commit 002b3d1
Show file tree
Hide file tree
Showing 4 changed files with 243 additions and 47 deletions.
8 changes: 4 additions & 4 deletions src/cfnlint/decode/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ def decode(filename):
matches.append(create_match_file_error(
filename, 'Cannot read file contents: %s' % filename))
except cfn_yaml.CfnParseError as err:
err.match.Filename = filename
matches = [err.match]
matches = err.matches
except ParserError as err:
matches = [create_match_yaml_parser_error(err, filename)]
except ScannerError as err:
Expand All @@ -63,8 +62,9 @@ def decode(filename):
try:
template = cfn_json.load(filename)
except cfn_json.JSONDecodeError as json_err:
json_err.match.filename = filename
matches = [json_err.match]
for e in json_err.matches:
e.filename = filename
matches = json_err.matches
except JSONDecodeError as json_err:
if hasattr(json_err, 'message'):
if json_err.message == 'No JSON object could be decoded': # pylint: disable=no-member
Expand Down
220 changes: 195 additions & 25 deletions src/cfnlint/decode/cfn_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ class DuplicateError(Exception):
Error thrown when the template contains duplicates
"""

def __init__(self, message, mapping, key):
super(DuplicateError, self).__init__(message)

self.mapping = mapping
self.key = key

class NullError(Exception):
"""
Expand All @@ -38,7 +43,7 @@ def check_duplicates(ordered_pairs, beg_mark, end_mark):
if value is None:
raise NullError('"{}"'.format(key))
if key in mapping:
raise DuplicateError('"{}"'.format(key))
raise DuplicateError('"{}"'.format(key), mapping, key)
mapping[key] = value
return mapping

Expand All @@ -53,24 +58,40 @@ class JSONDecodeError(ValueError):
"""
# Note that this exception is used from _json

def __init__(self, msg, doc, pos, key=' '):
lineno = doc.count('\n', 0, pos) + 1
colno = pos - doc.rfind('\n', 0, pos)
errmsg = '%s: line %d column %d (char %d)' % (msg, lineno, colno, pos)
def __init__(self, doc, pos, errors):

if isinstance(errors, cfnlint.rules.Match):
errors = [errors]

errmsg = '%s: line %d column %d (char %d)' % (errors[0].message, errors[0].linenumber, errors[0].linenumber, pos)
ValueError.__init__(self, errmsg)
self.msg = msg
self.msg = errors[0].message
self.doc = doc
self.pos = pos
self.lineno = lineno
self.colno = colno
self.match = cfnlint.rules.Match(
lineno, colno + 1, lineno,
colno + 1 + len(key), '', cfnlint.rules.ParseError(), message=msg)
self.lineno = errors[0].linenumber
self.colno = errors[0].linenumber
self.matches = errors

def __reduce__(self):
return self.__class__, (self.msg, self.doc, self.pos)


def build_match(message, doc, pos, key=' '):

lineno = doc.count('\n', 0, pos) + 1
colno = pos - doc.rfind('\n', 0, pos)

return cfnlint.rules.Match(
lineno, colno + 1, lineno,
colno + 1 + len(key), '', cfnlint.rules.ParseError(), message=message)


def build_match_from_node(message, node, key):

return cfnlint.rules.Match(
node[key].start_mark.line, node[key].start_mark.column + 1, node[key].end_mark.line,
node[key].end_mark.column + 1 + len(key), '', cfnlint.rules.ParseError(), message=message)

class Mark(object):
"""Mark of line and column"""
line = 1
Expand Down Expand Up @@ -98,7 +119,17 @@ def py_scanstring(s, end, strict=True,
while 1:
chunk = _m(s, end)
if chunk is None:
raise JSONDecodeError('Unterminated string starting at', s, begin)
raise JSONDecodeError(
doc=s,
pos=begin,
errors=[
build_match(
message='Unterminated string starting at',
doc=s,
pos=begin,
),
]
)
end = chunk.end()
content, terminator = chunk.groups()
# Content is contains zero or more unescaped string characters
Expand All @@ -111,20 +142,50 @@ def py_scanstring(s, end, strict=True,
if terminator != '\\':
if strict:
msg = 'Invalid control character {0!r} at'.format(terminator)
raise JSONDecodeError(msg, s, end)
raise JSONDecodeError(
doc=s,
pos=end,
errors=[
build_match(
message=msg,
doc=s,
pos=end,
),
]
)
_append(terminator)
continue
try:
esc = s[end]
except IndexError:
raise JSONDecodeError('Unterminated string starting at', s, begin)
raise JSONDecodeError(
doc=s,
pos=begin,
errors=[
build_match(
message='Unterminated string starting at',
doc=s,
pos=begin,
),
]
)
# If not a unicode escape sequence, must be in the lookup table
if esc != 'u':
try:
char = _b[esc]
except KeyError:
msg = 'Invalid \\escape: {0!r}'.format(esc)
raise JSONDecodeError(msg, s, end)
raise JSONDecodeError(
doc=s,
pos=end,
errors=[
build_match(
message=msg,
doc=s,
pos=end,
),
]
)
end += 1
else:
uni = _decode_uXXXX(s, end)
Expand All @@ -151,7 +212,17 @@ def _decode_uXXXX(s, pos):
except ValueError:
pass
msg = 'Invalid \\uXXXX escape'
raise JSONDecodeError(msg, s, pos)
raise JSONDecodeError(
doc=s,
pos=pos,
errors=[
build_match(
message=msg,
doc=s,
pos=pos,
),
]
)


def CfnJSONObject(s_and_end, strict, scan_once, object_hook, object_pairs_hook,
Expand Down Expand Up @@ -181,17 +252,52 @@ def CfnJSONObject(s_and_end, strict, scan_once, object_hook, object_pairs_hook,
result = object_pairs_hook(pairs, beg_mark, end_mark)
return result, end + 1
except DuplicateError as err:
raise JSONDecodeError('Duplicate found {}'.format(err), s, end)
raise JSONDecodeError(
doc=s,
pos=end,
errors=[
build_match_from_node(
message='Duplicate found {}'.format(err),
node=err.mapping,
key=err.key,
),
build_match(
message='Duplicate found {}'.format(err),
doc=s,
pos=end,
),
]
)
except NullError as err:
raise JSONDecodeError('Null Error {}'.format(err), s, end)
raise JSONDecodeError(
doc=s,
pos=end,
errors=[
build_match(
message='Null Error {}'.format(err),
doc=s,
pos=end,
),
]
)
pairs = {}
if object_hook is not None:
beg_mark, end_mark = get_beg_end_mark(s, original_end, end + 1)
pairs = object_hook(pairs, beg_mark, end_mark)
return pairs, end + 1

if nextchar != '"':
raise JSONDecodeError('Expecting property name enclosed in double quotes', s, end)
raise JSONDecodeError(
doc=s,
pos=end,
errors=[
build_match(
message='Expecting property name enclosed in double quotes',
doc=s,
pos=end,
),
]
)
end += 1
while True:
begin = end - 1
Expand All @@ -204,7 +310,17 @@ def CfnJSONObject(s_and_end, strict, scan_once, object_hook, object_pairs_hook,
if s[end:end + 1] != ':':
end = _w(s, end).end()
if s[end:end + 1] != ':':
raise JSONDecodeError('Expecting \':\' delimiter', s, end)
raise JSONDecodeError(
doc=s,
pos=end,
errors=[
build_match(
message='Expecting \':\' delimiter',
doc=s,
pos=end,
),
]
)
end += 1

try:
Expand All @@ -219,7 +335,17 @@ def CfnJSONObject(s_and_end, strict, scan_once, object_hook, object_pairs_hook,
try:
value, end = scan_once(s, end)
except StopIteration as err:
raise JSONDecodeError('Expecting value', s, str(err))
raise JSONDecodeError(
doc=s,
pos=str(err),
errors=[
build_match(
message='Expecting value',
doc=s,
pos=str(err),
),
]
)
key_str = str_node(key, beg_mark, end_mark)
pairs_append((key_str, value))
try:
Expand All @@ -234,21 +360,65 @@ def CfnJSONObject(s_and_end, strict, scan_once, object_hook, object_pairs_hook,
if nextchar == '}':
break
if nextchar != ',':
raise JSONDecodeError('Expecting \',\' delimiter', s, end - 1)
raise JSONDecodeError(
doc=s,
pos=end - 1,
errors=[
build_match(
message='Expecting \',\' delimiter',
doc=s,
pos=end - 1,
),
]
)
end = _w(s, end).end()
nextchar = s[end:end + 1]
end += 1
if nextchar != '"':
raise JSONDecodeError(
'Expecting property name enclosed in double quotes', s, end - 1)
doc=s,
pos=end - 1,
errors=[
build_match(
message='Expecting property name enclosed in double quotes',
doc=s,
pos=end - 1,
),
]
)
if object_pairs_hook is not None:
try:
beg_mark, end_mark = get_beg_end_mark(s, original_end, end)
result = object_pairs_hook(pairs, beg_mark, end_mark)
except DuplicateError as err:
raise JSONDecodeError('Duplicate found {}'.format(err), s, begin, key)
raise JSONDecodeError(
doc=s,
pos=end,
errors=[
build_match_from_node(
message='Duplicate found {}'.format(err),
node=err.mapping,
key=err.key,
),
build_match(
message='Duplicate found {}'.format(err),
doc=s,
pos=end,
),
]
)
except NullError as err:
raise JSONDecodeError('Null Error {}'.format(err), s, begin, key)
raise JSONDecodeError(
doc=s,
pos=end,
errors=[
build_match(
message='Null Error {}'.format(err),
doc=s,
pos=end,
),
]
)
return result, end

pairs = dict(pairs)
Expand Down
Loading

0 comments on commit 002b3d1

Please sign in to comment.