We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
build: Fix eslint results in travis logs
eslint currently outputs XML for zeus to ingest, but prints the XML to console as well. Ideally we would have checkstyle formatter write to XML file but standard formatter write to stdout

Reference: https://github.com/eslint/eslint/issues/8185

--- 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/sentry/lint/engine.py`
Content:
```
1 """
2 Our linter engine needs to run in 3 different scenarios:
3  * Linting all files (python, js, less)
4  * Linting only python files (--python)
5  * Linting only js files (--js)
6 
7 For the js only path, we should not depend on any packages outside the
8 python stdlib to prevent the need to install the world just to run eslint.
9 
10 This also means imports should be done lazily/inside of function calls for
11 dependencies such as flake8/pep8.
12 """
13 from __future__ import absolute_import
14 
15 
16 import os
17 import sys
18 import subprocess
19 import json
20 
21 from subprocess import check_output, Popen
22 from click import echo, secho, style
23 
24 os.environ['PYFLAKES_NODOCTEST'] = '1'
25 
26 
27 def register_checks():
28     import pycodestyle
29 
30     from sentry.lint.sentry_check import SentryCheck
31 
32     pycodestyle.register_check(SentryCheck)
33 
34 
35 register_checks()
36 
37 
38 def get_project_root():
39     return os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir)
40 
41 
42 def get_node_modules_bin(name):
43     return os.path.join(
44         get_project_root(), 'node_modules', '.bin', name)
45 
46 
47 def get_prettier_path():
48     return get_node_modules_bin('prettier')
49 
50 
51 def get_files(path):
52     results = []
53     for root, _, files in os.walk(path):
54         for name in files:
55             results.append(os.path.join(root, name))
56     return results
57 
58 
59 def get_modified_files(path):
60     return [
61         s
62         for s in check_output(['git', 'diff-index', '--cached', '--name-only', 'HEAD']).split('\n')
63         if s
64     ]
65 
66 
67 def get_files_for_list(file_list):
68     if file_list is None:
69         files_to_check = get_files('.')
70 
71     else:
72         files_to_check = []
73         for path in file_list:
74             if os.path.isdir(path):
75                 files_to_check.extend(get_files(path))
76             else:
77                 files_to_check.append(os.path.abspath(path))
78     return sorted(set(files_to_check))
79 
80 
81 def get_js_files(file_list=None, snapshots=False):
82     if snapshots:
83         extensions = ('.js', '.jsx', '.jsx.snap', '.js.snap')
84     else:
85         extensions = ('.js', '.jsx')
86 
87     if file_list is None:
88         file_list = ['tests/js', 'src/sentry/static/sentry/app']
89     return [
90         x for x in get_files_for_list(file_list)
91         if x.endswith(extensions)
92     ]
93 
94 
95 def get_less_files(file_list=None):
96     if file_list is None:
97         file_list = ['src/sentry/static/sentry/less', 'src/sentry/static/sentry/app']
98     return [x for x in get_files_for_list(file_list) if x.endswith(('.less'))]
99     return file_list
100 
101 
102 def get_python_files(file_list=None):
103     if file_list is None:
104         file_list = ['src', 'tests']
105     return [
106         x for x in get_files_for_list(file_list)
107         if x.endswith('.py')
108     ]
109 
110 
111 # parseable is a no-op
112 def py_lint(file_list, parseable=False):
113     from flake8.engine import get_style_guide
114 
115     file_list = get_python_files(file_list)
116     flake8_style = get_style_guide(parse_argv=True)
117     report = flake8_style.check_files(file_list)
118 
119     return report.total_errors != 0
120 
121 
122 def js_lint(file_list=None, parseable=False, format=False):
123 
124     eslint_path = get_node_modules_bin('eslint')
125 
126     if not os.path.exists(eslint_path):
127         from click import echo
128         echo('!! Skipping JavaScript linting because eslint is not installed.')
129         return False
130 
131     js_file_list = get_js_files(file_list, snapshots=True)
132 
133     has_errors = False
134     if js_file_list:
135         cmd = [eslint_path, '--ext', '.js,.jsx']
136         if format:
137             cmd.append('--fix')
138         if parseable:
139             cmd.append('--format=checkstyle')
140         status = Popen(cmd + js_file_list).wait()
141         has_errors = status != 0
142 
143     return has_errors
144 
145 
146 def yarn_check(file_list):
147     """
148     Checks if package.json was modified WITHOUT a corresponding change in the Yarn
149     lockfile. This can happen if a user manually edited package.json without running Yarn.
150 
151     This is a user prompt right now because there ARE cases where you can touch package.json
152     without a Yarn lockfile change, e.g. Jest config changes, license changes, etc.
153     """
154     if file_list is None or os.environ.get('SKIP_YARN_CHECK'):
155         return False
156 
157     if 'package.json' in file_list and 'yarn.lock' not in file_list:
158         echo(style("""
159 Warning: package.json modified without accompanying yarn.lock modifications.
160 
161 If you updated a dependency/devDependency in package.json, you must run `yarn install` to update the lockfile.
162 
163 To skip this check, run:
164 
165 $ SKIP_YARN_CHECK=1 git commit [options]""", fg='yellow'))
166         return True
167 
168     return False
169 
170 
171 def is_prettier_valid(project_root, prettier_path):
172     if not os.path.exists(prettier_path):
173         echo('[sentry.lint] Skipping JavaScript formatting because prettier is not installed.', err=True)
174         return False
175 
176     # Get Prettier version from package.json
177     package_version = None
178     package_json_path = os.path.join(project_root, 'package.json')
179     with open(package_json_path) as package_json:
180         try:
181             package_version = json.load(package_json)[
182                 'devDependencies']['prettier']
183         except KeyError:
184             echo('!! Prettier missing from package.json', err=True)
185             return False
186 
187     prettier_version = subprocess.check_output(
188         [prettier_path, '--version']).rstrip()
189     if prettier_version != package_version:
190         echo(
191             '[sentry.lint] Prettier is out of date: {} (expected {}). Please run `yarn install`.'.format(
192                 prettier_version,
193                 package_version),
194             err=True)
195         return False
196 
197     return True
198 
199 
200 def js_format(file_list=None):
201     """
202     We only format JavaScript code as part of this pre-commit hook. It is not part
203     of the lint engine.
204     """
205     project_root = get_project_root()
206     prettier_path = get_prettier_path()
207 
208     if not is_prettier_valid(project_root, prettier_path):
209         return False
210 
211     js_file_list = get_js_files(file_list)
212 
213     # manually exclude some bad files
214     js_file_list = [x for x in js_file_list if '/javascript/example-project/' not in x]
215 
216     return run_formatter([prettier_path,
217                           '--write',
218                           ],
219                          js_file_list)
220 
221 
222 def js_test(file_list=None):
223     """
224     Run JavaScript unit tests on relevant files ONLY as part of pre-commit hook
225     """
226     jest_path = get_node_modules_bin('jest')
227 
228     if not os.path.exists(jest_path):
229         from click import echo
230         echo('[sentry.test] Skipping JavaScript testing because jest is not installed.')
231         return False
232 
233     js_file_list = get_js_files(file_list)
234 
235     has_errors = False
236     if js_file_list:
237         status = Popen([jest_path, '--bail', '--findRelatedTests'] + js_file_list).wait()
238         has_errors = status != 0
239 
240     return has_errors
241 
242 
243 def less_format(file_list=None):
244     """
245     We only format less code as part of this pre-commit hook. It is not part
246     of the lint engine.
247     """
248     project_root = get_project_root()
249     prettier_path = get_prettier_path()
250 
251     if not is_prettier_valid(project_root, prettier_path):
252         return False
253 
254     less_file_list = get_less_files(file_list)
255     return run_formatter(
256         [
257             prettier_path,
258             '--write',
259         ], less_file_list
260     )
261 
262 
263 def py_format(file_list=None):
264     try:
265         __import__('autopep8')
266     except ImportError:
267         echo('[sentry.lint] Skipping Python autoformat because autopep8 is not installed.', err=True)
268         return False
269 
270     py_file_list = get_python_files(file_list)
271 
272     return run_formatter(['autopep8', '--in-place', '-j0'], py_file_list)
273 
274 
275 def run_formatter(cmd, file_list, prompt_on_changes=True):
276     if not file_list:
277         return False
278 
279     has_errors = False
280 
281     status = subprocess.Popen(cmd + file_list).wait()
282     has_errors = status != 0
283     if has_errors:
284         return False
285 
286     # this is not quite correct, but it at least represents what would be staged
287     output = subprocess.check_output(['git', 'diff'] + file_list)
288     if output:
289         echo('[sentry.lint] applied changes from autoformatting')
290         for line in output.splitlines():
291             if line.startswith('-'):
292                 secho(line, fg='red')
293             elif line.startswith('+'):
294                 secho(line, fg='green')
295             else:
296                 echo(line)
297         if prompt_on_changes:
298             with open('/dev/tty') as fp:
299                 secho('Stage this patch and continue? [Y/n] ', bold=True)
300                 if fp.readline().strip().lower() != 'y':
301                     echo(
302                         '[sentry.lint] Aborted! Changes have been applied but not staged.', err=True)
303                     if not os.environ.get('SENTRY_SKIP_FORCE_PATCH'):
304                         sys.exit(1)
305                 else:
306                     status = subprocess.Popen(
307                         ['git', 'update-index', '--add'] + file_list).wait()
308         has_errors = status != 0
309     return has_errors
310 
311 
312 def run(file_list=None, format=True, lint=True, js=True, py=True,
313         less=True, yarn=True, test=False, parseable=False):
314     # pep8.py uses sys.argv to find setup.cfg
315     old_sysargv = sys.argv
316 
317     try:
318         sys.argv = [
319             os.path.join(os.path.dirname(__file__),
320                          os.pardir, os.pardir, os.pardir)
321         ]
322         results = []
323 
324         # packages
325         if yarn:
326             results.append(yarn_check(file_list))
327 
328         # bail early if a deps failed
329         if any(results):
330             return 1
331 
332         if format:
333             if py:
334                 results.append(py_format(file_list))
335             if js:
336                 results.append(js_format(file_list))
337             if less:
338                 results.append(less_format(file_list))
339 
340         # bail early if a formatter failed
341         if any(results):
342             return 1
343 
344         if lint:
345             if py:
346                 results.append(py_lint(file_list, parseable=parseable))
347             if js:
348                 results.append(js_lint(file_list, parseable=parseable, format=format))
349 
350         if test:
351             if js:
352                 results.append(js_test(file_list))
353 
354         if any(results):
355             return 1
356         return 0
357     finally:
358         sys.argv = old_sysargv
359
```
--- 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/sentry/lint/engine.py b/src/sentry/lint/engine.py
--- a/src/sentry/lint/engine.py
+++ b/src/sentry/lint/engine.py
@@ -39,6 +39,10 @@
     return os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir)
 
 
+def get_sentry_bin(name):
+    return os.path.join(get_project_root(), 'bin', name)
+
+
 def get_node_modules_bin(name):
     return os.path.join(
         get_project_root(), 'node_modules', '.bin', name)
@@ -121,7 +125,9 @@
 
 def js_lint(file_list=None, parseable=False, format=False):
 
+    # We require eslint in path but we actually call an eslint wrapper
     eslint_path = get_node_modules_bin('eslint')
+    eslint_wrapper_path = get_sentry_bin('eslint-travis-wrapper')
 
     if not os.path.exists(eslint_path):
         from click import echo
@@ -132,7 +138,11 @@
 
     has_errors = False
     if js_file_list:
-        cmd = [eslint_path, '--ext', '.js,.jsx']
+        if os.environ.get('CI'):
+            cmd = [eslint_wrapper_path, '--ext', '.js,.jsx']
+        else:
+            cmd = [eslint_path, '--ext', '.js,.jsx']
+
         if format:
             cmd.append('--fix')
         if parseable:

{"golden_diff": "diff --git a/src/sentry/lint/engine.py b/src/sentry/lint/engine.py\n--- a/src/sentry/lint/engine.py\n+++ b/src/sentry/lint/engine.py\n@@ -39,6 +39,10 @@\n     return os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir)\n \n \n+def get_sentry_bin(name):\n+    return os.path.join(get_project_root(), 'bin', name)\n+\n+\n def get_node_modules_bin(name):\n     return os.path.join(\n         get_project_root(), 'node_modules', '.bin', name)\n@@ -121,7 +125,9 @@\n \n def js_lint(file_list=None, parseable=False, format=False):\n \n+    # We require eslint in path but we actually call an eslint wrapper\n     eslint_path = get_node_modules_bin('eslint')\n+    eslint_wrapper_path = get_sentry_bin('eslint-travis-wrapper')\n \n     if not os.path.exists(eslint_path):\n         from click import echo\n@@ -132,7 +138,11 @@\n \n     has_errors = False\n     if js_file_list:\n-        cmd = [eslint_path, '--ext', '.js,.jsx']\n+        if os.environ.get('CI'):\n+            cmd = [eslint_wrapper_path, '--ext', '.js,.jsx']\n+        else:\n+            cmd = [eslint_path, '--ext', '.js,.jsx']\n+\n         if format:\n             cmd.append('--fix')\n         if parseable:\n", "issue": "build: Fix eslint results in travis logs\neslint currently outputs XML for zeus to ingest, but prints the XML to console as well. Ideally we would have checkstyle formatter write to XML file but standard formatter write to stdout\r\n\r\nReference: https://github.com/eslint/eslint/issues/8185\n", "before_files": [{"content": "\"\"\"\nOur linter engine needs to run in 3 different scenarios:\n * Linting all files (python, js, less)\n * Linting only python files (--python)\n * Linting only js files (--js)\n\nFor the js only path, we should not depend on any packages outside the\npython stdlib to prevent the need to install the world just to run eslint.\n\nThis also means imports should be done lazily/inside of function calls for\ndependencies such as flake8/pep8.\n\"\"\"\nfrom __future__ import absolute_import\n\n\nimport os\nimport sys\nimport subprocess\nimport json\n\nfrom subprocess import check_output, Popen\nfrom click import echo, secho, style\n\nos.environ['PYFLAKES_NODOCTEST'] = '1'\n\n\ndef register_checks():\n    import pycodestyle\n\n    from sentry.lint.sentry_check import SentryCheck\n\n    pycodestyle.register_check(SentryCheck)\n\n\nregister_checks()\n\n\ndef get_project_root():\n    return os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir)\n\n\ndef get_node_modules_bin(name):\n    return os.path.join(\n        get_project_root(), 'node_modules', '.bin', name)\n\n\ndef get_prettier_path():\n    return get_node_modules_bin('prettier')\n\n\ndef get_files(path):\n    results = []\n    for root, _, files in os.walk(path):\n        for name in files:\n            results.append(os.path.join(root, name))\n    return results\n\n\ndef get_modified_files(path):\n    return [\n        s\n        for s in check_output(['git', 'diff-index', '--cached', '--name-only', 'HEAD']).split('\\n')\n        if s\n    ]\n\n\ndef get_files_for_list(file_list):\n    if file_list is None:\n        files_to_check = get_files('.')\n\n    else:\n        files_to_check = []\n        for path in file_list:\n            if os.path.isdir(path):\n                files_to_check.extend(get_files(path))\n            else:\n                files_to_check.append(os.path.abspath(path))\n    return sorted(set(files_to_check))\n\n\ndef get_js_files(file_list=None, snapshots=False):\n    if snapshots:\n        extensions = ('.js', '.jsx', '.jsx.snap', '.js.snap')\n    else:\n        extensions = ('.js', '.jsx')\n\n    if file_list is None:\n        file_list = ['tests/js', 'src/sentry/static/sentry/app']\n    return [\n        x for x in get_files_for_list(file_list)\n        if x.endswith(extensions)\n    ]\n\n\ndef get_less_files(file_list=None):\n    if file_list is None:\n        file_list = ['src/sentry/static/sentry/less', 'src/sentry/static/sentry/app']\n    return [x for x in get_files_for_list(file_list) if x.endswith(('.less'))]\n    return file_list\n\n\ndef get_python_files(file_list=None):\n    if file_list is None:\n        file_list = ['src', 'tests']\n    return [\n        x for x in get_files_for_list(file_list)\n        if x.endswith('.py')\n    ]\n\n\n# parseable is a no-op\ndef py_lint(file_list, parseable=False):\n    from flake8.engine import get_style_guide\n\n    file_list = get_python_files(file_list)\n    flake8_style = get_style_guide(parse_argv=True)\n    report = flake8_style.check_files(file_list)\n\n    return report.total_errors != 0\n\n\ndef js_lint(file_list=None, parseable=False, format=False):\n\n    eslint_path = get_node_modules_bin('eslint')\n\n    if not os.path.exists(eslint_path):\n        from click import echo\n        echo('!! Skipping JavaScript linting because eslint is not installed.')\n        return False\n\n    js_file_list = get_js_files(file_list, snapshots=True)\n\n    has_errors = False\n    if js_file_list:\n        cmd = [eslint_path, '--ext', '.js,.jsx']\n        if format:\n            cmd.append('--fix')\n        if parseable:\n            cmd.append('--format=checkstyle')\n        status = Popen(cmd + js_file_list).wait()\n        has_errors = status != 0\n\n    return has_errors\n\n\ndef yarn_check(file_list):\n    \"\"\"\n    Checks if package.json was modified WITHOUT a corresponding change in the Yarn\n    lockfile. This can happen if a user manually edited package.json without running Yarn.\n\n    This is a user prompt right now because there ARE cases where you can touch package.json\n    without a Yarn lockfile change, e.g. Jest config changes, license changes, etc.\n    \"\"\"\n    if file_list is None or os.environ.get('SKIP_YARN_CHECK'):\n        return False\n\n    if 'package.json' in file_list and 'yarn.lock' not in file_list:\n        echo(style(\"\"\"\nWarning: package.json modified without accompanying yarn.lock modifications.\n\nIf you updated a dependency/devDependency in package.json, you must run `yarn install` to update the lockfile.\n\nTo skip this check, run:\n\n$ SKIP_YARN_CHECK=1 git commit [options]\"\"\", fg='yellow'))\n        return True\n\n    return False\n\n\ndef is_prettier_valid(project_root, prettier_path):\n    if not os.path.exists(prettier_path):\n        echo('[sentry.lint] Skipping JavaScript formatting because prettier is not installed.', err=True)\n        return False\n\n    # Get Prettier version from package.json\n    package_version = None\n    package_json_path = os.path.join(project_root, 'package.json')\n    with open(package_json_path) as package_json:\n        try:\n            package_version = json.load(package_json)[\n                'devDependencies']['prettier']\n        except KeyError:\n            echo('!! Prettier missing from package.json', err=True)\n            return False\n\n    prettier_version = subprocess.check_output(\n        [prettier_path, '--version']).rstrip()\n    if prettier_version != package_version:\n        echo(\n            '[sentry.lint] Prettier is out of date: {} (expected {}). Please run `yarn install`.'.format(\n                prettier_version,\n                package_version),\n            err=True)\n        return False\n\n    return True\n\n\ndef js_format(file_list=None):\n    \"\"\"\n    We only format JavaScript code as part of this pre-commit hook. It is not part\n    of the lint engine.\n    \"\"\"\n    project_root = get_project_root()\n    prettier_path = get_prettier_path()\n\n    if not is_prettier_valid(project_root, prettier_path):\n        return False\n\n    js_file_list = get_js_files(file_list)\n\n    # manually exclude some bad files\n    js_file_list = [x for x in js_file_list if '/javascript/example-project/' not in x]\n\n    return run_formatter([prettier_path,\n                          '--write',\n                          ],\n                         js_file_list)\n\n\ndef js_test(file_list=None):\n    \"\"\"\n    Run JavaScript unit tests on relevant files ONLY as part of pre-commit hook\n    \"\"\"\n    jest_path = get_node_modules_bin('jest')\n\n    if not os.path.exists(jest_path):\n        from click import echo\n        echo('[sentry.test] Skipping JavaScript testing because jest is not installed.')\n        return False\n\n    js_file_list = get_js_files(file_list)\n\n    has_errors = False\n    if js_file_list:\n        status = Popen([jest_path, '--bail', '--findRelatedTests'] + js_file_list).wait()\n        has_errors = status != 0\n\n    return has_errors\n\n\ndef less_format(file_list=None):\n    \"\"\"\n    We only format less code as part of this pre-commit hook. It is not part\n    of the lint engine.\n    \"\"\"\n    project_root = get_project_root()\n    prettier_path = get_prettier_path()\n\n    if not is_prettier_valid(project_root, prettier_path):\n        return False\n\n    less_file_list = get_less_files(file_list)\n    return run_formatter(\n        [\n            prettier_path,\n            '--write',\n        ], less_file_list\n    )\n\n\ndef py_format(file_list=None):\n    try:\n        __import__('autopep8')\n    except ImportError:\n        echo('[sentry.lint] Skipping Python autoformat because autopep8 is not installed.', err=True)\n        return False\n\n    py_file_list = get_python_files(file_list)\n\n    return run_formatter(['autopep8', '--in-place', '-j0'], py_file_list)\n\n\ndef run_formatter(cmd, file_list, prompt_on_changes=True):\n    if not file_list:\n        return False\n\n    has_errors = False\n\n    status = subprocess.Popen(cmd + file_list).wait()\n    has_errors = status != 0\n    if has_errors:\n        return False\n\n    # this is not quite correct, but it at least represents what would be staged\n    output = subprocess.check_output(['git', 'diff'] + file_list)\n    if output:\n        echo('[sentry.lint] applied changes from autoformatting')\n        for line in output.splitlines():\n            if line.startswith('-'):\n                secho(line, fg='red')\n            elif line.startswith('+'):\n                secho(line, fg='green')\n            else:\n                echo(line)\n        if prompt_on_changes:\n            with open('/dev/tty') as fp:\n                secho('Stage this patch and continue? [Y/n] ', bold=True)\n                if fp.readline().strip().lower() != 'y':\n                    echo(\n                        '[sentry.lint] Aborted! Changes have been applied but not staged.', err=True)\n                    if not os.environ.get('SENTRY_SKIP_FORCE_PATCH'):\n                        sys.exit(1)\n                else:\n                    status = subprocess.Popen(\n                        ['git', 'update-index', '--add'] + file_list).wait()\n        has_errors = status != 0\n    return has_errors\n\n\ndef run(file_list=None, format=True, lint=True, js=True, py=True,\n        less=True, yarn=True, test=False, parseable=False):\n    # pep8.py uses sys.argv to find setup.cfg\n    old_sysargv = sys.argv\n\n    try:\n        sys.argv = [\n            os.path.join(os.path.dirname(__file__),\n                         os.pardir, os.pardir, os.pardir)\n        ]\n        results = []\n\n        # packages\n        if yarn:\n            results.append(yarn_check(file_list))\n\n        # bail early if a deps failed\n        if any(results):\n            return 1\n\n        if format:\n            if py:\n                results.append(py_format(file_list))\n            if js:\n                results.append(js_format(file_list))\n            if less:\n                results.append(less_format(file_list))\n\n        # bail early if a formatter failed\n        if any(results):\n            return 1\n\n        if lint:\n            if py:\n                results.append(py_lint(file_list, parseable=parseable))\n            if js:\n                results.append(js_lint(file_list, parseable=parseable, format=format))\n\n        if test:\n            if js:\n                results.append(js_test(file_list))\n\n        if any(results):\n            return 1\n        return 0\n    finally:\n        sys.argv = old_sysargv\n", "path": "src/sentry/lint/engine.py"}], "after_files": [{"content": "\"\"\"\nOur linter engine needs to run in 3 different scenarios:\n * Linting all files (python, js, less)\n * Linting only python files (--python)\n * Linting only js files (--js)\n\nFor the js only path, we should not depend on any packages outside the\npython stdlib to prevent the need to install the world just to run eslint.\n\nThis also means imports should be done lazily/inside of function calls for\ndependencies such as flake8/pep8.\n\"\"\"\nfrom __future__ import absolute_import\n\n\nimport os\nimport sys\nimport subprocess\nimport json\n\nfrom subprocess import check_output, Popen\nfrom click import echo, secho, style\n\nos.environ['PYFLAKES_NODOCTEST'] = '1'\n\n\ndef register_checks():\n    import pycodestyle\n\n    from sentry.lint.sentry_check import SentryCheck\n\n    pycodestyle.register_check(SentryCheck)\n\n\nregister_checks()\n\n\ndef get_project_root():\n    return os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir)\n\n\ndef get_sentry_bin(name):\n    return os.path.join(get_project_root(), 'bin', name)\n\n\ndef get_node_modules_bin(name):\n    return os.path.join(\n        get_project_root(), 'node_modules', '.bin', name)\n\n\ndef get_prettier_path():\n    return get_node_modules_bin('prettier')\n\n\ndef get_files(path):\n    results = []\n    for root, _, files in os.walk(path):\n        for name in files:\n            results.append(os.path.join(root, name))\n    return results\n\n\ndef get_modified_files(path):\n    return [\n        s\n        for s in check_output(['git', 'diff-index', '--cached', '--name-only', 'HEAD']).split('\\n')\n        if s\n    ]\n\n\ndef get_files_for_list(file_list):\n    if file_list is None:\n        files_to_check = get_files('.')\n\n    else:\n        files_to_check = []\n        for path in file_list:\n            if os.path.isdir(path):\n                files_to_check.extend(get_files(path))\n            else:\n                files_to_check.append(os.path.abspath(path))\n    return sorted(set(files_to_check))\n\n\ndef get_js_files(file_list=None, snapshots=False):\n    if snapshots:\n        extensions = ('.js', '.jsx', '.jsx.snap', '.js.snap')\n    else:\n        extensions = ('.js', '.jsx')\n\n    if file_list is None:\n        file_list = ['tests/js', 'src/sentry/static/sentry/app']\n    return [\n        x for x in get_files_for_list(file_list)\n        if x.endswith(extensions)\n    ]\n\n\ndef get_less_files(file_list=None):\n    if file_list is None:\n        file_list = ['src/sentry/static/sentry/less', 'src/sentry/static/sentry/app']\n    return [x for x in get_files_for_list(file_list) if x.endswith(('.less'))]\n    return file_list\n\n\ndef get_python_files(file_list=None):\n    if file_list is None:\n        file_list = ['src', 'tests']\n    return [\n        x for x in get_files_for_list(file_list)\n        if x.endswith('.py')\n    ]\n\n\n# parseable is a no-op\ndef py_lint(file_list, parseable=False):\n    from flake8.engine import get_style_guide\n\n    file_list = get_python_files(file_list)\n    flake8_style = get_style_guide(parse_argv=True)\n    report = flake8_style.check_files(file_list)\n\n    return report.total_errors != 0\n\n\ndef js_lint(file_list=None, parseable=False, format=False):\n\n    # We require eslint in path but we actually call an eslint wrapper\n    eslint_path = get_node_modules_bin('eslint')\n    eslint_wrapper_path = get_sentry_bin('eslint-travis-wrapper')\n\n    if not os.path.exists(eslint_path):\n        from click import echo\n        echo('!! Skipping JavaScript linting because eslint is not installed.')\n        return False\n\n    js_file_list = get_js_files(file_list, snapshots=True)\n\n    has_errors = False\n    if js_file_list:\n        if os.environ.get('CI'):\n            cmd = [eslint_wrapper_path, '--ext', '.js,.jsx']\n        else:\n            cmd = [eslint_path, '--ext', '.js,.jsx']\n\n        if format:\n            cmd.append('--fix')\n        if parseable:\n            cmd.append('--format=checkstyle')\n        status = Popen(cmd + js_file_list).wait()\n        has_errors = status != 0\n\n    return has_errors\n\n\ndef yarn_check(file_list):\n    \"\"\"\n    Checks if package.json was modified WITHOUT a corresponding change in the Yarn\n    lockfile. This can happen if a user manually edited package.json without running Yarn.\n\n    This is a user prompt right now because there ARE cases where you can touch package.json\n    without a Yarn lockfile change, e.g. Jest config changes, license changes, etc.\n    \"\"\"\n    if file_list is None or os.environ.get('SKIP_YARN_CHECK'):\n        return False\n\n    if 'package.json' in file_list and 'yarn.lock' not in file_list:\n        echo(style(\"\"\"\nWarning: package.json modified without accompanying yarn.lock modifications.\n\nIf you updated a dependency/devDependency in package.json, you must run `yarn install` to update the lockfile.\n\nTo skip this check, run:\n\n$ SKIP_YARN_CHECK=1 git commit [options]\"\"\", fg='yellow'))\n        return True\n\n    return False\n\n\ndef is_prettier_valid(project_root, prettier_path):\n    if not os.path.exists(prettier_path):\n        echo('[sentry.lint] Skipping JavaScript formatting because prettier is not installed.', err=True)\n        return False\n\n    # Get Prettier version from package.json\n    package_version = None\n    package_json_path = os.path.join(project_root, 'package.json')\n    with open(package_json_path) as package_json:\n        try:\n            package_version = json.load(package_json)[\n                'devDependencies']['prettier']\n        except KeyError:\n            echo('!! Prettier missing from package.json', err=True)\n            return False\n\n    prettier_version = subprocess.check_output(\n        [prettier_path, '--version']).rstrip()\n    if prettier_version != package_version:\n        echo(\n            '[sentry.lint] Prettier is out of date: {} (expected {}). Please run `yarn install`.'.format(\n                prettier_version,\n                package_version),\n            err=True)\n        return False\n\n    return True\n\n\ndef js_format(file_list=None):\n    \"\"\"\n    We only format JavaScript code as part of this pre-commit hook. It is not part\n    of the lint engine.\n    \"\"\"\n    project_root = get_project_root()\n    prettier_path = get_prettier_path()\n\n    if not is_prettier_valid(project_root, prettier_path):\n        return False\n\n    js_file_list = get_js_files(file_list)\n\n    # manually exclude some bad files\n    js_file_list = [x for x in js_file_list if '/javascript/example-project/' not in x]\n\n    return run_formatter([prettier_path,\n                          '--write',\n                          ],\n                         js_file_list)\n\n\ndef js_test(file_list=None):\n    \"\"\"\n    Run JavaScript unit tests on relevant files ONLY as part of pre-commit hook\n    \"\"\"\n    jest_path = get_node_modules_bin('jest')\n\n    if not os.path.exists(jest_path):\n        from click import echo\n        echo('[sentry.test] Skipping JavaScript testing because jest is not installed.')\n        return False\n\n    js_file_list = get_js_files(file_list)\n\n    has_errors = False\n    if js_file_list:\n        status = Popen([jest_path, '--bail', '--findRelatedTests'] + js_file_list).wait()\n        has_errors = status != 0\n\n    return has_errors\n\n\ndef less_format(file_list=None):\n    \"\"\"\n    We only format less code as part of this pre-commit hook. It is not part\n    of the lint engine.\n    \"\"\"\n    project_root = get_project_root()\n    prettier_path = get_prettier_path()\n\n    if not is_prettier_valid(project_root, prettier_path):\n        return False\n\n    less_file_list = get_less_files(file_list)\n    return run_formatter(\n        [\n            prettier_path,\n            '--write',\n        ], less_file_list\n    )\n\n\ndef py_format(file_list=None):\n    try:\n        __import__('autopep8')\n    except ImportError:\n        echo('[sentry.lint] Skipping Python autoformat because autopep8 is not installed.', err=True)\n        return False\n\n    py_file_list = get_python_files(file_list)\n\n    return run_formatter(['autopep8', '--in-place', '-j0'], py_file_list)\n\n\ndef run_formatter(cmd, file_list, prompt_on_changes=True):\n    if not file_list:\n        return False\n\n    has_errors = False\n\n    status = subprocess.Popen(cmd + file_list).wait()\n    has_errors = status != 0\n    if has_errors:\n        return False\n\n    # this is not quite correct, but it at least represents what would be staged\n    output = subprocess.check_output(['git', 'diff'] + file_list)\n    if output:\n        echo('[sentry.lint] applied changes from autoformatting')\n        for line in output.splitlines():\n            if line.startswith('-'):\n                secho(line, fg='red')\n            elif line.startswith('+'):\n                secho(line, fg='green')\n            else:\n                echo(line)\n        if prompt_on_changes:\n            with open('/dev/tty') as fp:\n                secho('Stage this patch and continue? [Y/n] ', bold=True)\n                if fp.readline().strip().lower() != 'y':\n                    echo(\n                        '[sentry.lint] Aborted! Changes have been applied but not staged.', err=True)\n                    if not os.environ.get('SENTRY_SKIP_FORCE_PATCH'):\n                        sys.exit(1)\n                else:\n                    status = subprocess.Popen(\n                        ['git', 'update-index', '--add'] + file_list).wait()\n        has_errors = status != 0\n    return has_errors\n\n\ndef run(file_list=None, format=True, lint=True, js=True, py=True,\n        less=True, yarn=True, test=False, parseable=False):\n    # pep8.py uses sys.argv to find setup.cfg\n    old_sysargv = sys.argv\n\n    try:\n        sys.argv = [\n            os.path.join(os.path.dirname(__file__),\n                         os.pardir, os.pardir, os.pardir)\n        ]\n        results = []\n\n        # packages\n        if yarn:\n            results.append(yarn_check(file_list))\n\n        # bail early if a deps failed\n        if any(results):\n            return 1\n\n        if format:\n            if py:\n                results.append(py_format(file_list))\n            if js:\n                results.append(js_format(file_list))\n            if less:\n                results.append(less_format(file_list))\n\n        # bail early if a formatter failed\n        if any(results):\n            return 1\n\n        if lint:\n            if py:\n                results.append(py_lint(file_list, parseable=parseable))\n            if js:\n                results.append(js_lint(file_list, parseable=parseable, format=format))\n\n        if test:\n            if js:\n                results.append(js_test(file_list))\n\n        if any(results):\n            return 1\n        return 0\n    finally:\n        sys.argv = old_sysargv\n", "path": "src/sentry/lint/engine.py"}]}