From fb13dc6499cc8a41fbbd2937b55b3b5ca742bc9c Mon Sep 17 00:00:00 2001 From: Robert Raposa Date: Mon, 2 May 2016 05:04:51 -0400 Subject: [PATCH] Move MakoLinter. --- scripts/safe_template_linter.py | 580 ++++++++++++++++---------------- 1 file changed, 290 insertions(+), 290 deletions(-) diff --git a/scripts/safe_template_linter.py b/scripts/safe_template_linter.py index 55d9ef2000..460a558a96 100755 --- a/scripts/safe_template_linter.py +++ b/scripts/safe_template_linter.py @@ -1407,6 +1407,296 @@ class JavaScriptLinter(BaseLinter): return False +class PythonLinter(BaseLinter): + """ + The linter for Python files. + + The current implementation of the linter does naive Python parsing. It does + not use the parser. One known issue is that parsing errors found inside a + docstring need to be disabled, rather than being automatically skipped. + Skipping docstrings is an enhancement that could be added. + """ + + def __init__(self): + """ + Init method. + """ + super(PythonLinter, self).__init__() + self._skip_python_dirs = self._skip_dirs + ('tests', 'test/acceptance') + + def process_file(self, directory, file_name): + """ + Process file to determine if it is a Python file and + if it is safe. + + Arguments: + directory (string): The directory of the file to be checked + file_name (string): A filename for a potential Python file + + Returns: + The file results containing any violations. + + """ + file_full_path = os.path.normpath(directory + '/' + file_name) + results = FileResults(file_full_path) + + if not results.is_file: + return results + + if file_name.lower().endswith('.py') is False: + return results + + # skip this linter code (i.e. safe_template_linter.py) + if file_name == os.path.basename(__file__): + return results + + if not self._is_valid_directory(self._skip_python_dirs, directory): + return results + + return self._load_and_check_file_is_safe(file_full_path, self.check_python_file_is_safe, results) + + def check_python_file_is_safe(self, file_contents, results): + """ + Checks for violations in a Python file. + + Arguments: + file_contents: The contents of the Python file. + results: A file results objects to which violations will be added. + + """ + self._check_concat_with_html(file_contents, Rules.python_concat_html, results) + self._check_deprecated_display_name(file_contents, results) + self._check_custom_escape(file_contents, results) + self._check_html(file_contents, results) + results.prepare_results(file_contents, line_comment_delim='#') + + def _check_deprecated_display_name(self, file_contents, results): + """ + Checks that the deprecated display_name_with_default_escaped is not + used. Adds violation to results if there is a problem. + + Arguments: + file_contents: The contents of the Python file + results: A list of results into which violations will be added. + + """ + for match in re.finditer(r'\.display_name_with_default_escaped', file_contents): + expression = Expression(match.start(), match.end()) + results.violations.append(ExpressionRuleViolation( + Rules.python_deprecated_display_name, expression + )) + + def _check_custom_escape(self, file_contents, results): + """ + Checks for custom escaping calls, rather than using a standard escaping + method. + + Arguments: + file_contents: The contents of the Python file + results: A list of results into which violations will be added. + + """ + for match in re.finditer("(<.*<|<.*<)", file_contents): + expression = Expression(match.start(), match.end()) + results.violations.append(ExpressionRuleViolation( + Rules.python_custom_escape, expression + )) + + def _check_html(self, file_contents, results): + """ + Checks many rules related to HTML in a Python file. + + Arguments: + file_contents: The contents of the Python file + results: A list of results into which violations will be added. + + """ + # Text() Expressions keyed by its end index + text_calls_by_end_index = {} + # HTML() Expressions keyed by its end index + html_calls_by_end_index = {} + start_index = 0 + while True: + + # check HTML(), Text() and format() calls + result = self._check_html_text_format( + file_contents, start_index, text_calls_by_end_index, html_calls_by_end_index, results + ) + next_start_index = result['next_start_index'] + interpolate_end_index = result['interpolate_end_index'] + + # check for interpolation including HTML outside of function calls + self._check_interpolate_with_html( + file_contents, start_index, interpolate_end_index, results + ) + + # advance the search + start_index = next_start_index + + # end if there is nothing left to search + if interpolate_end_index is None: + break + + def _check_html_text_format( + self, file_contents, start_index, text_calls_by_end_index, html_calls_by_end_index, results + ): + """ + Checks for HTML(), Text() and format() calls, and various rules related + to these calls. + + Arguments: + file_contents: The contents of the Python file + start_index: The index at which to begin searching for a function + call. + text_calls_by_end_index: Text() Expressions keyed by its end index. + html_calls_by_end_index: HTML() Expressions keyed by its end index. + results: A list of results into which violations will be added. + + Returns: + A dict with the following keys: + 'next_start_index': The start index of the next search for a + function call. + 'interpolate_end_index': The end index of the next next search + for interpolation with html, or None if the end of file + should be used. + + """ + # used to find opening of .format(), Text() and HTML() calls + regex_function_open = re.compile(r"(\.format\(|(?