Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion tools/lint/lib/checks/frontmatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ def run(self, name, meta, source):
for parsing_event in meta.parsing_events:
if not isinstance(parsing_event, yaml.ScalarEvent):
continue
if parsing_event.style is not None:
# The pure-Python parser reports plain scalars with a style of
# `None`; the libyaml parser uses the empty string.
if parsing_event.style:
continue
if parsing_event.start_mark.line != parsing_event.end_mark.line:
return 'YAML multiline scalar values in flow notation are disallowed (use "|" or ">")'
Expand Down
28 changes: 14 additions & 14 deletions tools/lint/lib/frontmatter.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
import re
import yaml

try:
# The libyaml-backed parser is roughly an order of magnitude faster than
# the pure-Python one, but it is not available in every environment.
from yaml import CSafeLoader as Loader
except ImportError:
from yaml import SafeLoader as Loader

class Result(dict):
def __init__(self, meta, events):
self.parsing_events = events
super(Result, self).__init__(**meta)

class MyLoader(yaml.SafeLoader):
events = None

def __init__(self, *args, **kwargs):
MyLoader.events = []
super(MyLoader, self).__init__(*args, **kwargs)

def get_event(self):
event = super(MyLoader, self).get_event()
MyLoader.events.append(event)
return event

def parse(src):
'''Parse the YAML-formatted metadata found in a given string of source
code. Tolerate missing or invalid metadata; those conditions are handled by
Expand All @@ -27,8 +22,13 @@ def parse(src):
if not match:
return None

# NB: Call strip() to match parseTestRecord.
attrs = match.group(1).strip()

try:
# NB: Call strip() to match parseTestRecord.
return Result(yaml.load(match.group(1).strip(), MyLoader), MyLoader.events)
# The event stream takes a second pass, since the
# libyaml parser cannot be instrumented from Python.
events = list(yaml.parse(attrs, Loader=Loader))
return Result(yaml.load(attrs, Loader), events)
except (yaml.scanner.ScannerError, yaml.parser.ParserError):
return None
6 changes: 5 additions & 1 deletion tools/lint/lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ def checks(features):

def lint(file_names, features):
errors = dict()
# The checks are stateless with respect to the files they inspect, so they
# are created once and reused for every file (some read data files during
# construction, which is expensive to repeat).
all_checks = checks(features)

for file_name in file_names:
if not file_name.endswith((".js", ".json")):
Expand All @@ -84,7 +88,7 @@ def lint(file_names, features):
with open(file_name, 'r') as f:
content = f.read()
meta = lib.frontmatter.parse(content)
for check in checks(features):
for check in all_checks:
error = check.run(file_name, meta, content)

if error is not None:
Expand Down
2 changes: 1 addition & 1 deletion tools/lint/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
PyYAML==5.1.2
PyYAML==6.0.3