From 4524ddd6a9e8cc0bba4b7588c038ad40fd25ff03 Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Fri, 2 Jun 2017 12:57:31 -0700 Subject: [PATCH 001/433] Use visualstudio_py_launcher in custom launcher --- .../PythonTools/visualstudio_py_launcher.py | 143 +++++++++--------- src/client/debugger/Common/Utils.ts | 3 + 2 files changed, 77 insertions(+), 69 deletions(-) diff --git a/pythonFiles/PythonTools/visualstudio_py_launcher.py b/pythonFiles/PythonTools/visualstudio_py_launcher.py index 12b2140c4f72..20bd34d237ba 100644 --- a/pythonFiles/PythonTools/visualstudio_py_launcher.py +++ b/pythonFiles/PythonTools/visualstudio_py_launcher.py @@ -1,16 +1,16 @@ # Python Tools for Visual Studio # Copyright(c) Microsoft Corporation # All rights reserved. -# +# # Licensed under the Apache License, Version 2.0 (the License); you may not use # this file except in compliance with the License. You may obtain a copy of the # License at http://www.apache.org/licenses/LICENSE-2.0 -# +# # THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS # OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY # IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, # MERCHANTABLITY OR NON-INFRINGEMENT. -# +# # See the Apache Version 2.0 License for specific language governing # permissions and limitations under the License. @@ -23,70 +23,75 @@ __author__ = "Microsoft Corporation " __version__ = "3.0.0.0" -import os -import os.path -import sys -import traceback -try: - import visualstudio_py_debugger as vspd -except: - traceback.print_exc() - print(''' -Internal error detected. Please copy the above traceback and report at -http://go.microsoft.com/fwlink/?LinkId=293415 - -Press Enter to close. . .''') +def launch(): + import os + import os.path + import sys + import traceback try: - raw_input() - except NameError: - input() - sys.exit(1) - -# Arguments are: -# 1. Working directory. -# 2. VS debugger port to connect to. -# 3. GUID for the debug session. -# 4. Debug options (as integer - see enum PythonDebugOptions). -# 5. '-m' or '-c' to override the default run-as mode. [optional] -# 6. Startup script name. -# 7. Script arguments. - -# change to directory we expected to start from -os.chdir(sys.argv[1]) - -port_num = int(sys.argv[2]) -debug_id = sys.argv[3] -debug_options = vspd.parse_debug_options(sys.argv[4]) -del sys.argv[0:5] - -# set run_as mode appropriately -run_as = 'script' -if sys.argv and sys.argv[0] == '-m': - run_as = 'module' - del sys.argv[0] -if sys.argv and sys.argv[0] == '-c': - run_as = 'code' - del sys.argv[0] - -# preserve filename before we del sys -filename = sys.argv[0] - -# fix sys.path to be the script file dir -sys.path[0] = '' - -# exclude ourselves from being debugged -vspd.DONT_DEBUG.append(os.path.normcase(__file__)) - -## Begin modification by Don Jayamanne -# Get current Process id to pass back to debugger -currentPid = os.getpid() -## End Modification by Don Jayamanne - -# remove all state we imported -del sys, os - -# and start debugging -## Begin modification by Don Jayamanne -# Pass current Process id to pass back to debugger -vspd.debug(filename, port_num, debug_id, debug_options, currentPid, run_as) -## End Modification by Don Jayamanne + import visualstudio_py_debugger as vspd + except: + traceback.print_exc() + print(''' + Internal error detected. Please copy the above traceback and report at + http://go.microsoft.com/fwlink/?LinkId=293415 + + Press Enter to close. . .''') + try: + raw_input() + except NameError: + input() + sys.exit(1) + + # Arguments are: + # 1. Working directory. + # 2. VS debugger port to connect to. + # 3. GUID for the debug session. + # 4. Debug options (as integer - see enum PythonDebugOptions). + # 5. '-m' or '-c' to override the default run-as mode. [optional] + # 6. Startup script name. + # 7. Script arguments. + + # change to directory we expected to start from + os.chdir(sys.argv[1]) + + port_num = int(sys.argv[2]) + debug_id = sys.argv[3] + debug_options = vspd.parse_debug_options(sys.argv[4]) + del sys.argv[0:5] + + # set run_as mode appropriately + run_as = 'script' + if sys.argv and sys.argv[0] == '-m': + run_as = 'module' + del sys.argv[0] + if sys.argv and sys.argv[0] == '-c': + run_as = 'code' + del sys.argv[0] + + # preserve filename before we del sys + filename = sys.argv[0] + + # fix sys.path to be the script file dir + sys.path[0] = '' + + # exclude ourselves from being debugged + vspd.DONT_DEBUG.append(os.path.normcase(__file__)) + + ## Begin modification by Don Jayamanne + # Get current Process id to pass back to debugger + currentPid = os.getpid() + ## End Modification by Don Jayamanne + + # remove all state we imported + del sys, os + + # and start debugging + ## Begin modification by Don Jayamanne + # Pass current Process id to pass back to debugger + vspd.debug(filename, port_num, debug_id, debug_options, currentPid, run_as) + ## End Modification by Don Jayamanne + + +if __name__ == "__main__": + launch() diff --git a/src/client/debugger/Common/Utils.ts b/src/client/debugger/Common/Utils.ts index a380451f2877..ddd9cf6b8c60 100644 --- a/src/client/debugger/Common/Utils.ts +++ b/src/client/debugger/Common/Utils.ts @@ -112,6 +112,9 @@ export function getPythonExecutable(pythonPath: string): string { } function isValidPythonPath(pythonPath): boolean { + if (fs.existsSync(pythonPath)) { + return true; + } try { let output = child_process.execFileSync(pythonPath, ['-c', 'print(1234)'], { encoding: 'utf8' }); return output.startsWith('1234'); From e98038c2b8d5a685035468ec3e43ca09b343eade Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Thu, 15 Jun 2017 16:12:53 -0700 Subject: [PATCH 002/433] Fix #298 --- pythonFiles/PythonTools/visualstudio_py_debugger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pythonFiles/PythonTools/visualstudio_py_debugger.py b/pythonFiles/PythonTools/visualstudio_py_debugger.py index dc3f5643ce69..19a9d9c4bb1c 100644 --- a/pythonFiles/PythonTools/visualstudio_py_debugger.py +++ b/pythonFiles/PythonTools/visualstudio_py_debugger.py @@ -1312,7 +1312,8 @@ def unblock(self): assert self._is_blocked assert self.id != thread.get_ident() # only someone else should unblock us - self._block_lock.release() + if self._block_lock.locked(): + self._block_lock.release() def schedule_work(self, work): self.unblock_work = work From 3c3a8a50b036e11e3e9c264a763ff9f75c180297 Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Sat, 1 Jul 2017 01:41:50 -0700 Subject: [PATCH 003/433] sys.path[0] should only be reset when debugging a file --- pythonFiles/PythonTools/visualstudio_py_debugger.py | 2 ++ pythonFiles/PythonTools/visualstudio_py_launcher.py | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pythonFiles/PythonTools/visualstudio_py_debugger.py b/pythonFiles/PythonTools/visualstudio_py_debugger.py index 19a9d9c4bb1c..d092d95a0eaa 100644 --- a/pythonFiles/PythonTools/visualstudio_py_debugger.py +++ b/pythonFiles/PythonTools/visualstudio_py_debugger.py @@ -2622,6 +2622,8 @@ def debug(file, port_num, debug_id, debug_options, currentPid, run_as = 'script' elif run_as == 'code': exec_code(file, '', globals_obj) else: + # fix sys.path to be the script file dir + sys.path[0] = '' exec_file(file, globals_obj) finally: sys.settrace(None) diff --git a/pythonFiles/PythonTools/visualstudio_py_launcher.py b/pythonFiles/PythonTools/visualstudio_py_launcher.py index 20bd34d237ba..9e202ff2064e 100644 --- a/pythonFiles/PythonTools/visualstudio_py_launcher.py +++ b/pythonFiles/PythonTools/visualstudio_py_launcher.py @@ -72,9 +72,6 @@ def launch(): # preserve filename before we del sys filename = sys.argv[0] - # fix sys.path to be the script file dir - sys.path[0] = '' - # exclude ourselves from being debugged vspd.DONT_DEBUG.append(os.path.normcase(__file__)) From 18ba91e52ef14358aeaa4dbd951c38da8dc31bd7 Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Tue, 1 Aug 2017 14:38:50 -0700 Subject: [PATCH 004/433] Enable debugger engine to notify a UI server (if one exists) about its existence/readiness to attach --- pythonFiles/PythonTools/ptvsd/__init__.py | 10 +- .../PythonTools/ptvsd/attach_server.py | 87 ++++++- .../ptvsd/visualstudio_py_debugger.py | 232 +++++++++--------- 3 files changed, 197 insertions(+), 132 deletions(-) diff --git a/pythonFiles/PythonTools/ptvsd/__init__.py b/pythonFiles/PythonTools/ptvsd/__init__.py index 25765e165f14..75bb35ec764a 100644 --- a/pythonFiles/PythonTools/ptvsd/__init__.py +++ b/pythonFiles/PythonTools/ptvsd/__init__.py @@ -1,22 +1,22 @@ # Python Tools for Visual Studio # Copyright(c) Microsoft Corporation # All rights reserved. -# +# # Licensed under the Apache License, Version 2.0 (the License); you may not use # this file except in compliance with the License. You may obtain a copy of the # License at http://www.apache.org/licenses/LICENSE-2.0 -# +# # THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS # OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY # IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, # MERCHANTABLITY OR NON-INFRINGEMENT. -# +# # See the Apache Version 2.0 License for specific language governing # permissions and limitations under the License. __author__ = "Microsoft Corporation " __version__ = "3.0.0.0" -__all__ = ['enable_attach', 'wait_for_attach', 'break_into_debugger', 'settrace', 'is_attached', 'AttachAlreadyEnabledError'] +__all__ = ['enable_attach', 'enable_attach_ui', 'wait_for_attach', 'break_into_debugger', 'set_trace', 'is_attached', 'AttachAlreadyEnabledError'] -from ptvsd.attach_server import enable_attach, wait_for_attach, break_into_debugger, settrace, is_attached, AttachAlreadyEnabledError \ No newline at end of file +from ptvsd.attach_server import enable_attach, enable_attach_ui, wait_for_attach, break_into_debugger, set_trace, is_attached, AttachAlreadyEnabledError diff --git a/pythonFiles/PythonTools/ptvsd/attach_server.py b/pythonFiles/PythonTools/ptvsd/attach_server.py index 743c89be787f..469a6fbe4dba 100644 --- a/pythonFiles/PythonTools/ptvsd/attach_server.py +++ b/pythonFiles/PythonTools/ptvsd/attach_server.py @@ -1,16 +1,16 @@ # Python Tools for Visual Studio # Copyright(c) Microsoft Corporation # All rights reserved. -# +# # Licensed under the Apache License, Version 2.0 (the License); you may not use # this file except in compliance with the License. You may obtain a copy of the # License at http://www.apache.org/licenses/LICENSE-2.0 -# +# # THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS # OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY # IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, # MERCHANTABLITY OR NON-INFRINGEMENT. -# +# # See the Apache Version 2.0 License for specific language governing # permissions and limitations under the License. @@ -37,6 +37,14 @@ except ImportError: ssl = None +import json +import errno +from socket import error as socket_error +try: + from urllib.request import urlopen, Request, URLError +except ImportError: + from urllib2 import urlopen, Request, URLError + import ptvsd.visualstudio_py_debugger as vspd import ptvsd.visualstudio_py_repl as vspr from ptvsd.visualstudio_py_util import to_bytes, read_bytes, read_int, read_string, write_bytes, write_int, write_string @@ -66,12 +74,12 @@ # (int64), and then the Python language version that the server is running represented by three int64s - # major, minor, micro; From there on the socket is assumed to be using the normal PTVS debugging protocol. # If attaching was not successful (which can happen if some other debugger is already attached), the server -# responds with 'RJCT' and closes the connection. +# responds with 'RJCT' and closes the connection. # # 'REPL' # Attach REPL to the process. If successful, the server responds with 'ACPT', and from there on the socket # is assumed to be using the normal PTVS REPL protocol. If not successful (which can happen if there is -# no debugger attached), the server responds with 'RJCT' and closes the connection. +# no debugger attached), the server responds with 'RJCT' and closes the connection. PTVS_VER = '2.2' DEFAULT_PORT = 5678 @@ -82,17 +90,20 @@ INFO = to_bytes('INFO') ATCH = to_bytes('ATCH') REPL = to_bytes('REPL') +DEBUGGER_UI_PORT = 9615 _attach_enabled = False _attached = threading.Event() +_attach_port = None +_ui_attach_enabled = False +_ui_attach_options = {} vspd.DONT_DEBUG.append(os.path.normcase(__file__)) class AttachAlreadyEnabledError(Exception): """`ptvsd.enable_attach` has already been called in this process.""" - -def enable_attach(secret, address = ('0.0.0.0', DEFAULT_PORT), certfile = None, keyfile = None, redirect_output = True): +def enable_attach(secret, address, certfile = None, keyfile = None, redirect_output = True): """Enables Python Tools for Visual Studio to attach to this process remotely to debug Python code. @@ -105,7 +116,7 @@ def enable_attach(secret, address = ('0.0.0.0', DEFAULT_PORT), certfile = None, hostname by ``'@'``, e.g.: ``'secret@myhost.cloudapp.net:5678'``. If secret is ``None``, there's no validation, and any client can connect freely. - address : (str, int), optional + address : (str, int), optional Specifies the interface and port on which the debugging server should listen for TCP connections. It is in the same format as used for regular sockets of the `socket.AF_INET` family, i.e. a tuple of @@ -116,7 +127,7 @@ def enable_attach(secret, address = ('0.0.0.0', DEFAULT_PORT), certfile = None, Used to enable SSL. If not specified, or if set to ``None``, the connection between this program and the debugger will be unsecure, and can be intercepted on the wire. If specified, the meaning of this - parameter is the same as for `ssl.wrap_socket`. + parameter is the same as for `ssl.wrap_socket`. keyfile : str, optional Used together with `certfile` when SSL is enabled. Its meaning is the same as for ``ssl.wrap_socket``. @@ -131,7 +142,7 @@ def enable_attach(secret, address = ('0.0.0.0', DEFAULT_PORT), certfile = None, is attached, call `ptvsd.wait_for_attach`. The debugger can be detached and re-attached multiple times after `enable_attach` is called. - This function can only be called once during the lifetime of the process. + This function can only be called once during the lifetime of the process. On a second call, `AttachAlreadyEnabledError` is raised. In circumstances where the caller does not control how many times the function will be called (e.g. when a script with a single call is run more than once by @@ -164,8 +175,15 @@ def enable_attach(secret, address = ('0.0.0.0', DEFAULT_PORT), certfile = None, server = socket.socket(proto=socket.IPPROTO_TCP) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + if address is None: + if register_options is not None: + address = ('0.0.0.0', 0) + else: + address = ('0.0.0.0', DEFAULT_PORT) server.bind(address) server.listen(1) + global _attach_port + _attach_port = server.getsockname()[1] def server_thread_func(): while True: client = None @@ -298,8 +316,53 @@ def replace_trace_func(): vspd.intercept_threads(for_attach = True) -# Alias for convenience of users of pydevd -settrace = enable_attach +# `set_trace` should pause debug execution and attach the debugger UI to the debugger engine +def set_trace(options=None): + # Enable on-demand UI attach to the debugger. + enable_attach_ui(options) + # Trigger the debugger ui to attach, if one exists + debugger_ui_attach() + wait_for_attach() + break_into_debugger() + + +def enable_attach_ui(options): + global _attach_enabled, _attach_port, _ui_attach_enabled, _ui_attach_options + if not _attach_enabled: + enable_attach(None, ('0.0.0.0', 0)) + _ui_attach_options = options if options is not None else _ui_attach_options + if not _ui_attach_enabled: + _ui_attach_enabled = debugger_ui_enable_attach() + + +def debugger_ui_attach(): + if not vspd.DETACHED: + return + global _attach_port + attach_info = {"domain": "debug", "type": "python", "command": "attach", "port": _attach_port} + return debugger_ui_request(attach_info) + + +def debugger_ui_enable_attach(): + global _attach_port, _ui_attach_options + attach_info = {"domain": "debug", "type": "python", "command": "enable-attach", + "port": _attach_port, "options": _ui_attach_options} + return debugger_ui_request(attach_info) + + +def debugger_ui_request(info): + req = Request('http://localhost:' + str(DEBUGGER_UI_PORT), + data=json.dumps(info).encode('utf8'), + headers={'Content-Type': 'application/json', 'Accept': 'application/json'}) + try: + response = urlopen(req) + except URLError: + # It's okay if there's no debugger ui server waiting for that info + return False + json_response = json.loads(response.read()) + if not json_response['success']: + raise RuntimeError('Failed attach attempt') + return True def wait_for_attach(timeout = None): diff --git a/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py b/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py index b589a8208baa..2263a267585f 100644 --- a/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py +++ b/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py @@ -1,16 +1,16 @@ # Python Tools for Visual Studio # Copyright(c) Microsoft Corporation # All rights reserved. -# +# # Licensed under the Apache License, Version 2.0 (the License); you may not use # this file except in compliance with the License. You may obtain a copy of the # License at http://www.apache.org/licenses/LICENSE-2.0 -# +# # THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS # OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY # IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, # MERCHANTABLITY OR NON-INFRINGEMENT. -# +# # See the Apache Version 2.0 License for specific language governing # permissions and limitations under the License. # With number of modifications by Don Jayamanne @@ -76,8 +76,10 @@ except ImportError: import ptvsd.visualstudio_py_repl as _vspr + try: import stackless + stackless.tasklet # work-around lazy on-demand importers except ImportError: stackless = None @@ -233,8 +235,8 @@ def find_by_id(breakpoint_id): send_lock = thread.allocate_lock() class _SendLockContextManager(object): - """context manager for send lock. Handles both acquiring/releasing the - send lock as well as detaching the debugger if the remote process + """context manager for send lock. Handles both acquiring/releasing the + send lock as well as detaching the debugger if the remote process is disconnected""" def __enter__(self): @@ -248,7 +250,7 @@ def __enter__(self): def __exit__(self, exc_type, exc_value, tb): send_lock.release() - + # start sending debug events again cur_thread = get_thread_from_id(thread.get_ident()) if cur_thread is not None: @@ -258,8 +260,8 @@ def __exit__(self, exc_type, exc_value, tb): detach_threads() detach_process() # swallow the exception, we're no longer debugging - return True - + return True + _SendLockCtx = _SendLockContextManager() SEND_BREAK_COMPLETE = False @@ -334,7 +336,7 @@ def is_repr_round_tripping(x): try: # getfilesystemencoding is used here because it effectively corresponds to the notion of "locale encoding": # current ANSI codepage on Windows, LC_CTYPE on Linux, UTF-8 on OS X - which is exactly what we want. - TYPES_WITH_RAW_REPR[bytearray] = lambda b: b.decode(sys.getfilesystemencoding(), 'ignore') + TYPES_WITH_RAW_REPR[bytearray] = lambda b: b.decode(sys.getfilesystemencoding(), 'ignore') except: pass @@ -350,7 +352,7 @@ def is_repr_round_tripping(x): class StackOverflowException(Exception): pass else: StackOverflowException = RuntimeError - + ASBR = to_bytes('ASBR') SETL = to_bytes('SETL') THRF = to_bytes('THRF') @@ -420,7 +422,7 @@ def lookup_local(frame, name): while bits and obj is not None and type(obj) is types.ModuleType: obj = getattr(obj, bits.pop(0), None) return obj - + if sys.version_info[0] >= 3: _EXCEPTIONS_MODULE = 'builtins' else: @@ -489,7 +491,7 @@ def should_break(self, thread, ex_type, ex_value, trace): break_type = BREAK_TYPE_NONE return break_type - + def is_handled(self, thread, ex_type, ex_value, trace): if trace is None: # get out if we didn't get a traceback @@ -500,9 +502,9 @@ def is_handled(self, thread, ex_type, ex_value, trace): # don't break if this is not the top of the traceback, # unless the previous frame was not debuggable return True - + cur_frame = trace.tb_frame - + while should_send_frame(cur_frame) and cur_frame.f_code is not None and cur_frame.f_code.co_filename is not None: filename = path.normcase(cur_frame.f_code.co_filename) if is_file_in_zip(filename): @@ -511,11 +513,11 @@ def is_handled(self, thread, ex_type, ex_value, trace): if not is_same_py_file(filename, __file__): handlers = self.handler_cache.get(filename) - + if handlers is None: # req handlers for this file from the debug engine self.handler_lock.acquire() - + with _SendLockCtx: write_bytes(conn, REQH) write_string(conn, filename) @@ -547,7 +549,7 @@ def is_handled(self, thread, ex_type, ex_value, trace): cur_frame = cur_frame.f_back return False - + def add_exception(self, name, mode=BREAK_MODE_UNHANDLED): if name.startswith(_EXCEPTIONS_MODULE + '.'): name = name[len(_EXCEPTIONS_MODULE) + 1:] @@ -556,7 +558,7 @@ def add_exception(self, name, mode=BREAK_MODE_UNHANDLED): BREAK_ON = ExceptionBreakInfo() def probe_stack(depth = 10): - """helper to make sure we have enough stack space to proceed w/o corrupting + """helper to make sure we have enough stack space to proceed w/o corrupting debugger state.""" if depth == 0: return @@ -599,7 +601,7 @@ def breakpoint_path_match(vs_path, local_path): local_path_norm = path.normcase(local_path) if local_path_to_vs_path.get(local_path_norm) == vs_path_norm: return True - + # Walk the local filesystem from local_path up, matching agains win_path component by component, # and stop when we no longer see an __init__.py. This should give a reasonably close approximation # of matching the package name. @@ -615,7 +617,7 @@ def breakpoint_path_match(vs_path, local_path): # needed to, and matched all names on our way, so this is a match. if not path.exists(path.join(local_path, '__init__.py')): break - + local_path_to_vs_path[local_path_norm] = vs_path_norm return True @@ -623,26 +625,26 @@ def update_all_thread_stacks(blocking_thread = None, check_is_blocked = True): THREADS_LOCK.acquire() all_threads = list(THREADS.values()) THREADS_LOCK.release() - + for cur_thread in all_threads: if cur_thread is blocking_thread: continue - + cur_thread._block_starting_lock.acquire() if not check_is_blocked or not cur_thread._is_blocked: # release the lock, we're going to run user code to evaluate the frames - cur_thread._block_starting_lock.release() - + cur_thread._block_starting_lock.release() + frames = cur_thread.get_frame_list() - + # re-acquire the lock and make sure we're still not blocked. If so send # the frame list. cur_thread._block_starting_lock.acquire() if not check_is_blocked or not cur_thread._is_blocked: cur_thread.send_frame_list(frames) - + cur_thread._block_starting_lock.release() - + DJANGO_BREAKPOINTS = {} DJANGO_TEMPLATES = {} @@ -652,7 +654,7 @@ def __init__(self, filename): self.filename = filename self.breakpoints = {} self.rangeIsPlainText = {} - + def add_breakpoint(self, lineno, brkpt_id): self.breakpoints[lineno] = brkpt_id @@ -673,20 +675,20 @@ def is_range_plain_text(self, start, end): with open(self.filename, 'rb') as contents: contents.seek(start, 0) # 0 = start of file, optional in this case data = contents.read(end - start) - isPlainText = True + isPlainText = True if data.startswith('{{') and data.endswith('}}'): isPlainText = False if data.startswith('{%') and data.endswith('%}'): isPlainText = False self.rangeIsPlainText[key] = isPlainText - return isPlainText + return isPlainText except: return False else: return self.rangeIsPlainText.get(key) def line_number_to_offset(self, lineNumber): - line_locs = self.line_locations + line_locs = self.line_locations if line_locs is not None: low_line = line_locs[lineNumber - 1] hi_line = line_locs[lineNumber] @@ -722,7 +724,7 @@ def line_locations(self): return self._line_locations def get_line_range(self, start, end): - line_locs = self.line_locations + line_locs = self.line_locations if line_locs is not None: low_line = bisect.bisect_right(line_locs, start) hi_line = bisect.bisect_right(line_locs, end) @@ -735,10 +737,10 @@ def should_break(self, start, end): low_line, hi_line = self.get_line_range(start, end) if low_line is not None and hi_line is not None: # low_line/hi_line is 0 based, self.breakpoints is 1 based - for i in xrange(low_line+1, hi_line+2): + for i in xrange(low_line+1, hi_line+2): bkpt_id = self.breakpoints.get(i) if bkpt_id is not None: - return True, bkpt_id + return True, bkpt_id return False, 0 @@ -756,7 +758,7 @@ def get_django_frame_source(frame): IS_DJANGO19 = version[0] == 1 and version[1] == 9 IS_DJANGO19_OR_HIGHER = ((version[0] == 1 and version[1] >= 9) or version[0] > 1) except: - pass + pass if frame.f_code.co_name == 'render': self_obj = frame.f_locals.get('self', None) if self_obj is None: @@ -794,12 +796,12 @@ def __getattr__(self, name): class Thread(object): def __init__(self, id = None): if id is not None: - self.id = id + self.id = id else: self.id = thread.get_ident() - self._events = {'call' : self.handle_call, - 'line' : self.handle_line, - 'return' : self.handle_return, + self._events = {'call' : self.handle_call, + 'line' : self.handle_line, + 'return' : self.handle_return, 'exception' : self.handle_exception, 'c_call' : self.handle_c_call, 'c_return' : self.handle_c_return, @@ -865,7 +867,7 @@ def new_f(old_f, args, kwargs): tsk.tempval = new_f stackless.tasklet.setup(tsk, f, args, kwargs) return tsk - + def settrace(tsk, tb): if hasattr(tsk.frame, "f_trace"): tsk.frame.f_trace = tb @@ -876,14 +878,14 @@ def settrace(tsk, tb): stackless.tasklet.__call__ = __call__ if sys.platform == 'cli': self.frames = [] - + if sys.platform == 'cli': # workaround an IronPython bug where we're sometimes missing the back frames # http://ironpython.codeplex.com/workitem/31437 def push_frame(self, frame): self.cur_frame = frame self.frames.append(frame) - + def pop_frame(self): self.frames.pop() self.cur_frame = self.frames[-1] @@ -908,11 +910,11 @@ def _stackless_schedule_cb(self, prev, next): if not current: return current_tf = current.trace_function - + try: current.trace_function = None self.stepping = STEPPING_NONE - + # If the current frame has no trace function, we may need to get it # from the previous frame, depending on how we ended up in the # callback. @@ -939,7 +941,7 @@ def trace_func(self, frame, event, arg): if sys is None: return None elif self.is_sending: - # https://pytools.codeplex.com/workitem/1864 + # https://pytools.codeplex.com/workitem/1864 # we're currently doing I/O w/ the socket, we don't want to deliver # any breakpoints or async breaks because we'll deadlock. Continue # to return the trace function so all of our frames remain @@ -953,7 +955,7 @@ def trace_func(self, frame, event, arg): try: # if should_debug_code(frame.f_code) is not true during attach - # the current frame is None and a pop_frame will cause an exception and + # the current frame is None and a pop_frame will cause an exception and # break the debugger if self.cur_frame is None: # happens during attach, we need frame for blocking @@ -972,7 +974,7 @@ def trace_func(self, frame, event, arg): except (StackOverflowException, KeyboardInterrupt): # stack overflow, disable tracing return self.trace_func - + def handle_call(self, frame, arg): self.push_frame(frame) @@ -980,10 +982,10 @@ def handle_call(self, frame, arg): source_obj = get_django_frame_source(frame) if source_obj is not None: origin, (start, end), lineNumber = source_obj - + active_bps = DJANGO_BREAKPOINTS.get(origin.lower()) should_break = False - if active_bps is not None and origin != '': + if active_bps is not None and origin != '': should_break, bkpt_id = active_bps.should_break(start, end) isPlainText = active_bps.is_range_plain_text(start, end) if isPlainText: @@ -1023,8 +1025,8 @@ def handle_call(self, frame, arg): elif stepping <= STEPPING_OUT: self.stepping -= 1 - if (sys.platform == 'cli' and - frame.f_code.co_name == '' and + if (sys.platform == 'cli' and + frame.f_code.co_name == '' and not IPY_SEEN_MODULES.TryGetValue(frame.f_code)[0]): IPY_SEEN_MODULES.Add(frame.f_code, None) # work around IronPython bug - http://ironpython.codeplex.com/workitem/30127 @@ -1038,7 +1040,7 @@ def handle_call(self, frame, arg): self.prev_trace_func = old_trace_func(frame, 'call', arg) return self.trace_func - + def should_block_on_frame(self, frame): if not should_debug_code(frame.f_code): return False @@ -1091,7 +1093,7 @@ def handle_line(self, frame, arg): # the module to which it was bound, so only exact matches are considered hits. if bp.is_bound: continue - # Otherwise, use relaxed path check that tries to handle differences between + # Otherwise, use relaxed path check that tries to handle differences between # local and remote filesystems for remote scenarios: if not breakpoint_path_match(filename, frame.f_code.co_filename): continue @@ -1159,7 +1161,7 @@ def handle_line(self, frame, arg): self.prev_trace_func = old_trace_func(frame, 'line', arg) return self.trace_func - + def handle_return(self, frame, arg): self.pop_frame() @@ -1196,7 +1198,7 @@ def handle_return(self, frame, arg): # restore previous frames trace function if there is one if self.trace_func_stack: self.prev_trace_func = self.trace_func_stack.pop() - + def handle_exception(self, frame, arg): if self.stepping == STEPPING_ATTACH_BREAK: self.block_maybe_attach() @@ -1214,15 +1216,15 @@ def handle_exception(self, frame, arg): self.prev_trace_func = old_trace_func(frame, 'exception', arg) return self.trace_func - + def handle_c_call(self, frame, arg): # break points? pass - + def handle_c_return(self, frame, arg): # step out of ? pass - + def handle_c_exception(self, frame, arg): pass @@ -1236,7 +1238,7 @@ def block_maybe_attach(self): will_block_now = False attach_sent_break = True attach_lock.release() - + probe_stack() stepping = self.stepping self.stepping = STEPPING_NONE @@ -1252,7 +1254,7 @@ def block_cond(): return report_process_loaded(self.id) update_all_thread_stacks(self) self.block(block_cond) - + def async_break(self): def async_break_send(): with _SendLockCtx: @@ -1266,7 +1268,7 @@ def async_break_send(): write_int(conn, self.id) if sent_break_complete: - # if we have threads which have not broken yet capture their frame list and + # if we have threads which have not broken yet capture their frame list and # send it now. If they block we'll send an updated (and possibly more accurate - if # there are any thread locals) list of frames. update_all_thread_stacks(self) @@ -1278,10 +1280,10 @@ def block(self, block_lambda, keep_stopped_on_line = False): """blocks the current thread until the debugger resumes it""" assert not self._is_blocked #assert self.id == thread.get_ident(), 'wrong thread identity' + str(self.id) + ' ' + str(thread.get_ident()) # we should only ever block ourselves - + # send thread frames before we block self.enum_thread_frames_locally() - + if not keep_stopped_on_line: self.stopped_on_line = self.cur_frame.f_lineno @@ -1301,7 +1303,7 @@ def block(self, block_lambda, keep_stopped_on_line = False): self.unblock_work() self.unblock_work = None self._is_working = False - + self._block_starting_lock.acquire() assert self._is_blocked self._is_blocked = False @@ -1309,9 +1311,9 @@ def block(self, block_lambda, keep_stopped_on_line = False): def unblock(self): """unblocks the current thread allowing it to continue to run""" - assert self._is_blocked + assert self._is_blocked assert self.id != thread.get_ident() # only someone else should unblock us - + self._block_lock.release() def schedule_work(self, work): @@ -1320,26 +1322,26 @@ def schedule_work(self, work): def run_on_thread(self, text, cur_frame, execution_id, frame_kind, repr_kind = PYTHON_EVALUATION_RESULT_REPR_KIND_NORMAL): self._block_starting_lock.acquire() - + if not self._is_blocked: report_execution_error('', execution_id) elif not self._is_working: self.schedule_work(lambda : self.run_locally(text, cur_frame, execution_id, frame_kind, repr_kind)) else: report_execution_error('', execution_id) - + self._block_starting_lock.release() def run_on_thread_no_report(self, text, cur_frame, frame_kind): self._block_starting_lock.acquire() - + if not self._is_blocked: pass elif not self._is_working: self.schedule_work(lambda : self.run_locally_no_report(text, cur_frame, frame_kind)) else: pass - + self._block_starting_lock.release() def enum_child_on_thread(self, text, cur_frame, execution_id, frame_kind): @@ -1463,7 +1465,7 @@ def enum_child_locally(self, expr, cur_frame, execution_id, frame_kind): break key_repr = safe_repr(key) - + # Some objects are enumerable but not indexable, or repr(key) is not a valid Python expression. For those, we # cannot use obj[key] to get the item by its key, and have to retrieve it by index from enumerate() instead. try: @@ -1494,7 +1496,7 @@ def enum_child_locally(self, expr, cur_frame, execution_id, frame_kind): def get_frame_list(self): frames = [] cur_frame = self.cur_frame - + while should_send_frame(cur_frame): # calculate the ending line number lineno = cur_frame.f_code.co_firstlineno @@ -1541,7 +1543,7 @@ def get_frame_list(self): f_globals = cur_frame.f_globals if f_globals: # ensure globals to work with (IPy may have None for cur_frame.f_globals for frames within stdlib) self.collect_variables(vars, f_globals, cur_frame.f_code.co_names, treated, skip_unknown = True) - + frame_info = None if source_obj is not None: @@ -1557,8 +1559,8 @@ def get_frame_list(self): frame_kind = FRAME_KIND_DJANGO frame_info = ( low_line + 1, - hi_line + 1, - low_line + 1, + hi_line + 1, + low_line + 1, cur_frame.f_code.co_name, str(origin), 0, @@ -1571,8 +1573,8 @@ def get_frame_list(self): if frame_info is None: frame_info = ( cur_frame.f_code.co_firstlineno, - lineno, - cur_frame.f_lineno, + lineno, + cur_frame.f_lineno, cur_frame.f_code.co_name, get_code_filename(cur_frame.f_code), cur_frame.f_code.co_argcount, @@ -1583,9 +1585,9 @@ def get_frame_list(self): ) frames.append(frame_info) - + cur_frame = cur_frame.f_back - + return frames def collect_variables(self, vars, objects, names, treated, skip_unknown = False): @@ -1613,24 +1615,24 @@ def send_frame_list(self, frames, thread_name = None): write_bytes(conn, THRF) write_int(conn, self.id) write_string(conn, thread_name) - + # send the frame count write_int(conn, len(frames)) for firstlineno, lineno, curlineno, name, filename, argcount, variables, frameKind, sourceFile, sourceLine in frames: - # send each frame + # send each frame write_int(conn, firstlineno) write_int(conn, lineno) write_int(conn, curlineno) - + write_string(conn, name) write_string(conn, filename) write_int(conn, argcount) - + write_int(conn, frameKind) if frameKind == FRAME_KIND_DJANGO: write_string(conn, sourceFile) write_int(conn, sourceLine) - + write_int(conn, len(variables)) for name, type_obj, safe_repr_obj, hex_repr_obj, type_name, obj_len in variables: write_string(conn, name) @@ -1737,7 +1739,7 @@ def loop(self): pass except: traceback.print_exc() - + def command_step_into(self): tid = read_int(self.conn) thread = get_thread_from_id(tid) @@ -1753,7 +1755,7 @@ def command_step_out(self): assert thread._is_blocked thread.stepping = STEPPING_OUT self.command_resume_all() - + def command_step_over(self): # set step over tid = read_int(self.conn) @@ -1793,7 +1795,7 @@ def command_set_breakpoint_condition(self): breakpoint_id = read_int(self.conn) kind = read_int(self.conn) condition = read_string(self.conn) - + bp = BreakpointInfo.find_by_id(breakpoint_id) if bp is not None: bp.condition_kind = kind @@ -1812,7 +1814,7 @@ def command_set_breakpoint_pass_count(self): def command_set_breakpoint_hit_count(self): breakpoint_id = read_int(self.conn) count = read_int(self.conn) - + bp = BreakpointInfo.find_by_id(breakpoint_id) if bp is not None: bp.hit_count = count @@ -1820,7 +1822,7 @@ def command_set_breakpoint_hit_count(self): def command_get_breakpoint_hit_count(self): req_id = read_int(self.conn) breakpoint_id = read_int(self.conn) - + bp = BreakpointInfo.find_by_id(breakpoint_id) count = 0 if bp is not None: @@ -1907,7 +1909,7 @@ def command_resume_all(self): if thread._is_blocked: thread.unblock() thread._block_starting_lock.release() - + def command_resume_thread(self): tid = read_int(self.conn) THREADS_LOCK.acquire() @@ -1927,7 +1929,7 @@ def command_auto_resume(self): THREADS_LOCK.release() stepping = thread.stepping - if ((stepping == STEPPING_OVER or stepping == STEPPING_INTO) and thread.cur_frame.f_lineno != thread.stopped_on_line): + if ((stepping == STEPPING_OVER or stepping == STEPPING_INTO) and thread.cur_frame.f_lineno != thread.stopped_on_line): report_step_finished(tid) else: self.command_resume_all() @@ -2020,11 +2022,11 @@ def command_enum_children(self): fid = read_int(self.conn) # frame id eid = read_int(self.conn) # execution id frame_kind = read_int(self.conn) # frame kind - + thread, cur_frame = self.get_thread_and_frame(tid, fid, frame_kind) if thread is not None and cur_frame is not None: thread.enum_child_on_thread(text, cur_frame, eid, frame_kind) - + def get_thread_and_frame(self, tid, fid, frame_kind): thread = get_thread_from_id(tid) cur_frame = None @@ -2049,11 +2051,11 @@ def command_detach(self): with _SendLockCtx: write_bytes(conn, DETC) - detach_process() + detach_process() for callback in DETACH_CALLBACKS: callback() - + raise DebuggerExitException() def command_last_ack(self): @@ -2099,12 +2101,12 @@ def report_exception(frame, exc_info, tid, break_type): exc_name = get_exception_name(exc_type) exc_value = exc_info[1] tb_value = exc_info[2] - + if type(exc_value) is tuple: - # exception object hasn't been created yet, create it now + # exception object hasn't been created yet, create it now # so we can get the correct msg. exc_value = exc_type(*exc_value) - + data = { 'typename': get_exception_name(exc_type), 'message': str(exc_value), @@ -2172,7 +2174,7 @@ def report_breakpoint_failed(id): write_bytes(conn, BRKF) write_int(conn, id) -def report_breakpoint_hit(id, tid): +def report_breakpoint_hit(id, tid): with _SendLockCtx: write_bytes(conn, BRKH) write_int(conn, id) @@ -2217,7 +2219,7 @@ def report_execution_result(execution_id, result, repr_kind = PYTHON_EVALUATION_ hex_repr = safe_hex_repr(result) else: flags = PYTHON_EVALUATION_RESULT_RAW - hex_repr = None + hex_repr = None for cls, raw_repr in TYPES_WITH_RAW_REPR.items(): if isinstance(result, cls): try: @@ -2311,7 +2313,7 @@ def attach_process(port_num, debug_id, debug_options, currentPid, report = False ## Begin modification by Don Jayamanne # Pass current Process id to pass back to debugger write_int(conn, currentPid) # success - ## End Modification by Don Jayamanne + ## End Modification by Don Jayamanne break except: import time @@ -2370,7 +2372,7 @@ def _excepthook(exc_type, exc_value, exc_tb): else: MODULES.append((filename, Module(fullpath))) except: - traceback.print_exc() + traceback.print_exc() if report: THREADS_LOCK.acquire() @@ -2409,7 +2411,7 @@ def detach_process(): global DETACHED DETACHED = True if not _INTERCEPTING_FOR_ATTACH: - if isinstance(sys.stdout, _DebuggerOutput): + if isinstance(sys.stdout, _DebuggerOutput): sys.stdout = sys.stdout.old_out if isinstance(sys.stderr, _DebuggerOutput): sys.stderr = sys.stderr.old_out @@ -2436,7 +2438,7 @@ def detach_threads(): THREADS_LOCK.acquire() THREADS.clear() THREADS_LOCK.release() - + BREAKPOINTS.clear() def new_thread(tid = None, set_break = False, frame = None): @@ -2444,7 +2446,7 @@ def new_thread(tid = None, set_break = False, frame = None): if tid == debugger_thread_id: return None - cur_thread = Thread(tid) + cur_thread = Thread(tid) THREADS_LOCK.acquire() THREADS[cur_thread.id] = cur_thread THREADS_LOCK.release() @@ -2498,11 +2500,11 @@ def __init__(self, old_out, is_stdout): def flush(self): if self.old_out: self.old_out.flush() - + def writelines(self, lines): for line in lines: self.write(line) - + @property def encoding(self): return 'utf8' @@ -2516,13 +2518,13 @@ def write(self, value): write_string(conn, value) if self.old_out: self.old_out.write(value) - + def isatty(self): return True def next(self): pass - + @property def name(self): if self.is_stdout: @@ -2547,7 +2549,7 @@ def write(self, data): write_string(conn, str_data) self.buffer.write(data) - def flush(self): + def flush(self): self.buffer.flush() def truncate(self, pos = None): @@ -2561,9 +2563,9 @@ def seek(self, pos, whence = 0): def is_same_py_file(file1, file2): """compares 2 filenames accounting for .pyc files""" - if file1.endswith('.pyc') or file1.endswith('.pyo'): + if file1.endswith('.pyc') or file1.endswith('.pyo'): file1 = file1[:-1] - if file2.endswith('.pyc') or file2.endswith('.pyo'): + if file2.endswith('.pyc') or file2.endswith('.pyo'): file2 = file2[:-1] return file1 == file2 @@ -2583,7 +2585,7 @@ def print_exception(exc_type, exc_value, exc_tb): print('Traceback (most recent call last):') for out in traceback.format_list(tb): sys.stderr.write(out) - + # print the exception for out in traceback.format_exception_only(exc_type, exc_value): sys.stdout.write(out) @@ -2691,7 +2693,7 @@ def _get_source_django_18_or_lower(frame): else: if IGNORE_DJANGO_TEMPLATE_WARNINGS: return None - + if IS_DJANGO18: # The debug setting was changed since Django 1.8 print("WARNING: Template path is not available. Set the 'debug' option in the OPTIONS of a DjangoTemplates " @@ -2749,4 +2751,4 @@ def _get_template_line(frame): return _offset_to_line_number(_read_file(file_name), source[1][0]) except: return None -## End modification by Don Jayamanne \ No newline at end of file +## End modification by Don Jayamanne From 2f71fef622824a7999104619ca6224d5fd6f9b94 Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Tue, 1 Aug 2017 14:55:56 -0700 Subject: [PATCH 005/433] Enable remote debugging of Django apps --- src/client/debugger/Common/Contracts.ts | 16 +++++++++++++--- .../debugger/DebugClients/LocalDebugClient.ts | 9 +-------- .../debugger/DebugServers/RemoteDebugServer.ts | 9 ++++++--- src/client/debugger/Main.ts | 11 +++++------ 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/client/debugger/Common/Contracts.ts b/src/client/debugger/Common/Contracts.ts index 41222ddf0c79..12d5a3d3fbfe 100644 --- a/src/client/debugger/Common/Contracts.ts +++ b/src/client/debugger/Common/Contracts.ts @@ -20,6 +20,15 @@ export class TelemetryEvent extends OutputEvent { } } export const DjangoApp = "DJANGO"; + +export const VALID_DEBUG_OPTIONS = ['WaitOnAbnormalExit', + 'WaitOnNormalExit', + 'RedirectOutput', + 'DebugStdLib', + 'BreakOnSystemExitZero', + 'DjangoDebugging']; + + export enum DebugFlags { None = 0, IgnoreCommandBursts = 1 @@ -60,9 +69,10 @@ export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArgum export interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments { /** An absolute path to local directory with source. */ - localRoot: string; - remoteRoot: string; - port?: number; + debugOptions?: string[]; + localRoot?: string; + remoteRoot?: string; + port: number; host?: string; secret?: string; } diff --git a/src/client/debugger/DebugClients/LocalDebugClient.ts b/src/client/debugger/DebugClients/LocalDebugClient.ts index f81c788af7a2..8dd05a7ecb06 100644 --- a/src/client/debugger/DebugClients/LocalDebugClient.ts +++ b/src/client/debugger/DebugClients/LocalDebugClient.ts @@ -5,18 +5,11 @@ import { DebugSession, OutputEvent } from 'vscode-debugadapter'; import { DebugProtocol } from 'vscode-debugprotocol'; import * as path from 'path'; import * as child_process from 'child_process'; -import { LaunchRequestArguments } from '../Common/Contracts'; +import { LaunchRequestArguments, VALID_DEBUG_OPTIONS } from '../Common/Contracts'; import { DebugClient, DebugType } from './DebugClient'; import { open } from '../../common/open'; import { getCustomEnvVars } from '../Common/Utils'; -const VALID_DEBUG_OPTIONS = ['WaitOnAbnormalExit', - 'WaitOnNormalExit', - 'RedirectOutput', - 'DebugStdLib', - 'BreakOnSystemExitZero', - 'DjangoDebugging']; - export class LocalDebugClient extends DebugClient { protected args: LaunchRequestArguments; constructor(args: any, debugSession: DebugSession) { diff --git a/src/client/debugger/DebugServers/RemoteDebugServer.ts b/src/client/debugger/DebugServers/RemoteDebugServer.ts index f8a617e8d63b..87e5bed9abab 100644 --- a/src/client/debugger/DebugServers/RemoteDebugServer.ts +++ b/src/client/debugger/DebugServers/RemoteDebugServer.ts @@ -1,7 +1,7 @@ "use strict"; import {DebugSession, OutputEvent} from "vscode-debugadapter"; -import {IPythonProcess, IDebugServer, AttachRequestArguments} from "../Common/Contracts"; +import {IPythonProcess, IDebugServer, AttachRequestArguments, VALID_DEBUG_OPTIONS} from "../Common/Contracts"; import * as net from "net"; import {BaseDebugServer} from "./BaseDebugServer"; import {SocketStream} from "../../common/comms/SocketStream"; @@ -136,8 +136,11 @@ export class RemoteDebugServer extends BaseDebugServer { if (!commandBytesWritten) { that.stream.Write(AttachCommandBytes); - let debugOptions = "WaitOnAbnormalExit, WaitOnNormalExit, RedirectOutput"; - that.stream.WriteString(debugOptions); + let vsDebugOptions = 'WaitOnAbnormalExit,WaitOnNormalExit,RedirectOutput'; + if (Array.isArray(this.args.debugOptions)) { + vsDebugOptions = this.args.debugOptions.filter(opt => VALID_DEBUG_OPTIONS.indexOf(opt) >= 0).join(','); + } + that.stream.WriteString(vsDebugOptions); commandBytesWritten = true; } diff --git a/src/client/debugger/Main.ts b/src/client/debugger/Main.ts index 176cdc74ef5e..d460495b5922 100644 --- a/src/client/debugger/Main.ts +++ b/src/client/debugger/Main.ts @@ -302,12 +302,11 @@ export class PythonDebugger extends DebugSession { this.launchArgs.debugOptions.indexOf(DebugOptions.DjangoDebugging) >= 0) { isDjangoFile = filePath.toUpperCase().endsWith(".HTML"); } - // Todo: Remote DJango debugging - // if (this.attachArgs != null && - // Array.isArray(this.attachArgs.debugOptions) && - // this.attachArgs.debugOptions.indexOf(DebugOptions.DjangoDebugging) >= 0) { - // isDjangoFile = filePath.toUpperCase().endsWith(".HTML"); - // } + if (this.attachArgs != null && + Array.isArray(this.attachArgs.debugOptions) && + this.attachArgs.debugOptions.indexOf(DebugOptions.DjangoDebugging) >= 0) { + isDjangoFile = filePath.toUpperCase().endsWith(".HTML"); + } condition = typeof condition === "string" ? condition : ""; From e0b0bbeace21432881ac565332525ac007730575 Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Thu, 3 Aug 2017 16:09:01 -0700 Subject: [PATCH 006/433] Enabling setting UI attach options while not enabling the attachability yet --- pythonFiles/PythonTools/ptvsd/__init__.py | 4 ++-- .../PythonTools/ptvsd/attach_server.py | 24 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/pythonFiles/PythonTools/ptvsd/__init__.py b/pythonFiles/PythonTools/ptvsd/__init__.py index 75bb35ec764a..c1b6fb6cf61e 100644 --- a/pythonFiles/PythonTools/ptvsd/__init__.py +++ b/pythonFiles/PythonTools/ptvsd/__init__.py @@ -17,6 +17,6 @@ __author__ = "Microsoft Corporation " __version__ = "3.0.0.0" -__all__ = ['enable_attach', 'enable_attach_ui', 'wait_for_attach', 'break_into_debugger', 'set_trace', 'is_attached', 'AttachAlreadyEnabledError'] +__all__ = ['enable_attach', 'enable_attach_ui', 'wait_for_attach', 'break_into_debugger', 'set_attach_ui_options', 'set_trace', 'is_attached', 'AttachAlreadyEnabledError'] -from ptvsd.attach_server import enable_attach, enable_attach_ui, wait_for_attach, break_into_debugger, set_trace, is_attached, AttachAlreadyEnabledError +from ptvsd.attach_server import enable_attach, enable_attach_ui, wait_for_attach, break_into_debugger, set_attach_ui_options, set_trace, is_attached, AttachAlreadyEnabledError diff --git a/pythonFiles/PythonTools/ptvsd/attach_server.py b/pythonFiles/PythonTools/ptvsd/attach_server.py index 469a6fbe4dba..49a9ffc5bfb8 100644 --- a/pythonFiles/PythonTools/ptvsd/attach_server.py +++ b/pythonFiles/PythonTools/ptvsd/attach_server.py @@ -103,7 +103,7 @@ class AttachAlreadyEnabledError(Exception): """`ptvsd.enable_attach` has already been called in this process.""" -def enable_attach(secret, address, certfile = None, keyfile = None, redirect_output = True): +def enable_attach(secret, address = ('0.0.0.0', DEFAULT_PORT), certfile = None, keyfile = None, redirect_output = True): """Enables Python Tools for Visual Studio to attach to this process remotely to debug Python code. @@ -175,11 +175,6 @@ def enable_attach(secret, address, certfile = None, keyfile = None, redirect_out server = socket.socket(proto=socket.IPPROTO_TCP) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - if address is None: - if register_options is not None: - address = ('0.0.0.0', 0) - else: - address = ('0.0.0.0', DEFAULT_PORT) server.bind(address) server.listen(1) global _attach_port @@ -317,30 +312,35 @@ def replace_trace_func(): # `set_trace` should pause debug execution and attach the debugger UI to the debugger engine -def set_trace(options=None): +def set_trace(): # Enable on-demand UI attach to the debugger. - enable_attach_ui(options) + enable_attach_ui() # Trigger the debugger ui to attach, if one exists debugger_ui_attach() wait_for_attach() break_into_debugger() -def enable_attach_ui(options): - global _attach_enabled, _attach_port, _ui_attach_enabled, _ui_attach_options +# Options could have: `debugOptions`, `localRoot` & `remoteRoot` & `id`. +def enable_attach_ui(): + global _attach_enabled, _ui_attach_options, _ui_attach_enabled if not _attach_enabled: enable_attach(None, ('0.0.0.0', 0)) - _ui_attach_options = options if options is not None else _ui_attach_options if not _ui_attach_enabled: _ui_attach_enabled = debugger_ui_enable_attach() +def set_attach_ui_options(options): + global _ui_attach_options + _ui_attach_options = options + + def debugger_ui_attach(): if not vspd.DETACHED: return global _attach_port attach_info = {"domain": "debug", "type": "python", "command": "attach", "port": _attach_port} - return debugger_ui_request(attach_info) + debugger_ui_request(attach_info) def debugger_ui_enable_attach(): From 2168acda9258dc477646cfeb7ff59fc041ec8b21 Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Tue, 8 Aug 2017 10:18:40 -0700 Subject: [PATCH 007/433] 30 seconds timeout waiting for attach - to avoid blocking program execution --- pythonFiles/PythonTools/ptvsd/attach_server.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pythonFiles/PythonTools/ptvsd/attach_server.py b/pythonFiles/PythonTools/ptvsd/attach_server.py index 49a9ffc5bfb8..6791ea586aca 100644 --- a/pythonFiles/PythonTools/ptvsd/attach_server.py +++ b/pythonFiles/PythonTools/ptvsd/attach_server.py @@ -317,8 +317,11 @@ def set_trace(): enable_attach_ui() # Trigger the debugger ui to attach, if one exists debugger_ui_attach() - wait_for_attach() - break_into_debugger() + wait_for_attach(30) + if vspd.DETACHED: + sys.stderr.write('Debugger timed out (30 seconds) waiting for attach!\n') + else: + break_into_debugger() # Options could have: `debugOptions`, `localRoot` & `remoteRoot` & `id`. From e3c7f059dc25deeb36021311e09e37ee90379785 Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Tue, 15 Aug 2017 17:21:22 -0700 Subject: [PATCH 008/433] Replace localhost with 127.0.0.1 --- pythonFiles/PythonTools/ptvsd/attach_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonFiles/PythonTools/ptvsd/attach_server.py b/pythonFiles/PythonTools/ptvsd/attach_server.py index 6791ea586aca..48dbda79b434 100644 --- a/pythonFiles/PythonTools/ptvsd/attach_server.py +++ b/pythonFiles/PythonTools/ptvsd/attach_server.py @@ -354,7 +354,7 @@ def debugger_ui_enable_attach(): def debugger_ui_request(info): - req = Request('http://localhost:' + str(DEBUGGER_UI_PORT), + req = Request('http://127.0.0.1:' + str(DEBUGGER_UI_PORT), data=json.dumps(info).encode('utf8'), headers={'Content-Type': 'application/json', 'Accept': 'application/json'}) try: From 2822b037d7d1d886d241afd94a9d13d17e1af5ad Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Tue, 29 Aug 2017 14:33:31 -0700 Subject: [PATCH 009/433] Fix deprecation warnings with python 3.6.2 and adapter exit stdin error --- .../PythonTools/ptvsd/visualstudio_py_debugger.py | 4 ++-- pythonFiles/PythonTools/ptvsd/visualstudio_py_repl.py | 2 +- pythonFiles/PythonTools/ptvsd/visualstudio_py_util.py | 10 +++++----- pythonFiles/PythonTools/visualstudio_py_repl.py | 2 +- pythonFiles/PythonTools/visualstudio_py_util.py | 10 +++++----- src/client/debugger/DebugClients/LocalDebugClient.ts | 8 ++++---- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py b/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py index 2263a267585f..cf65c4116a64 100644 --- a/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py +++ b/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py @@ -2361,7 +2361,7 @@ def _excepthook(exc_type, exc_value, exc_tb): global debugger_thread_id debugger_thread_id = _start_new_thread(DebuggerLoop(conn).loop, ()) - for mod_name, mod_value in sys.modules.items(): + for mod_value in list(sys.modules.values()): try: filename = getattr(mod_value, '__file__', None) if filename is not None: @@ -2751,4 +2751,4 @@ def _get_template_line(frame): return _offset_to_line_number(_read_file(file_name), source[1][0]) except: return None -## End modification by Don Jayamanne +## End modification by Don Jayamanne diff --git a/pythonFiles/PythonTools/ptvsd/visualstudio_py_repl.py b/pythonFiles/PythonTools/ptvsd/visualstudio_py_repl.py index 3618533910e1..35a4d810132e 100644 --- a/pythonFiles/PythonTools/ptvsd/visualstudio_py_repl.py +++ b/pythonFiles/PythonTools/ptvsd/visualstudio_py_repl.py @@ -39,7 +39,6 @@ import select import time import struct -import imp import traceback import random import os @@ -553,6 +552,7 @@ class BasicReplBackend(ReplBackend): """Basic back end which executes all Python code in-proc""" def __init__(self, mod_name='__main__'): import threading + import imp ReplBackend.__init__(self) if mod_name is not None: if sys.platform == 'cli': diff --git a/pythonFiles/PythonTools/ptvsd/visualstudio_py_util.py b/pythonFiles/PythonTools/ptvsd/visualstudio_py_util.py index c00519eebb09..88d173b2a1c3 100644 --- a/pythonFiles/PythonTools/ptvsd/visualstudio_py_util.py +++ b/pythonFiles/PythonTools/ptvsd/visualstudio_py_util.py @@ -22,7 +22,6 @@ # hasn't been loaded already, it will assume that the thread on which it is being loaded is the # main thread. This will cause issues when the thread goes away after attach completes. -import imp import os import sys import struct @@ -68,6 +67,7 @@ def exec_code(code, file, global_variables): ``sys.path[0]`` will be changed to the value of `file` without the filename. Both values are restored when this function exits. ''' + import imp original_main = sys.modules.get('__main__') global_variables = dict(global_variables) @@ -510,14 +510,14 @@ def re_test(source, pattern): d1 = {} d1_key = 'a' * self.maxstring_inner * 2 d1[d1_key] = d1_key - re_test(d1, "{'a+\.\.\.a+': 'a+\.\.\.a+'}") + re_test(d1, r"{'a+\.\.\.a+': 'a+\.\.\.a+'}") d2 = {d1_key : d1} - re_test(d2, "{'a+\.\.\.a+': {'a+\.\.\.a+': 'a+\.\.\.a+'}}") + re_test(d2, r"{'a+\.\.\.a+': {'a+\.\.\.a+': 'a+\.\.\.a+'}}") d3 = {d1_key : d2} if len(self.maxcollection) == 2: - re_test(d3, "{'a+\.\.\.a+': {'a+\.\.\.a+': {\.\.\.}}}") + re_test(d3, r"{'a+\.\.\.a+': {'a+\.\.\.a+': {\.\.\.}}}") else: - re_test(d3, "{'a+\.\.\.a+': {'a+\.\.\.a+': {'a+\.\.\.a+': 'a+\.\.\.a+'}}}") + re_test(d3, r"{'a+\.\.\.a+': {'a+\.\.\.a+': {'a+\.\.\.a+': 'a+\.\.\.a+'}}}") # Ensure empty dicts work test({}, '{}') diff --git a/pythonFiles/PythonTools/visualstudio_py_repl.py b/pythonFiles/PythonTools/visualstudio_py_repl.py index 74870fd696b3..ff84f9115dac 100644 --- a/pythonFiles/PythonTools/visualstudio_py_repl.py +++ b/pythonFiles/PythonTools/visualstudio_py_repl.py @@ -39,7 +39,6 @@ import select import time import struct -import imp import traceback import random import os @@ -553,6 +552,7 @@ class BasicReplBackend(ReplBackend): """Basic back end which executes all Python code in-proc""" def __init__(self, mod_name='__main__'): import threading + import imp ReplBackend.__init__(self) if mod_name is not None: if sys.platform == 'cli': diff --git a/pythonFiles/PythonTools/visualstudio_py_util.py b/pythonFiles/PythonTools/visualstudio_py_util.py index b3ed951e8718..58b798821750 100644 --- a/pythonFiles/PythonTools/visualstudio_py_util.py +++ b/pythonFiles/PythonTools/visualstudio_py_util.py @@ -22,7 +22,6 @@ # hasn't been loaded already, it will assume that the thread on which it is being loaded is the # main thread. This will cause issues when the thread goes away after attach completes. -import imp import os import sys import struct @@ -68,6 +67,7 @@ def exec_code(code, file, global_variables): ``sys.path[0]`` will be changed to the value of `file` without the filename. Both values are restored when this function exits. ''' + import imp original_main = sys.modules.get('__main__') global_variables = dict(global_variables) @@ -510,14 +510,14 @@ def re_test(source, pattern): d1 = {} d1_key = 'a' * self.maxstring_inner * 2 d1[d1_key] = d1_key - re_test(d1, "{'a+\.\.\.a+': 'a+\.\.\.a+'}") + re_test(d1, r"{'a+\.\.\.a+': 'a+\.\.\.a+'}") d2 = {d1_key : d1} - re_test(d2, "{'a+\.\.\.a+': {'a+\.\.\.a+': 'a+\.\.\.a+'}}") + re_test(d2, r"{'a+\.\.\.a+': {'a+\.\.\.a+': 'a+\.\.\.a+'}}") d3 = {d1_key : d2} if len(self.maxcollection) == 2: - re_test(d3, "{'a+\.\.\.a+': {'a+\.\.\.a+': {\.\.\.}}}") + re_test(d3, r"{'a+\.\.\.a+': {'a+\.\.\.a+': {\.\.\.}}}") else: - re_test(d3, "{'a+\.\.\.a+': {'a+\.\.\.a+': {'a+\.\.\.a+': 'a+\.\.\.a+'}}}") + re_test(d3, r"{'a+\.\.\.a+': {'a+\.\.\.a+': {'a+\.\.\.a+': 'a+\.\.\.a+'}}}") # Ensure empty dicts work test({}, '{}') diff --git a/src/client/debugger/DebugClients/LocalDebugClient.ts b/src/client/debugger/DebugClients/LocalDebugClient.ts index 40c972eed69b..5e0fa2635b62 100644 --- a/src/client/debugger/DebugClients/LocalDebugClient.ts +++ b/src/client/debugger/DebugClients/LocalDebugClient.ts @@ -39,10 +39,10 @@ export class LocalDebugClient extends DebugClient { if (this.pyProc) { try { this.pyProc.send('EXIT'); } catch (ex) { } - try { this.pyProc.stdin.write('EXIT'); } - catch (ex) { } - try { this.pyProc.disconnect(); } - catch (ex) { } + try { + this.pyProc.stdin.once('error', () => {}); + this.pyProc.stdin.write('EXIT'); + } catch (ex) { } this.pyProc = null; } } From 14864ba1e42462f8fd1ad4d653a86a8514e70743 Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Mon, 11 Sep 2017 19:58:53 -0700 Subject: [PATCH 010/433] Fix #298 with remote debugging as well --- pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py b/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py index cf65c4116a64..0d523ed64cd6 100644 --- a/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py +++ b/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py @@ -1314,7 +1314,8 @@ def unblock(self): assert self._is_blocked assert self.id != thread.get_ident() # only someone else should unblock us - self._block_lock.release() + if self._block_lock.locked(): + self._block_lock.release() def schedule_work(self, work): self.unblock_work = work From 19da6bc04e31da00fa3a3edb35f8aff1004fa73d Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Fri, 2 Jun 2017 12:57:31 -0700 Subject: [PATCH 011/433] Use visualstudio_py_launcher in custom launcher --- .../PythonTools/visualstudio_py_launcher.py | 143 +++++++++--------- src/client/debugger/Common/Utils.ts | 3 + 2 files changed, 77 insertions(+), 69 deletions(-) diff --git a/pythonFiles/PythonTools/visualstudio_py_launcher.py b/pythonFiles/PythonTools/visualstudio_py_launcher.py index 12b2140c4f72..20bd34d237ba 100644 --- a/pythonFiles/PythonTools/visualstudio_py_launcher.py +++ b/pythonFiles/PythonTools/visualstudio_py_launcher.py @@ -1,16 +1,16 @@ # Python Tools for Visual Studio # Copyright(c) Microsoft Corporation # All rights reserved. -# +# # Licensed under the Apache License, Version 2.0 (the License); you may not use # this file except in compliance with the License. You may obtain a copy of the # License at http://www.apache.org/licenses/LICENSE-2.0 -# +# # THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS # OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY # IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, # MERCHANTABLITY OR NON-INFRINGEMENT. -# +# # See the Apache Version 2.0 License for specific language governing # permissions and limitations under the License. @@ -23,70 +23,75 @@ __author__ = "Microsoft Corporation " __version__ = "3.0.0.0" -import os -import os.path -import sys -import traceback -try: - import visualstudio_py_debugger as vspd -except: - traceback.print_exc() - print(''' -Internal error detected. Please copy the above traceback and report at -http://go.microsoft.com/fwlink/?LinkId=293415 - -Press Enter to close. . .''') +def launch(): + import os + import os.path + import sys + import traceback try: - raw_input() - except NameError: - input() - sys.exit(1) - -# Arguments are: -# 1. Working directory. -# 2. VS debugger port to connect to. -# 3. GUID for the debug session. -# 4. Debug options (as integer - see enum PythonDebugOptions). -# 5. '-m' or '-c' to override the default run-as mode. [optional] -# 6. Startup script name. -# 7. Script arguments. - -# change to directory we expected to start from -os.chdir(sys.argv[1]) - -port_num = int(sys.argv[2]) -debug_id = sys.argv[3] -debug_options = vspd.parse_debug_options(sys.argv[4]) -del sys.argv[0:5] - -# set run_as mode appropriately -run_as = 'script' -if sys.argv and sys.argv[0] == '-m': - run_as = 'module' - del sys.argv[0] -if sys.argv and sys.argv[0] == '-c': - run_as = 'code' - del sys.argv[0] - -# preserve filename before we del sys -filename = sys.argv[0] - -# fix sys.path to be the script file dir -sys.path[0] = '' - -# exclude ourselves from being debugged -vspd.DONT_DEBUG.append(os.path.normcase(__file__)) - -## Begin modification by Don Jayamanne -# Get current Process id to pass back to debugger -currentPid = os.getpid() -## End Modification by Don Jayamanne - -# remove all state we imported -del sys, os - -# and start debugging -## Begin modification by Don Jayamanne -# Pass current Process id to pass back to debugger -vspd.debug(filename, port_num, debug_id, debug_options, currentPid, run_as) -## End Modification by Don Jayamanne + import visualstudio_py_debugger as vspd + except: + traceback.print_exc() + print(''' + Internal error detected. Please copy the above traceback and report at + http://go.microsoft.com/fwlink/?LinkId=293415 + + Press Enter to close. . .''') + try: + raw_input() + except NameError: + input() + sys.exit(1) + + # Arguments are: + # 1. Working directory. + # 2. VS debugger port to connect to. + # 3. GUID for the debug session. + # 4. Debug options (as integer - see enum PythonDebugOptions). + # 5. '-m' or '-c' to override the default run-as mode. [optional] + # 6. Startup script name. + # 7. Script arguments. + + # change to directory we expected to start from + os.chdir(sys.argv[1]) + + port_num = int(sys.argv[2]) + debug_id = sys.argv[3] + debug_options = vspd.parse_debug_options(sys.argv[4]) + del sys.argv[0:5] + + # set run_as mode appropriately + run_as = 'script' + if sys.argv and sys.argv[0] == '-m': + run_as = 'module' + del sys.argv[0] + if sys.argv and sys.argv[0] == '-c': + run_as = 'code' + del sys.argv[0] + + # preserve filename before we del sys + filename = sys.argv[0] + + # fix sys.path to be the script file dir + sys.path[0] = '' + + # exclude ourselves from being debugged + vspd.DONT_DEBUG.append(os.path.normcase(__file__)) + + ## Begin modification by Don Jayamanne + # Get current Process id to pass back to debugger + currentPid = os.getpid() + ## End Modification by Don Jayamanne + + # remove all state we imported + del sys, os + + # and start debugging + ## Begin modification by Don Jayamanne + # Pass current Process id to pass back to debugger + vspd.debug(filename, port_num, debug_id, debug_options, currentPid, run_as) + ## End Modification by Don Jayamanne + + +if __name__ == "__main__": + launch() diff --git a/src/client/debugger/Common/Utils.ts b/src/client/debugger/Common/Utils.ts index 6d6d785f7e36..700ebb055042 100644 --- a/src/client/debugger/Common/Utils.ts +++ b/src/client/debugger/Common/Utils.ts @@ -110,6 +110,9 @@ export function getPythonExecutable(pythonPath: string): string { } function isValidPythonPath(pythonPath): boolean { + if (fs.existsSync(pythonPath)) { + return true; + } try { const output = child_process.execFileSync(pythonPath, ['-c', 'print(1234)'], { encoding: 'utf8' }); return output.startsWith('1234'); From 48b4d843967da70cc60db7a385e961b028500b09 Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Thu, 15 Jun 2017 16:12:53 -0700 Subject: [PATCH 012/433] Fix #298 --- pythonFiles/PythonTools/visualstudio_py_debugger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pythonFiles/PythonTools/visualstudio_py_debugger.py b/pythonFiles/PythonTools/visualstudio_py_debugger.py index c0a603c077e5..12b9d917d07f 100644 --- a/pythonFiles/PythonTools/visualstudio_py_debugger.py +++ b/pythonFiles/PythonTools/visualstudio_py_debugger.py @@ -1312,7 +1312,8 @@ def unblock(self): assert self._is_blocked assert self.id != thread.get_ident() # only someone else should unblock us - self._block_lock.release() + if self._block_lock.locked(): + self._block_lock.release() def schedule_work(self, work): self.unblock_work = work From 7db7e908bdd3d50eb6c62cc3d0f1367e50bfa05b Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Sat, 1 Jul 2017 01:41:50 -0700 Subject: [PATCH 013/433] sys.path[0] should only be reset when debugging a file --- pythonFiles/PythonTools/visualstudio_py_debugger.py | 2 ++ pythonFiles/PythonTools/visualstudio_py_launcher.py | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pythonFiles/PythonTools/visualstudio_py_debugger.py b/pythonFiles/PythonTools/visualstudio_py_debugger.py index 12b9d917d07f..6454a5d840ec 100644 --- a/pythonFiles/PythonTools/visualstudio_py_debugger.py +++ b/pythonFiles/PythonTools/visualstudio_py_debugger.py @@ -2623,6 +2623,8 @@ def debug(file, port_num, debug_id, debug_options, currentPid, run_as = 'script' elif run_as == 'code': exec_code(file, '', globals_obj) else: + # fix sys.path to be the script file dir + sys.path[0] = '' exec_file(file, globals_obj) finally: sys.settrace(None) diff --git a/pythonFiles/PythonTools/visualstudio_py_launcher.py b/pythonFiles/PythonTools/visualstudio_py_launcher.py index 20bd34d237ba..9e202ff2064e 100644 --- a/pythonFiles/PythonTools/visualstudio_py_launcher.py +++ b/pythonFiles/PythonTools/visualstudio_py_launcher.py @@ -72,9 +72,6 @@ def launch(): # preserve filename before we del sys filename = sys.argv[0] - # fix sys.path to be the script file dir - sys.path[0] = '' - # exclude ourselves from being debugged vspd.DONT_DEBUG.append(os.path.normcase(__file__)) From bb4e145eb79d506a7d01084d555639751fff9cc9 Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Tue, 1 Aug 2017 14:38:50 -0700 Subject: [PATCH 014/433] Enable debugger engine to notify a UI server (if one exists) about its existence/readiness to attach --- pythonFiles/PythonTools/ptvsd/__init__.py | 10 +- .../PythonTools/ptvsd/attach_server.py | 87 ++++++- .../ptvsd/visualstudio_py_debugger.py | 232 +++++++++--------- 3 files changed, 197 insertions(+), 132 deletions(-) diff --git a/pythonFiles/PythonTools/ptvsd/__init__.py b/pythonFiles/PythonTools/ptvsd/__init__.py index 25765e165f14..75bb35ec764a 100644 --- a/pythonFiles/PythonTools/ptvsd/__init__.py +++ b/pythonFiles/PythonTools/ptvsd/__init__.py @@ -1,22 +1,22 @@ # Python Tools for Visual Studio # Copyright(c) Microsoft Corporation # All rights reserved. -# +# # Licensed under the Apache License, Version 2.0 (the License); you may not use # this file except in compliance with the License. You may obtain a copy of the # License at http://www.apache.org/licenses/LICENSE-2.0 -# +# # THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS # OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY # IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, # MERCHANTABLITY OR NON-INFRINGEMENT. -# +# # See the Apache Version 2.0 License for specific language governing # permissions and limitations under the License. __author__ = "Microsoft Corporation " __version__ = "3.0.0.0" -__all__ = ['enable_attach', 'wait_for_attach', 'break_into_debugger', 'settrace', 'is_attached', 'AttachAlreadyEnabledError'] +__all__ = ['enable_attach', 'enable_attach_ui', 'wait_for_attach', 'break_into_debugger', 'set_trace', 'is_attached', 'AttachAlreadyEnabledError'] -from ptvsd.attach_server import enable_attach, wait_for_attach, break_into_debugger, settrace, is_attached, AttachAlreadyEnabledError \ No newline at end of file +from ptvsd.attach_server import enable_attach, enable_attach_ui, wait_for_attach, break_into_debugger, set_trace, is_attached, AttachAlreadyEnabledError diff --git a/pythonFiles/PythonTools/ptvsd/attach_server.py b/pythonFiles/PythonTools/ptvsd/attach_server.py index 743c89be787f..469a6fbe4dba 100644 --- a/pythonFiles/PythonTools/ptvsd/attach_server.py +++ b/pythonFiles/PythonTools/ptvsd/attach_server.py @@ -1,16 +1,16 @@ # Python Tools for Visual Studio # Copyright(c) Microsoft Corporation # All rights reserved. -# +# # Licensed under the Apache License, Version 2.0 (the License); you may not use # this file except in compliance with the License. You may obtain a copy of the # License at http://www.apache.org/licenses/LICENSE-2.0 -# +# # THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS # OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY # IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, # MERCHANTABLITY OR NON-INFRINGEMENT. -# +# # See the Apache Version 2.0 License for specific language governing # permissions and limitations under the License. @@ -37,6 +37,14 @@ except ImportError: ssl = None +import json +import errno +from socket import error as socket_error +try: + from urllib.request import urlopen, Request, URLError +except ImportError: + from urllib2 import urlopen, Request, URLError + import ptvsd.visualstudio_py_debugger as vspd import ptvsd.visualstudio_py_repl as vspr from ptvsd.visualstudio_py_util import to_bytes, read_bytes, read_int, read_string, write_bytes, write_int, write_string @@ -66,12 +74,12 @@ # (int64), and then the Python language version that the server is running represented by three int64s - # major, minor, micro; From there on the socket is assumed to be using the normal PTVS debugging protocol. # If attaching was not successful (which can happen if some other debugger is already attached), the server -# responds with 'RJCT' and closes the connection. +# responds with 'RJCT' and closes the connection. # # 'REPL' # Attach REPL to the process. If successful, the server responds with 'ACPT', and from there on the socket # is assumed to be using the normal PTVS REPL protocol. If not successful (which can happen if there is -# no debugger attached), the server responds with 'RJCT' and closes the connection. +# no debugger attached), the server responds with 'RJCT' and closes the connection. PTVS_VER = '2.2' DEFAULT_PORT = 5678 @@ -82,17 +90,20 @@ INFO = to_bytes('INFO') ATCH = to_bytes('ATCH') REPL = to_bytes('REPL') +DEBUGGER_UI_PORT = 9615 _attach_enabled = False _attached = threading.Event() +_attach_port = None +_ui_attach_enabled = False +_ui_attach_options = {} vspd.DONT_DEBUG.append(os.path.normcase(__file__)) class AttachAlreadyEnabledError(Exception): """`ptvsd.enable_attach` has already been called in this process.""" - -def enable_attach(secret, address = ('0.0.0.0', DEFAULT_PORT), certfile = None, keyfile = None, redirect_output = True): +def enable_attach(secret, address, certfile = None, keyfile = None, redirect_output = True): """Enables Python Tools for Visual Studio to attach to this process remotely to debug Python code. @@ -105,7 +116,7 @@ def enable_attach(secret, address = ('0.0.0.0', DEFAULT_PORT), certfile = None, hostname by ``'@'``, e.g.: ``'secret@myhost.cloudapp.net:5678'``. If secret is ``None``, there's no validation, and any client can connect freely. - address : (str, int), optional + address : (str, int), optional Specifies the interface and port on which the debugging server should listen for TCP connections. It is in the same format as used for regular sockets of the `socket.AF_INET` family, i.e. a tuple of @@ -116,7 +127,7 @@ def enable_attach(secret, address = ('0.0.0.0', DEFAULT_PORT), certfile = None, Used to enable SSL. If not specified, or if set to ``None``, the connection between this program and the debugger will be unsecure, and can be intercepted on the wire. If specified, the meaning of this - parameter is the same as for `ssl.wrap_socket`. + parameter is the same as for `ssl.wrap_socket`. keyfile : str, optional Used together with `certfile` when SSL is enabled. Its meaning is the same as for ``ssl.wrap_socket``. @@ -131,7 +142,7 @@ def enable_attach(secret, address = ('0.0.0.0', DEFAULT_PORT), certfile = None, is attached, call `ptvsd.wait_for_attach`. The debugger can be detached and re-attached multiple times after `enable_attach` is called. - This function can only be called once during the lifetime of the process. + This function can only be called once during the lifetime of the process. On a second call, `AttachAlreadyEnabledError` is raised. In circumstances where the caller does not control how many times the function will be called (e.g. when a script with a single call is run more than once by @@ -164,8 +175,15 @@ def enable_attach(secret, address = ('0.0.0.0', DEFAULT_PORT), certfile = None, server = socket.socket(proto=socket.IPPROTO_TCP) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + if address is None: + if register_options is not None: + address = ('0.0.0.0', 0) + else: + address = ('0.0.0.0', DEFAULT_PORT) server.bind(address) server.listen(1) + global _attach_port + _attach_port = server.getsockname()[1] def server_thread_func(): while True: client = None @@ -298,8 +316,53 @@ def replace_trace_func(): vspd.intercept_threads(for_attach = True) -# Alias for convenience of users of pydevd -settrace = enable_attach +# `set_trace` should pause debug execution and attach the debugger UI to the debugger engine +def set_trace(options=None): + # Enable on-demand UI attach to the debugger. + enable_attach_ui(options) + # Trigger the debugger ui to attach, if one exists + debugger_ui_attach() + wait_for_attach() + break_into_debugger() + + +def enable_attach_ui(options): + global _attach_enabled, _attach_port, _ui_attach_enabled, _ui_attach_options + if not _attach_enabled: + enable_attach(None, ('0.0.0.0', 0)) + _ui_attach_options = options if options is not None else _ui_attach_options + if not _ui_attach_enabled: + _ui_attach_enabled = debugger_ui_enable_attach() + + +def debugger_ui_attach(): + if not vspd.DETACHED: + return + global _attach_port + attach_info = {"domain": "debug", "type": "python", "command": "attach", "port": _attach_port} + return debugger_ui_request(attach_info) + + +def debugger_ui_enable_attach(): + global _attach_port, _ui_attach_options + attach_info = {"domain": "debug", "type": "python", "command": "enable-attach", + "port": _attach_port, "options": _ui_attach_options} + return debugger_ui_request(attach_info) + + +def debugger_ui_request(info): + req = Request('http://localhost:' + str(DEBUGGER_UI_PORT), + data=json.dumps(info).encode('utf8'), + headers={'Content-Type': 'application/json', 'Accept': 'application/json'}) + try: + response = urlopen(req) + except URLError: + # It's okay if there's no debugger ui server waiting for that info + return False + json_response = json.loads(response.read()) + if not json_response['success']: + raise RuntimeError('Failed attach attempt') + return True def wait_for_attach(timeout = None): diff --git a/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py b/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py index b589a8208baa..2263a267585f 100644 --- a/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py +++ b/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py @@ -1,16 +1,16 @@ # Python Tools for Visual Studio # Copyright(c) Microsoft Corporation # All rights reserved. -# +# # Licensed under the Apache License, Version 2.0 (the License); you may not use # this file except in compliance with the License. You may obtain a copy of the # License at http://www.apache.org/licenses/LICENSE-2.0 -# +# # THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS # OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY # IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, # MERCHANTABLITY OR NON-INFRINGEMENT. -# +# # See the Apache Version 2.0 License for specific language governing # permissions and limitations under the License. # With number of modifications by Don Jayamanne @@ -76,8 +76,10 @@ except ImportError: import ptvsd.visualstudio_py_repl as _vspr + try: import stackless + stackless.tasklet # work-around lazy on-demand importers except ImportError: stackless = None @@ -233,8 +235,8 @@ def find_by_id(breakpoint_id): send_lock = thread.allocate_lock() class _SendLockContextManager(object): - """context manager for send lock. Handles both acquiring/releasing the - send lock as well as detaching the debugger if the remote process + """context manager for send lock. Handles both acquiring/releasing the + send lock as well as detaching the debugger if the remote process is disconnected""" def __enter__(self): @@ -248,7 +250,7 @@ def __enter__(self): def __exit__(self, exc_type, exc_value, tb): send_lock.release() - + # start sending debug events again cur_thread = get_thread_from_id(thread.get_ident()) if cur_thread is not None: @@ -258,8 +260,8 @@ def __exit__(self, exc_type, exc_value, tb): detach_threads() detach_process() # swallow the exception, we're no longer debugging - return True - + return True + _SendLockCtx = _SendLockContextManager() SEND_BREAK_COMPLETE = False @@ -334,7 +336,7 @@ def is_repr_round_tripping(x): try: # getfilesystemencoding is used here because it effectively corresponds to the notion of "locale encoding": # current ANSI codepage on Windows, LC_CTYPE on Linux, UTF-8 on OS X - which is exactly what we want. - TYPES_WITH_RAW_REPR[bytearray] = lambda b: b.decode(sys.getfilesystemencoding(), 'ignore') + TYPES_WITH_RAW_REPR[bytearray] = lambda b: b.decode(sys.getfilesystemencoding(), 'ignore') except: pass @@ -350,7 +352,7 @@ def is_repr_round_tripping(x): class StackOverflowException(Exception): pass else: StackOverflowException = RuntimeError - + ASBR = to_bytes('ASBR') SETL = to_bytes('SETL') THRF = to_bytes('THRF') @@ -420,7 +422,7 @@ def lookup_local(frame, name): while bits and obj is not None and type(obj) is types.ModuleType: obj = getattr(obj, bits.pop(0), None) return obj - + if sys.version_info[0] >= 3: _EXCEPTIONS_MODULE = 'builtins' else: @@ -489,7 +491,7 @@ def should_break(self, thread, ex_type, ex_value, trace): break_type = BREAK_TYPE_NONE return break_type - + def is_handled(self, thread, ex_type, ex_value, trace): if trace is None: # get out if we didn't get a traceback @@ -500,9 +502,9 @@ def is_handled(self, thread, ex_type, ex_value, trace): # don't break if this is not the top of the traceback, # unless the previous frame was not debuggable return True - + cur_frame = trace.tb_frame - + while should_send_frame(cur_frame) and cur_frame.f_code is not None and cur_frame.f_code.co_filename is not None: filename = path.normcase(cur_frame.f_code.co_filename) if is_file_in_zip(filename): @@ -511,11 +513,11 @@ def is_handled(self, thread, ex_type, ex_value, trace): if not is_same_py_file(filename, __file__): handlers = self.handler_cache.get(filename) - + if handlers is None: # req handlers for this file from the debug engine self.handler_lock.acquire() - + with _SendLockCtx: write_bytes(conn, REQH) write_string(conn, filename) @@ -547,7 +549,7 @@ def is_handled(self, thread, ex_type, ex_value, trace): cur_frame = cur_frame.f_back return False - + def add_exception(self, name, mode=BREAK_MODE_UNHANDLED): if name.startswith(_EXCEPTIONS_MODULE + '.'): name = name[len(_EXCEPTIONS_MODULE) + 1:] @@ -556,7 +558,7 @@ def add_exception(self, name, mode=BREAK_MODE_UNHANDLED): BREAK_ON = ExceptionBreakInfo() def probe_stack(depth = 10): - """helper to make sure we have enough stack space to proceed w/o corrupting + """helper to make sure we have enough stack space to proceed w/o corrupting debugger state.""" if depth == 0: return @@ -599,7 +601,7 @@ def breakpoint_path_match(vs_path, local_path): local_path_norm = path.normcase(local_path) if local_path_to_vs_path.get(local_path_norm) == vs_path_norm: return True - + # Walk the local filesystem from local_path up, matching agains win_path component by component, # and stop when we no longer see an __init__.py. This should give a reasonably close approximation # of matching the package name. @@ -615,7 +617,7 @@ def breakpoint_path_match(vs_path, local_path): # needed to, and matched all names on our way, so this is a match. if not path.exists(path.join(local_path, '__init__.py')): break - + local_path_to_vs_path[local_path_norm] = vs_path_norm return True @@ -623,26 +625,26 @@ def update_all_thread_stacks(blocking_thread = None, check_is_blocked = True): THREADS_LOCK.acquire() all_threads = list(THREADS.values()) THREADS_LOCK.release() - + for cur_thread in all_threads: if cur_thread is blocking_thread: continue - + cur_thread._block_starting_lock.acquire() if not check_is_blocked or not cur_thread._is_blocked: # release the lock, we're going to run user code to evaluate the frames - cur_thread._block_starting_lock.release() - + cur_thread._block_starting_lock.release() + frames = cur_thread.get_frame_list() - + # re-acquire the lock and make sure we're still not blocked. If so send # the frame list. cur_thread._block_starting_lock.acquire() if not check_is_blocked or not cur_thread._is_blocked: cur_thread.send_frame_list(frames) - + cur_thread._block_starting_lock.release() - + DJANGO_BREAKPOINTS = {} DJANGO_TEMPLATES = {} @@ -652,7 +654,7 @@ def __init__(self, filename): self.filename = filename self.breakpoints = {} self.rangeIsPlainText = {} - + def add_breakpoint(self, lineno, brkpt_id): self.breakpoints[lineno] = brkpt_id @@ -673,20 +675,20 @@ def is_range_plain_text(self, start, end): with open(self.filename, 'rb') as contents: contents.seek(start, 0) # 0 = start of file, optional in this case data = contents.read(end - start) - isPlainText = True + isPlainText = True if data.startswith('{{') and data.endswith('}}'): isPlainText = False if data.startswith('{%') and data.endswith('%}'): isPlainText = False self.rangeIsPlainText[key] = isPlainText - return isPlainText + return isPlainText except: return False else: return self.rangeIsPlainText.get(key) def line_number_to_offset(self, lineNumber): - line_locs = self.line_locations + line_locs = self.line_locations if line_locs is not None: low_line = line_locs[lineNumber - 1] hi_line = line_locs[lineNumber] @@ -722,7 +724,7 @@ def line_locations(self): return self._line_locations def get_line_range(self, start, end): - line_locs = self.line_locations + line_locs = self.line_locations if line_locs is not None: low_line = bisect.bisect_right(line_locs, start) hi_line = bisect.bisect_right(line_locs, end) @@ -735,10 +737,10 @@ def should_break(self, start, end): low_line, hi_line = self.get_line_range(start, end) if low_line is not None and hi_line is not None: # low_line/hi_line is 0 based, self.breakpoints is 1 based - for i in xrange(low_line+1, hi_line+2): + for i in xrange(low_line+1, hi_line+2): bkpt_id = self.breakpoints.get(i) if bkpt_id is not None: - return True, bkpt_id + return True, bkpt_id return False, 0 @@ -756,7 +758,7 @@ def get_django_frame_source(frame): IS_DJANGO19 = version[0] == 1 and version[1] == 9 IS_DJANGO19_OR_HIGHER = ((version[0] == 1 and version[1] >= 9) or version[0] > 1) except: - pass + pass if frame.f_code.co_name == 'render': self_obj = frame.f_locals.get('self', None) if self_obj is None: @@ -794,12 +796,12 @@ def __getattr__(self, name): class Thread(object): def __init__(self, id = None): if id is not None: - self.id = id + self.id = id else: self.id = thread.get_ident() - self._events = {'call' : self.handle_call, - 'line' : self.handle_line, - 'return' : self.handle_return, + self._events = {'call' : self.handle_call, + 'line' : self.handle_line, + 'return' : self.handle_return, 'exception' : self.handle_exception, 'c_call' : self.handle_c_call, 'c_return' : self.handle_c_return, @@ -865,7 +867,7 @@ def new_f(old_f, args, kwargs): tsk.tempval = new_f stackless.tasklet.setup(tsk, f, args, kwargs) return tsk - + def settrace(tsk, tb): if hasattr(tsk.frame, "f_trace"): tsk.frame.f_trace = tb @@ -876,14 +878,14 @@ def settrace(tsk, tb): stackless.tasklet.__call__ = __call__ if sys.platform == 'cli': self.frames = [] - + if sys.platform == 'cli': # workaround an IronPython bug where we're sometimes missing the back frames # http://ironpython.codeplex.com/workitem/31437 def push_frame(self, frame): self.cur_frame = frame self.frames.append(frame) - + def pop_frame(self): self.frames.pop() self.cur_frame = self.frames[-1] @@ -908,11 +910,11 @@ def _stackless_schedule_cb(self, prev, next): if not current: return current_tf = current.trace_function - + try: current.trace_function = None self.stepping = STEPPING_NONE - + # If the current frame has no trace function, we may need to get it # from the previous frame, depending on how we ended up in the # callback. @@ -939,7 +941,7 @@ def trace_func(self, frame, event, arg): if sys is None: return None elif self.is_sending: - # https://pytools.codeplex.com/workitem/1864 + # https://pytools.codeplex.com/workitem/1864 # we're currently doing I/O w/ the socket, we don't want to deliver # any breakpoints or async breaks because we'll deadlock. Continue # to return the trace function so all of our frames remain @@ -953,7 +955,7 @@ def trace_func(self, frame, event, arg): try: # if should_debug_code(frame.f_code) is not true during attach - # the current frame is None and a pop_frame will cause an exception and + # the current frame is None and a pop_frame will cause an exception and # break the debugger if self.cur_frame is None: # happens during attach, we need frame for blocking @@ -972,7 +974,7 @@ def trace_func(self, frame, event, arg): except (StackOverflowException, KeyboardInterrupt): # stack overflow, disable tracing return self.trace_func - + def handle_call(self, frame, arg): self.push_frame(frame) @@ -980,10 +982,10 @@ def handle_call(self, frame, arg): source_obj = get_django_frame_source(frame) if source_obj is not None: origin, (start, end), lineNumber = source_obj - + active_bps = DJANGO_BREAKPOINTS.get(origin.lower()) should_break = False - if active_bps is not None and origin != '': + if active_bps is not None and origin != '': should_break, bkpt_id = active_bps.should_break(start, end) isPlainText = active_bps.is_range_plain_text(start, end) if isPlainText: @@ -1023,8 +1025,8 @@ def handle_call(self, frame, arg): elif stepping <= STEPPING_OUT: self.stepping -= 1 - if (sys.platform == 'cli' and - frame.f_code.co_name == '' and + if (sys.platform == 'cli' and + frame.f_code.co_name == '' and not IPY_SEEN_MODULES.TryGetValue(frame.f_code)[0]): IPY_SEEN_MODULES.Add(frame.f_code, None) # work around IronPython bug - http://ironpython.codeplex.com/workitem/30127 @@ -1038,7 +1040,7 @@ def handle_call(self, frame, arg): self.prev_trace_func = old_trace_func(frame, 'call', arg) return self.trace_func - + def should_block_on_frame(self, frame): if not should_debug_code(frame.f_code): return False @@ -1091,7 +1093,7 @@ def handle_line(self, frame, arg): # the module to which it was bound, so only exact matches are considered hits. if bp.is_bound: continue - # Otherwise, use relaxed path check that tries to handle differences between + # Otherwise, use relaxed path check that tries to handle differences between # local and remote filesystems for remote scenarios: if not breakpoint_path_match(filename, frame.f_code.co_filename): continue @@ -1159,7 +1161,7 @@ def handle_line(self, frame, arg): self.prev_trace_func = old_trace_func(frame, 'line', arg) return self.trace_func - + def handle_return(self, frame, arg): self.pop_frame() @@ -1196,7 +1198,7 @@ def handle_return(self, frame, arg): # restore previous frames trace function if there is one if self.trace_func_stack: self.prev_trace_func = self.trace_func_stack.pop() - + def handle_exception(self, frame, arg): if self.stepping == STEPPING_ATTACH_BREAK: self.block_maybe_attach() @@ -1214,15 +1216,15 @@ def handle_exception(self, frame, arg): self.prev_trace_func = old_trace_func(frame, 'exception', arg) return self.trace_func - + def handle_c_call(self, frame, arg): # break points? pass - + def handle_c_return(self, frame, arg): # step out of ? pass - + def handle_c_exception(self, frame, arg): pass @@ -1236,7 +1238,7 @@ def block_maybe_attach(self): will_block_now = False attach_sent_break = True attach_lock.release() - + probe_stack() stepping = self.stepping self.stepping = STEPPING_NONE @@ -1252,7 +1254,7 @@ def block_cond(): return report_process_loaded(self.id) update_all_thread_stacks(self) self.block(block_cond) - + def async_break(self): def async_break_send(): with _SendLockCtx: @@ -1266,7 +1268,7 @@ def async_break_send(): write_int(conn, self.id) if sent_break_complete: - # if we have threads which have not broken yet capture their frame list and + # if we have threads which have not broken yet capture their frame list and # send it now. If they block we'll send an updated (and possibly more accurate - if # there are any thread locals) list of frames. update_all_thread_stacks(self) @@ -1278,10 +1280,10 @@ def block(self, block_lambda, keep_stopped_on_line = False): """blocks the current thread until the debugger resumes it""" assert not self._is_blocked #assert self.id == thread.get_ident(), 'wrong thread identity' + str(self.id) + ' ' + str(thread.get_ident()) # we should only ever block ourselves - + # send thread frames before we block self.enum_thread_frames_locally() - + if not keep_stopped_on_line: self.stopped_on_line = self.cur_frame.f_lineno @@ -1301,7 +1303,7 @@ def block(self, block_lambda, keep_stopped_on_line = False): self.unblock_work() self.unblock_work = None self._is_working = False - + self._block_starting_lock.acquire() assert self._is_blocked self._is_blocked = False @@ -1309,9 +1311,9 @@ def block(self, block_lambda, keep_stopped_on_line = False): def unblock(self): """unblocks the current thread allowing it to continue to run""" - assert self._is_blocked + assert self._is_blocked assert self.id != thread.get_ident() # only someone else should unblock us - + self._block_lock.release() def schedule_work(self, work): @@ -1320,26 +1322,26 @@ def schedule_work(self, work): def run_on_thread(self, text, cur_frame, execution_id, frame_kind, repr_kind = PYTHON_EVALUATION_RESULT_REPR_KIND_NORMAL): self._block_starting_lock.acquire() - + if not self._is_blocked: report_execution_error('', execution_id) elif not self._is_working: self.schedule_work(lambda : self.run_locally(text, cur_frame, execution_id, frame_kind, repr_kind)) else: report_execution_error('', execution_id) - + self._block_starting_lock.release() def run_on_thread_no_report(self, text, cur_frame, frame_kind): self._block_starting_lock.acquire() - + if not self._is_blocked: pass elif not self._is_working: self.schedule_work(lambda : self.run_locally_no_report(text, cur_frame, frame_kind)) else: pass - + self._block_starting_lock.release() def enum_child_on_thread(self, text, cur_frame, execution_id, frame_kind): @@ -1463,7 +1465,7 @@ def enum_child_locally(self, expr, cur_frame, execution_id, frame_kind): break key_repr = safe_repr(key) - + # Some objects are enumerable but not indexable, or repr(key) is not a valid Python expression. For those, we # cannot use obj[key] to get the item by its key, and have to retrieve it by index from enumerate() instead. try: @@ -1494,7 +1496,7 @@ def enum_child_locally(self, expr, cur_frame, execution_id, frame_kind): def get_frame_list(self): frames = [] cur_frame = self.cur_frame - + while should_send_frame(cur_frame): # calculate the ending line number lineno = cur_frame.f_code.co_firstlineno @@ -1541,7 +1543,7 @@ def get_frame_list(self): f_globals = cur_frame.f_globals if f_globals: # ensure globals to work with (IPy may have None for cur_frame.f_globals for frames within stdlib) self.collect_variables(vars, f_globals, cur_frame.f_code.co_names, treated, skip_unknown = True) - + frame_info = None if source_obj is not None: @@ -1557,8 +1559,8 @@ def get_frame_list(self): frame_kind = FRAME_KIND_DJANGO frame_info = ( low_line + 1, - hi_line + 1, - low_line + 1, + hi_line + 1, + low_line + 1, cur_frame.f_code.co_name, str(origin), 0, @@ -1571,8 +1573,8 @@ def get_frame_list(self): if frame_info is None: frame_info = ( cur_frame.f_code.co_firstlineno, - lineno, - cur_frame.f_lineno, + lineno, + cur_frame.f_lineno, cur_frame.f_code.co_name, get_code_filename(cur_frame.f_code), cur_frame.f_code.co_argcount, @@ -1583,9 +1585,9 @@ def get_frame_list(self): ) frames.append(frame_info) - + cur_frame = cur_frame.f_back - + return frames def collect_variables(self, vars, objects, names, treated, skip_unknown = False): @@ -1613,24 +1615,24 @@ def send_frame_list(self, frames, thread_name = None): write_bytes(conn, THRF) write_int(conn, self.id) write_string(conn, thread_name) - + # send the frame count write_int(conn, len(frames)) for firstlineno, lineno, curlineno, name, filename, argcount, variables, frameKind, sourceFile, sourceLine in frames: - # send each frame + # send each frame write_int(conn, firstlineno) write_int(conn, lineno) write_int(conn, curlineno) - + write_string(conn, name) write_string(conn, filename) write_int(conn, argcount) - + write_int(conn, frameKind) if frameKind == FRAME_KIND_DJANGO: write_string(conn, sourceFile) write_int(conn, sourceLine) - + write_int(conn, len(variables)) for name, type_obj, safe_repr_obj, hex_repr_obj, type_name, obj_len in variables: write_string(conn, name) @@ -1737,7 +1739,7 @@ def loop(self): pass except: traceback.print_exc() - + def command_step_into(self): tid = read_int(self.conn) thread = get_thread_from_id(tid) @@ -1753,7 +1755,7 @@ def command_step_out(self): assert thread._is_blocked thread.stepping = STEPPING_OUT self.command_resume_all() - + def command_step_over(self): # set step over tid = read_int(self.conn) @@ -1793,7 +1795,7 @@ def command_set_breakpoint_condition(self): breakpoint_id = read_int(self.conn) kind = read_int(self.conn) condition = read_string(self.conn) - + bp = BreakpointInfo.find_by_id(breakpoint_id) if bp is not None: bp.condition_kind = kind @@ -1812,7 +1814,7 @@ def command_set_breakpoint_pass_count(self): def command_set_breakpoint_hit_count(self): breakpoint_id = read_int(self.conn) count = read_int(self.conn) - + bp = BreakpointInfo.find_by_id(breakpoint_id) if bp is not None: bp.hit_count = count @@ -1820,7 +1822,7 @@ def command_set_breakpoint_hit_count(self): def command_get_breakpoint_hit_count(self): req_id = read_int(self.conn) breakpoint_id = read_int(self.conn) - + bp = BreakpointInfo.find_by_id(breakpoint_id) count = 0 if bp is not None: @@ -1907,7 +1909,7 @@ def command_resume_all(self): if thread._is_blocked: thread.unblock() thread._block_starting_lock.release() - + def command_resume_thread(self): tid = read_int(self.conn) THREADS_LOCK.acquire() @@ -1927,7 +1929,7 @@ def command_auto_resume(self): THREADS_LOCK.release() stepping = thread.stepping - if ((stepping == STEPPING_OVER or stepping == STEPPING_INTO) and thread.cur_frame.f_lineno != thread.stopped_on_line): + if ((stepping == STEPPING_OVER or stepping == STEPPING_INTO) and thread.cur_frame.f_lineno != thread.stopped_on_line): report_step_finished(tid) else: self.command_resume_all() @@ -2020,11 +2022,11 @@ def command_enum_children(self): fid = read_int(self.conn) # frame id eid = read_int(self.conn) # execution id frame_kind = read_int(self.conn) # frame kind - + thread, cur_frame = self.get_thread_and_frame(tid, fid, frame_kind) if thread is not None and cur_frame is not None: thread.enum_child_on_thread(text, cur_frame, eid, frame_kind) - + def get_thread_and_frame(self, tid, fid, frame_kind): thread = get_thread_from_id(tid) cur_frame = None @@ -2049,11 +2051,11 @@ def command_detach(self): with _SendLockCtx: write_bytes(conn, DETC) - detach_process() + detach_process() for callback in DETACH_CALLBACKS: callback() - + raise DebuggerExitException() def command_last_ack(self): @@ -2099,12 +2101,12 @@ def report_exception(frame, exc_info, tid, break_type): exc_name = get_exception_name(exc_type) exc_value = exc_info[1] tb_value = exc_info[2] - + if type(exc_value) is tuple: - # exception object hasn't been created yet, create it now + # exception object hasn't been created yet, create it now # so we can get the correct msg. exc_value = exc_type(*exc_value) - + data = { 'typename': get_exception_name(exc_type), 'message': str(exc_value), @@ -2172,7 +2174,7 @@ def report_breakpoint_failed(id): write_bytes(conn, BRKF) write_int(conn, id) -def report_breakpoint_hit(id, tid): +def report_breakpoint_hit(id, tid): with _SendLockCtx: write_bytes(conn, BRKH) write_int(conn, id) @@ -2217,7 +2219,7 @@ def report_execution_result(execution_id, result, repr_kind = PYTHON_EVALUATION_ hex_repr = safe_hex_repr(result) else: flags = PYTHON_EVALUATION_RESULT_RAW - hex_repr = None + hex_repr = None for cls, raw_repr in TYPES_WITH_RAW_REPR.items(): if isinstance(result, cls): try: @@ -2311,7 +2313,7 @@ def attach_process(port_num, debug_id, debug_options, currentPid, report = False ## Begin modification by Don Jayamanne # Pass current Process id to pass back to debugger write_int(conn, currentPid) # success - ## End Modification by Don Jayamanne + ## End Modification by Don Jayamanne break except: import time @@ -2370,7 +2372,7 @@ def _excepthook(exc_type, exc_value, exc_tb): else: MODULES.append((filename, Module(fullpath))) except: - traceback.print_exc() + traceback.print_exc() if report: THREADS_LOCK.acquire() @@ -2409,7 +2411,7 @@ def detach_process(): global DETACHED DETACHED = True if not _INTERCEPTING_FOR_ATTACH: - if isinstance(sys.stdout, _DebuggerOutput): + if isinstance(sys.stdout, _DebuggerOutput): sys.stdout = sys.stdout.old_out if isinstance(sys.stderr, _DebuggerOutput): sys.stderr = sys.stderr.old_out @@ -2436,7 +2438,7 @@ def detach_threads(): THREADS_LOCK.acquire() THREADS.clear() THREADS_LOCK.release() - + BREAKPOINTS.clear() def new_thread(tid = None, set_break = False, frame = None): @@ -2444,7 +2446,7 @@ def new_thread(tid = None, set_break = False, frame = None): if tid == debugger_thread_id: return None - cur_thread = Thread(tid) + cur_thread = Thread(tid) THREADS_LOCK.acquire() THREADS[cur_thread.id] = cur_thread THREADS_LOCK.release() @@ -2498,11 +2500,11 @@ def __init__(self, old_out, is_stdout): def flush(self): if self.old_out: self.old_out.flush() - + def writelines(self, lines): for line in lines: self.write(line) - + @property def encoding(self): return 'utf8' @@ -2516,13 +2518,13 @@ def write(self, value): write_string(conn, value) if self.old_out: self.old_out.write(value) - + def isatty(self): return True def next(self): pass - + @property def name(self): if self.is_stdout: @@ -2547,7 +2549,7 @@ def write(self, data): write_string(conn, str_data) self.buffer.write(data) - def flush(self): + def flush(self): self.buffer.flush() def truncate(self, pos = None): @@ -2561,9 +2563,9 @@ def seek(self, pos, whence = 0): def is_same_py_file(file1, file2): """compares 2 filenames accounting for .pyc files""" - if file1.endswith('.pyc') or file1.endswith('.pyo'): + if file1.endswith('.pyc') or file1.endswith('.pyo'): file1 = file1[:-1] - if file2.endswith('.pyc') or file2.endswith('.pyo'): + if file2.endswith('.pyc') or file2.endswith('.pyo'): file2 = file2[:-1] return file1 == file2 @@ -2583,7 +2585,7 @@ def print_exception(exc_type, exc_value, exc_tb): print('Traceback (most recent call last):') for out in traceback.format_list(tb): sys.stderr.write(out) - + # print the exception for out in traceback.format_exception_only(exc_type, exc_value): sys.stdout.write(out) @@ -2691,7 +2693,7 @@ def _get_source_django_18_or_lower(frame): else: if IGNORE_DJANGO_TEMPLATE_WARNINGS: return None - + if IS_DJANGO18: # The debug setting was changed since Django 1.8 print("WARNING: Template path is not available. Set the 'debug' option in the OPTIONS of a DjangoTemplates " @@ -2749,4 +2751,4 @@ def _get_template_line(frame): return _offset_to_line_number(_read_file(file_name), source[1][0]) except: return None -## End modification by Don Jayamanne \ No newline at end of file +## End modification by Don Jayamanne From 20ce269480e99622f8d6f0a7eb6dcc9de72b0d36 Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Tue, 1 Aug 2017 14:55:56 -0700 Subject: [PATCH 015/433] Enable remote debugging of Django apps --- src/client/debugger/Common/Contracts.ts | 14 +++++++++++--- .../debugger/DebugClients/LocalDebugClient.ts | 8 +------- .../debugger/DebugServers/RemoteDebugServer.ts | 5 ++++- src/client/debugger/Main.ts | 5 +++++ 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/client/debugger/Common/Contracts.ts b/src/client/debugger/Common/Contracts.ts index bd5c697fede4..2fb8e9d17cdf 100644 --- a/src/client/debugger/Common/Contracts.ts +++ b/src/client/debugger/Common/Contracts.ts @@ -23,7 +23,14 @@ export class TelemetryEvent extends OutputEvent { } } } -export const DjangoApp = 'DJANGO'; + +export const VALID_DEBUG_OPTIONS = ['WaitOnAbnormalExit', + 'WaitOnNormalExit', + 'RedirectOutput', + 'DebugStdLib', + 'BreakOnSystemExitZero', + 'DjangoDebugging']; + export enum DebugFlags { None = 0, IgnoreCommandBursts = 1 @@ -69,8 +76,9 @@ export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArgum export interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments { /** An absolute path to local directory with source. */ - localRoot: string; - remoteRoot: string; + debugOptions?: string[]; + localRoot?: string; + remoteRoot?: string; port?: number; host?: string; secret?: string; diff --git a/src/client/debugger/DebugClients/LocalDebugClient.ts b/src/client/debugger/DebugClients/LocalDebugClient.ts index 10b8b2de24c9..9e7cedb05727 100644 --- a/src/client/debugger/DebugClients/LocalDebugClient.ts +++ b/src/client/debugger/DebugClients/LocalDebugClient.ts @@ -9,7 +9,7 @@ import { PathUtils } from '../../common/platform/pathUtils'; import { CurrentProcess } from '../../common/process/currentProcess'; import { EnvironmentVariablesService } from '../../common/variables/environment'; import { IServiceContainer } from '../../ioc/types'; -import { IDebugServer, IPythonProcess, LaunchRequestArguments } from '../Common/Contracts'; +import { IDebugServer, IPythonProcess, LaunchRequestArguments, VALID_DEBUG_OPTIONS } from '../Common/Contracts'; import { IS_WINDOWS } from '../Common/Utils'; import { BaseDebugServer } from '../DebugServers/BaseDebugServer'; import { LocalDebugServer } from '../DebugServers/LocalDebugServer'; @@ -18,12 +18,6 @@ import { IDebugLauncherScriptProvider } from '../types'; import { DebugClient, DebugType } from './DebugClient'; import { DebugClientHelper } from './helper'; -const VALID_DEBUG_OPTIONS = [ - 'RedirectOutput', - 'DebugStdLib', - 'BreakOnSystemExitZero', - 'DjangoDebugging']; - enum DebugServerStatus { Unknown = 1, Running = 2, diff --git a/src/client/debugger/DebugServers/RemoteDebugServer.ts b/src/client/debugger/DebugServers/RemoteDebugServer.ts index 32d93d004920..f954a15b5147 100644 --- a/src/client/debugger/DebugServers/RemoteDebugServer.ts +++ b/src/client/debugger/DebugServers/RemoteDebugServer.ts @@ -2,7 +2,7 @@ "use strict"; import { DebugSession, OutputEvent } from "vscode-debugadapter"; -import { IPythonProcess, IDebugServer, AttachRequestArguments } from "../Common/Contracts"; +import { IPythonProcess, IDebugServer, AttachRequestArguments, VALID_DEBUG_OPTIONS } from "../Common/Contracts"; import * as net from "net"; import { BaseDebugServer } from "./BaseDebugServer"; import { SocketStream } from "../../common/net/socket/SocketStream"; @@ -132,6 +132,9 @@ export class RemoteDebugServer extends BaseDebugServer { if (!commandBytesWritten) { that.stream.Write(AttachCommandBytes); let debugOptions = "WaitOnAbnormalExit, WaitOnNormalExit, RedirectOutput"; + if (Array.isArray(this.args.debugOptions)) { + debugOptions = this.args.debugOptions.filter(opt => VALID_DEBUG_OPTIONS.indexOf(opt) >= 0).join(','); + } that.stream.WriteString(debugOptions); commandBytesWritten = true; } diff --git a/src/client/debugger/Main.ts b/src/client/debugger/Main.ts index e2fc28642df1..b72860bc1d94 100644 --- a/src/client/debugger/Main.ts +++ b/src/client/debugger/Main.ts @@ -344,6 +344,11 @@ export class PythonDebugger extends LoggingDebugSession { this.launchArgs.debugOptions.indexOf(DebugOptions.DjangoDebugging) >= 0) { isDjangoFile = filePath.toUpperCase().endsWith(".HTML"); } + if (this.attachArgs != null && + Array.isArray(this.attachArgs.debugOptions) && + this.attachArgs.debugOptions.indexOf(DebugOptions.DjangoDebugging) >= 0) { + isDjangoFile = filePath.toUpperCase().endsWith(".HTML"); + } condition = typeof condition === "string" ? condition : ""; From 895f388f3ee80fd10380a1299813ce169a0a0460 Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Thu, 3 Aug 2017 16:09:01 -0700 Subject: [PATCH 016/433] Enabling setting UI attach options while not enabling the attachability yet --- pythonFiles/PythonTools/ptvsd/__init__.py | 4 ++-- .../PythonTools/ptvsd/attach_server.py | 24 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/pythonFiles/PythonTools/ptvsd/__init__.py b/pythonFiles/PythonTools/ptvsd/__init__.py index 75bb35ec764a..c1b6fb6cf61e 100644 --- a/pythonFiles/PythonTools/ptvsd/__init__.py +++ b/pythonFiles/PythonTools/ptvsd/__init__.py @@ -17,6 +17,6 @@ __author__ = "Microsoft Corporation " __version__ = "3.0.0.0" -__all__ = ['enable_attach', 'enable_attach_ui', 'wait_for_attach', 'break_into_debugger', 'set_trace', 'is_attached', 'AttachAlreadyEnabledError'] +__all__ = ['enable_attach', 'enable_attach_ui', 'wait_for_attach', 'break_into_debugger', 'set_attach_ui_options', 'set_trace', 'is_attached', 'AttachAlreadyEnabledError'] -from ptvsd.attach_server import enable_attach, enable_attach_ui, wait_for_attach, break_into_debugger, set_trace, is_attached, AttachAlreadyEnabledError +from ptvsd.attach_server import enable_attach, enable_attach_ui, wait_for_attach, break_into_debugger, set_attach_ui_options, set_trace, is_attached, AttachAlreadyEnabledError diff --git a/pythonFiles/PythonTools/ptvsd/attach_server.py b/pythonFiles/PythonTools/ptvsd/attach_server.py index 469a6fbe4dba..49a9ffc5bfb8 100644 --- a/pythonFiles/PythonTools/ptvsd/attach_server.py +++ b/pythonFiles/PythonTools/ptvsd/attach_server.py @@ -103,7 +103,7 @@ class AttachAlreadyEnabledError(Exception): """`ptvsd.enable_attach` has already been called in this process.""" -def enable_attach(secret, address, certfile = None, keyfile = None, redirect_output = True): +def enable_attach(secret, address = ('0.0.0.0', DEFAULT_PORT), certfile = None, keyfile = None, redirect_output = True): """Enables Python Tools for Visual Studio to attach to this process remotely to debug Python code. @@ -175,11 +175,6 @@ def enable_attach(secret, address, certfile = None, keyfile = None, redirect_out server = socket.socket(proto=socket.IPPROTO_TCP) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - if address is None: - if register_options is not None: - address = ('0.0.0.0', 0) - else: - address = ('0.0.0.0', DEFAULT_PORT) server.bind(address) server.listen(1) global _attach_port @@ -317,30 +312,35 @@ def replace_trace_func(): # `set_trace` should pause debug execution and attach the debugger UI to the debugger engine -def set_trace(options=None): +def set_trace(): # Enable on-demand UI attach to the debugger. - enable_attach_ui(options) + enable_attach_ui() # Trigger the debugger ui to attach, if one exists debugger_ui_attach() wait_for_attach() break_into_debugger() -def enable_attach_ui(options): - global _attach_enabled, _attach_port, _ui_attach_enabled, _ui_attach_options +# Options could have: `debugOptions`, `localRoot` & `remoteRoot` & `id`. +def enable_attach_ui(): + global _attach_enabled, _ui_attach_options, _ui_attach_enabled if not _attach_enabled: enable_attach(None, ('0.0.0.0', 0)) - _ui_attach_options = options if options is not None else _ui_attach_options if not _ui_attach_enabled: _ui_attach_enabled = debugger_ui_enable_attach() +def set_attach_ui_options(options): + global _ui_attach_options + _ui_attach_options = options + + def debugger_ui_attach(): if not vspd.DETACHED: return global _attach_port attach_info = {"domain": "debug", "type": "python", "command": "attach", "port": _attach_port} - return debugger_ui_request(attach_info) + debugger_ui_request(attach_info) def debugger_ui_enable_attach(): From 9467d2d5c6c7f01a5dc055d0c210a637d5870b6f Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Tue, 8 Aug 2017 10:18:40 -0700 Subject: [PATCH 017/433] 30 seconds timeout waiting for attach - to avoid blocking program execution --- pythonFiles/PythonTools/ptvsd/attach_server.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pythonFiles/PythonTools/ptvsd/attach_server.py b/pythonFiles/PythonTools/ptvsd/attach_server.py index 49a9ffc5bfb8..6791ea586aca 100644 --- a/pythonFiles/PythonTools/ptvsd/attach_server.py +++ b/pythonFiles/PythonTools/ptvsd/attach_server.py @@ -317,8 +317,11 @@ def set_trace(): enable_attach_ui() # Trigger the debugger ui to attach, if one exists debugger_ui_attach() - wait_for_attach() - break_into_debugger() + wait_for_attach(30) + if vspd.DETACHED: + sys.stderr.write('Debugger timed out (30 seconds) waiting for attach!\n') + else: + break_into_debugger() # Options could have: `debugOptions`, `localRoot` & `remoteRoot` & `id`. From 3651c87791f6f466489582eab9326bd01191dbf6 Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Tue, 15 Aug 2017 17:21:22 -0700 Subject: [PATCH 018/433] Replace localhost with 127.0.0.1 --- pythonFiles/PythonTools/ptvsd/attach_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonFiles/PythonTools/ptvsd/attach_server.py b/pythonFiles/PythonTools/ptvsd/attach_server.py index 6791ea586aca..48dbda79b434 100644 --- a/pythonFiles/PythonTools/ptvsd/attach_server.py +++ b/pythonFiles/PythonTools/ptvsd/attach_server.py @@ -354,7 +354,7 @@ def debugger_ui_enable_attach(): def debugger_ui_request(info): - req = Request('http://localhost:' + str(DEBUGGER_UI_PORT), + req = Request('http://127.0.0.1:' + str(DEBUGGER_UI_PORT), data=json.dumps(info).encode('utf8'), headers={'Content-Type': 'application/json', 'Accept': 'application/json'}) try: From 47a34fb61224432e7c963f4ab841f6df5d74a97d Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Tue, 29 Aug 2017 14:33:31 -0700 Subject: [PATCH 019/433] Fix deprecation warnings with python 3.6.2 and adapter exit stdin error --- .../PythonTools/ptvsd/visualstudio_py_debugger.py | 4 ++-- pythonFiles/PythonTools/ptvsd/visualstudio_py_repl.py | 2 +- pythonFiles/PythonTools/ptvsd/visualstudio_py_util.py | 10 +++++----- pythonFiles/PythonTools/visualstudio_py_repl.py | 2 +- pythonFiles/PythonTools/visualstudio_py_util.py | 10 +++++----- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py b/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py index 2263a267585f..cf65c4116a64 100644 --- a/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py +++ b/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py @@ -2361,7 +2361,7 @@ def _excepthook(exc_type, exc_value, exc_tb): global debugger_thread_id debugger_thread_id = _start_new_thread(DebuggerLoop(conn).loop, ()) - for mod_name, mod_value in sys.modules.items(): + for mod_value in list(sys.modules.values()): try: filename = getattr(mod_value, '__file__', None) if filename is not None: @@ -2751,4 +2751,4 @@ def _get_template_line(frame): return _offset_to_line_number(_read_file(file_name), source[1][0]) except: return None -## End modification by Don Jayamanne +## End modification by Don Jayamanne diff --git a/pythonFiles/PythonTools/ptvsd/visualstudio_py_repl.py b/pythonFiles/PythonTools/ptvsd/visualstudio_py_repl.py index 3618533910e1..35a4d810132e 100644 --- a/pythonFiles/PythonTools/ptvsd/visualstudio_py_repl.py +++ b/pythonFiles/PythonTools/ptvsd/visualstudio_py_repl.py @@ -39,7 +39,6 @@ import select import time import struct -import imp import traceback import random import os @@ -553,6 +552,7 @@ class BasicReplBackend(ReplBackend): """Basic back end which executes all Python code in-proc""" def __init__(self, mod_name='__main__'): import threading + import imp ReplBackend.__init__(self) if mod_name is not None: if sys.platform == 'cli': diff --git a/pythonFiles/PythonTools/ptvsd/visualstudio_py_util.py b/pythonFiles/PythonTools/ptvsd/visualstudio_py_util.py index c00519eebb09..88d173b2a1c3 100644 --- a/pythonFiles/PythonTools/ptvsd/visualstudio_py_util.py +++ b/pythonFiles/PythonTools/ptvsd/visualstudio_py_util.py @@ -22,7 +22,6 @@ # hasn't been loaded already, it will assume that the thread on which it is being loaded is the # main thread. This will cause issues when the thread goes away after attach completes. -import imp import os import sys import struct @@ -68,6 +67,7 @@ def exec_code(code, file, global_variables): ``sys.path[0]`` will be changed to the value of `file` without the filename. Both values are restored when this function exits. ''' + import imp original_main = sys.modules.get('__main__') global_variables = dict(global_variables) @@ -510,14 +510,14 @@ def re_test(source, pattern): d1 = {} d1_key = 'a' * self.maxstring_inner * 2 d1[d1_key] = d1_key - re_test(d1, "{'a+\.\.\.a+': 'a+\.\.\.a+'}") + re_test(d1, r"{'a+\.\.\.a+': 'a+\.\.\.a+'}") d2 = {d1_key : d1} - re_test(d2, "{'a+\.\.\.a+': {'a+\.\.\.a+': 'a+\.\.\.a+'}}") + re_test(d2, r"{'a+\.\.\.a+': {'a+\.\.\.a+': 'a+\.\.\.a+'}}") d3 = {d1_key : d2} if len(self.maxcollection) == 2: - re_test(d3, "{'a+\.\.\.a+': {'a+\.\.\.a+': {\.\.\.}}}") + re_test(d3, r"{'a+\.\.\.a+': {'a+\.\.\.a+': {\.\.\.}}}") else: - re_test(d3, "{'a+\.\.\.a+': {'a+\.\.\.a+': {'a+\.\.\.a+': 'a+\.\.\.a+'}}}") + re_test(d3, r"{'a+\.\.\.a+': {'a+\.\.\.a+': {'a+\.\.\.a+': 'a+\.\.\.a+'}}}") # Ensure empty dicts work test({}, '{}') diff --git a/pythonFiles/PythonTools/visualstudio_py_repl.py b/pythonFiles/PythonTools/visualstudio_py_repl.py index 74870fd696b3..ff84f9115dac 100644 --- a/pythonFiles/PythonTools/visualstudio_py_repl.py +++ b/pythonFiles/PythonTools/visualstudio_py_repl.py @@ -39,7 +39,6 @@ import select import time import struct -import imp import traceback import random import os @@ -553,6 +552,7 @@ class BasicReplBackend(ReplBackend): """Basic back end which executes all Python code in-proc""" def __init__(self, mod_name='__main__'): import threading + import imp ReplBackend.__init__(self) if mod_name is not None: if sys.platform == 'cli': diff --git a/pythonFiles/PythonTools/visualstudio_py_util.py b/pythonFiles/PythonTools/visualstudio_py_util.py index b3ed951e8718..58b798821750 100644 --- a/pythonFiles/PythonTools/visualstudio_py_util.py +++ b/pythonFiles/PythonTools/visualstudio_py_util.py @@ -22,7 +22,6 @@ # hasn't been loaded already, it will assume that the thread on which it is being loaded is the # main thread. This will cause issues when the thread goes away after attach completes. -import imp import os import sys import struct @@ -68,6 +67,7 @@ def exec_code(code, file, global_variables): ``sys.path[0]`` will be changed to the value of `file` without the filename. Both values are restored when this function exits. ''' + import imp original_main = sys.modules.get('__main__') global_variables = dict(global_variables) @@ -510,14 +510,14 @@ def re_test(source, pattern): d1 = {} d1_key = 'a' * self.maxstring_inner * 2 d1[d1_key] = d1_key - re_test(d1, "{'a+\.\.\.a+': 'a+\.\.\.a+'}") + re_test(d1, r"{'a+\.\.\.a+': 'a+\.\.\.a+'}") d2 = {d1_key : d1} - re_test(d2, "{'a+\.\.\.a+': {'a+\.\.\.a+': 'a+\.\.\.a+'}}") + re_test(d2, r"{'a+\.\.\.a+': {'a+\.\.\.a+': 'a+\.\.\.a+'}}") d3 = {d1_key : d2} if len(self.maxcollection) == 2: - re_test(d3, "{'a+\.\.\.a+': {'a+\.\.\.a+': {\.\.\.}}}") + re_test(d3, r"{'a+\.\.\.a+': {'a+\.\.\.a+': {\.\.\.}}}") else: - re_test(d3, "{'a+\.\.\.a+': {'a+\.\.\.a+': {'a+\.\.\.a+': 'a+\.\.\.a+'}}}") + re_test(d3, r"{'a+\.\.\.a+': {'a+\.\.\.a+': {'a+\.\.\.a+': 'a+\.\.\.a+'}}}") # Ensure empty dicts work test({}, '{}') From 091e5e5ce77d2197943bd0eb0f8f7aa341d80895 Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Mon, 11 Sep 2017 19:58:53 -0700 Subject: [PATCH 020/433] Fix #298 with remote debugging as well --- pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py b/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py index cf65c4116a64..0d523ed64cd6 100644 --- a/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py +++ b/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py @@ -1314,7 +1314,8 @@ def unblock(self): assert self._is_blocked assert self.id != thread.get_ident() # only someone else should unblock us - self._block_lock.release() + if self._block_lock.locked(): + self._block_lock.release() def schedule_work(self, work): self.unblock_work = work From 0a7026aa265ce6da2ae55d2eeefd7fa57f6f89b8 Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Tue, 13 Mar 2018 17:50:31 -0700 Subject: [PATCH 021/433] resolve real path in stack frames (useful when debugging built symlinked buck trees) --- src/client/debugger/Main.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/debugger/Main.ts b/src/client/debugger/Main.ts index b72860bc1d94..08dd71b00754 100644 --- a/src/client/debugger/Main.ts +++ b/src/client/debugger/Main.ts @@ -500,8 +500,9 @@ export class PythonDebugger extends LoggingDebugSession { return new StackFrame(frameId, frame.FunctionName); } else { + const realFilePath = fs.realpathSync(fileName); return new StackFrame(frameId, frame.FunctionName, - new Source(path.basename(frame.FileName), fileName), + new Source(path.basename(realFilePath), realFilePath), this.convertDebuggerLineToClient(frame.LineNo - 1), 1); } From e0cc7a5aadfb6496e63903fc1a1ba95c6976bfd5 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 13 Mar 2018 16:29:05 -0700 Subject: [PATCH 022/433] Drop angle brackets for XXX markers People are leaving them in --- .github/ISSUE_TEMPLATE.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index fadcc7ec1f20..1d6d6a97d1df 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -12,33 +12,33 @@ Otherwise **please** fill in the requested details below. "XXX" markers should n ## Environment data -- VS Code version: < XXX > -- Extension version (available under the Extensions sidebar): < XXX > -- OS and version: < XXX > -- Python version: < XXX; include the distribution details if applicable, e.g. Anaconda > -- Type of virtual environment used (if applicable): < XXX: N/A | venv | virtualenv | conda | ... > -- Relevant/affected Python packages and their versions: < XXX > +- VS Code version: XXX +- Extension version (available under the Extensions sidebar): XXX +- OS and version: XXX +- Python version (& distribution if applicable, e.g. Anaconda): XXX +- Type of virtual environment used (N/A | venv | virtualenv | conda | ...): XXX +- Relevant/affected Python packages and their versions: XXX ## Actual behavior -< XXX > +XXX ## Expected behavior -< XXX > +XXX ## Steps to reproduce: -1. < XXX > +1. XXX ## Logs Output for `Python` in the `Output` panel (`View`→`Output`, change the drop-down the upper-right of the `Output` panel to `Python`) ``` -< XXX > +XXX ``` Output from `Console` under the `Developer Tools` panel (toggle Developer Tools on under `Help`) ``` -< XXX > +XXX ``` From 0986b55c7914c4559e72f2380306130a7d036a82 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 13 Mar 2018 16:52:49 -0700 Subject: [PATCH 023/433] Add a news entry for the removal of Jupyter commands Fixes #1055 --- news/3 Code Health/1034.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/3 Code Health/1034.md diff --git a/news/3 Code Health/1034.md b/news/3 Code Health/1034.md new file mode 100644 index 000000000000..93047bb85ff6 --- /dev/null +++ b/news/3 Code Health/1034.md @@ -0,0 +1 @@ +Remove Jupyter commands. From 81a4dc860f8a9d5f17a44db524a340d36ed09f00 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 13 Mar 2018 16:53:13 -0700 Subject: [PATCH 024/433] Update npm package `vscode-extension-telemetry` to fix the warning 'os.tmpDir() deprecation' Fixes #1066 --- news/{2 Fixes/896.md => 3 Code Health/1066.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename news/{2 Fixes/896.md => 3 Code Health/1066.md} (100%) diff --git a/news/2 Fixes/896.md b/news/3 Code Health/1066.md similarity index 100% rename from news/2 Fixes/896.md rename to news/3 Code Health/1066.md From 289a51d0d5643a3bf744028b9f95f2e10d2982fd Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 13 Mar 2018 16:53:54 -0700 Subject: [PATCH 025/433] Create a pull request template --- .github/pull_request_template.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000000..c5f76ff975c1 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,8 @@ +Fixes # + +This pull request: +- [ ] Has a title summarizes what is changing +- [ ] Includes a [news entry](https://github.com/Microsoft/vscode-python/tree/master/news) file +- [ ] Has unit tests & [code coverage](https://codecov.io/gh/Microsoft/vscode-python) is not adversely affected (within reason) +- [ ] Works on all [actively maintained versions of Python](https://devguide.python.org/#status-of-python-branches) (e.g. Python 2.7 & the latest Python 3 release) +- [ ] Works on Windows 10, macOS, and Linux (e.g. considered file system case-sensitivity) From 0bf5a58d3a9d55f1a8e0830d40d35dd8e01eaa35 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 13 Mar 2018 16:54:07 -0700 Subject: [PATCH 026/433] Execute prospector as a module (using -m) * :hammer: run prospector as a module (with -m) * :fire: remove unwanted settings * :fire: remove redundant class and fix tests * :hammer: remove versions * :memo: news entry --- news/3 Code Health/982.md | 1 + requirements.txt | 14 +- src/client/common/installer/installer.ts | 236 ------------------ .../common/installer/productInstaller.ts | 9 +- src/client/formatters/helper.ts | 1 - src/client/linters/linterInfo.ts | 1 - src/test/.vscode/settings.json | 4 +- src/test/common/installer.test.ts | 3 - src/test/common/installer/installer.test.ts | 10 +- src/test/common/moduleInstaller.test.ts | 4 +- src/test/linters/lint.test.ts | 7 +- 11 files changed, 22 insertions(+), 268 deletions(-) create mode 100644 news/3 Code Health/982.md delete mode 100644 src/client/common/installer/installer.ts diff --git a/news/3 Code Health/982.md b/news/3 Code Health/982.md new file mode 100644 index 000000000000..00553e1194e9 --- /dev/null +++ b/news/3 Code Health/982.md @@ -0,0 +1 @@ +Execute prospector as a module (using -m). diff --git a/requirements.txt b/requirements.txt index 752eee82422f..e7f764089ecf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,10 @@ -autopep8==1.2.1 -yapf==0.6.2 -pylint==1.8.2 -pep8==1.7.0 -prospector==0.11.7 -flake8==2.6.0 -pydocstyle==1.0.0 +autopep8 +yapf +pylint +pep8 +prospector +flake8 +pydocstyle nose pytest fabric diff --git a/src/client/common/installer/installer.ts b/src/client/common/installer/installer.ts deleted file mode 100644 index f01113fa3ab0..000000000000 --- a/src/client/common/installer/installer.ts +++ /dev/null @@ -1,236 +0,0 @@ -import { inject, injectable, named } from 'inversify'; -import * as os from 'os'; -import * as path from 'path'; -import { ConfigurationTarget, Uri, window, workspace } from 'vscode'; -import * as vscode from 'vscode'; -import { IFormatterHelper } from '../../formatters/types'; -import { IServiceContainer } from '../../ioc/types'; -import { ILinterManager } from '../../linters/types'; -import { ITestsHelper } from '../../unittests/common/types'; -import { PythonSettings } from '../configSettings'; -import { STANDARD_OUTPUT_CHANNEL } from '../constants'; -import { IPlatformService } from '../platform/types'; -import { IProcessService, IPythonExecutionFactory } from '../process/types'; -import { ITerminalServiceFactory } from '../terminal/types'; -import { IInstaller, ILogger, InstallerResponse, IOutputChannel, ModuleNamePurpose, Product } from '../types'; -import { IInstallationChannelManager } from './types'; - -export { Product } from '../types'; - -const CTagsInsllationScript = os.platform() === 'darwin' ? 'brew install ctags' : 'sudo apt-get install exuberant-ctags'; - -// tslint:disable-next-line:variable-name -const ProductNames = new Map(); -ProductNames.set(Product.autopep8, 'autopep8'); -ProductNames.set(Product.flake8, 'flake8'); -ProductNames.set(Product.mypy, 'mypy'); -ProductNames.set(Product.nosetest, 'nosetest'); -ProductNames.set(Product.pep8, 'pep8'); -ProductNames.set(Product.pylama, 'pylama'); -ProductNames.set(Product.prospector, 'prospector'); -ProductNames.set(Product.pydocstyle, 'pydocstyle'); -ProductNames.set(Product.pylint, 'pylint'); -ProductNames.set(Product.pytest, 'pytest'); -ProductNames.set(Product.yapf, 'yapf'); -ProductNames.set(Product.rope, 'rope'); - -// tslint:disable-next-line:variable-name -const ProductInstallationPrompt = new Map(); -ProductInstallationPrompt.set(Product.ctags, 'Install CTags to enable Python workspace symbols'); - -enum ProductType { - Linter, - Formatter, - TestFramework, - RefactoringLibrary, - WorkspaceSymbols -} - -const ProductTypeNames = new Map(); -ProductTypeNames.set(ProductType.Formatter, 'Formatter'); -ProductTypeNames.set(ProductType.Linter, 'Linter'); -ProductTypeNames.set(ProductType.RefactoringLibrary, 'Refactoring library'); -ProductTypeNames.set(ProductType.TestFramework, 'Test Framework'); -ProductTypeNames.set(ProductType.WorkspaceSymbols, 'Workspace Symbols'); - -const ProductTypes = new Map(); -ProductTypes.set(Product.flake8, ProductType.Linter); -ProductTypes.set(Product.mypy, ProductType.Linter); -ProductTypes.set(Product.pep8, ProductType.Linter); -ProductTypes.set(Product.prospector, ProductType.Linter); -ProductTypes.set(Product.pydocstyle, ProductType.Linter); -ProductTypes.set(Product.pylama, ProductType.Linter); -ProductTypes.set(Product.pylint, ProductType.Linter); -ProductTypes.set(Product.ctags, ProductType.WorkspaceSymbols); -ProductTypes.set(Product.nosetest, ProductType.TestFramework); -ProductTypes.set(Product.pytest, ProductType.TestFramework); -ProductTypes.set(Product.unittest, ProductType.TestFramework); -ProductTypes.set(Product.autopep8, ProductType.Formatter); -ProductTypes.set(Product.yapf, ProductType.Formatter); -ProductTypes.set(Product.rope, ProductType.RefactoringLibrary); - -@injectable() -export class Installer implements IInstaller { - constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer, - @inject(IOutputChannel) @named(STANDARD_OUTPUT_CHANNEL) private outputChannel: vscode.OutputChannel) { - } - // tslint:disable-next-line:no-empty - public dispose() { } - public async promptToInstall(product: Product, resource?: Uri): Promise { - const productType = ProductTypes.get(product)!; - const productTypeName = ProductTypeNames.get(productType)!; - const productName = ProductNames.get(product)!; - - const installOption = ProductInstallationPrompt.has(product) ? ProductInstallationPrompt.get(product)! : `Install ${productName}`; - const alternateFormatter = product === Product.autopep8 ? 'yapf' : 'autopep8'; - const useOtherFormatter = `Use '${alternateFormatter}' formatter`; - const options: string[] = []; - options.push(installOption); - if (productType === ProductType.Formatter) { - options.push(...[useOtherFormatter]); - } - const item = await window.showErrorMessage(`${productTypeName} ${productName} is not installed`, ...options); - if (!item) { - return InstallerResponse.Ignore; - } - switch (item) { - case installOption: { - return this.install(product, resource); - } - case useOtherFormatter: { - return this.updateSetting('formatting.provider', alternateFormatter, resource) - .then(() => InstallerResponse.Installed); - } - default: { - throw new Error('Invalid selection'); - } - } - } - public translateProductToModuleName(product: Product, purpose: ModuleNamePurpose): string { - switch (product) { - case Product.mypy: return 'mypy'; - case Product.nosetest: { - return purpose === ModuleNamePurpose.install ? 'nose' : 'nosetests'; - } - case Product.pylama: return 'pylama'; - case Product.prospector: return 'prospector'; - case Product.pylint: return 'pylint'; - case Product.pytest: return 'pytest'; - case Product.autopep8: return 'autopep8'; - case Product.pep8: return 'pep8'; - case Product.pydocstyle: return 'pydocstyle'; - case Product.yapf: return 'yapf'; - case Product.flake8: return 'flake8'; - case Product.unittest: return 'unittest'; - case Product.rope: return 'rope'; - default: { - throw new Error(`Product ${product} cannot be installed as a Python Module.`); - } - } - } - public async install(product: Product, resource?: Uri): Promise { - if (product === Product.unittest) { - return InstallerResponse.Installed; - } - if (product === Product.ctags) { - return this.installCTags(); - } - - const channels = this.serviceContainer.get(IInstallationChannelManager); - const installer = await channels.getInstallationChannel(product, resource); - if (!installer) { - return InstallerResponse.Ignore; - } - - const moduleName = this.translateProductToModuleName(product, ModuleNamePurpose.install); - const logger = this.serviceContainer.get(ILogger); - await installer.installModule(moduleName, resource) - .catch(logger.logError.bind(logger, `Error in installing the module '${moduleName}'`)); - - return this.isInstalled(product) - .then(isInstalled => isInstalled ? InstallerResponse.Installed : InstallerResponse.Ignore); - } - public async isInstalled(product: Product, resource?: Uri): Promise { - if (product === Product.unittest) { - return true; - } - let moduleName: string | undefined; - try { - moduleName = this.translateProductToModuleName(product, ModuleNamePurpose.run); - // tslint:disable-next-line:no-empty - } catch { } - - // User may have customized the module name or provided the fully qualifieid path. - const executableName = this.getExecutableNameFromSettings(product, resource); - - const isModule = typeof moduleName === 'string' && moduleName.length > 0 && path.basename(executableName) === executableName; - // Prospector is an exception, it can be installed as a module, but not run as one. - if (product !== Product.prospector && isModule) { - const pythonProcess = await this.serviceContainer.get(IPythonExecutionFactory).create(resource); - return pythonProcess.isModuleInstalled(executableName); - } else { - const process = this.serviceContainer.get(IProcessService); - const prospectorPath = PythonSettings.getInstance(resource).linting.prospectorPath; - return process.exec(prospectorPath, ['--version'], { mergeStdOutErr: true }) - .then(() => true) - .catch(() => false); - } - } - private installCTags() { - if (this.serviceContainer.get(IPlatformService).isWindows) { - this.outputChannel.appendLine('Install Universal Ctags Win32 to enable support for Workspace Symbols'); - this.outputChannel.appendLine('Download the CTags binary from the Universal CTags site.'); - this.outputChannel.appendLine('Option 1: Extract ctags.exe from the downloaded zip to any folder within your PATH so that Visual Studio Code can run it.'); - this.outputChannel.appendLine('Option 2: Extract to any folder and add the path to this folder to the command setting.'); - this.outputChannel.appendLine('Option 3: Extract to any folder and define that path in the python.workspaceSymbols.ctagsPath setting of your user settings file (settings.json).'); - this.outputChannel.show(); - } else { - const terminalServiceFactory = this.serviceContainer.get(ITerminalServiceFactory); - const terminalService = terminalServiceFactory.getTerminalService(); - const logger = this.serviceContainer.get(ILogger); - terminalService.sendCommand(CTagsInsllationScript, []) - .catch(logger.logError.bind(logger, `Failed to install ctags. Script sent '${CTagsInsllationScript}'.`)); - } - return InstallerResponse.Ignore; - } - - // tslint:disable-next-line:no-any - private updateSetting(setting: string, value: any, resource?: Uri) { - if (resource && workspace.getWorkspaceFolder(resource)) { - const pythonConfig = workspace.getConfiguration('python', resource); - return pythonConfig.update(setting, value, ConfigurationTarget.Workspace); - } else { - const pythonConfig = workspace.getConfiguration('python'); - return pythonConfig.update(setting, value, true); - } - } - private getExecutableNameFromSettings(product: Product, resource?: Uri): string { - const settings = PythonSettings.getInstance(resource); - const productType = ProductTypes.get(product)!; - switch (productType) { - case ProductType.WorkspaceSymbols: return settings.workspaceSymbols.ctagsPath; - case ProductType.TestFramework: { - const testHelper = this.serviceContainer.get(ITestsHelper); - const settingsPropNames = testHelper.getSettingsPropertyNames(product); - if (!settingsPropNames.pathName) { - // E.g. in the case of UnitTests we don't allow customizing the paths. - return this.translateProductToModuleName(product, ModuleNamePurpose.run); - } - return settings.unitTest[settingsPropNames.pathName] as string; - } - case ProductType.Formatter: { - const formatHelper = this.serviceContainer.get(IFormatterHelper); - const settingsPropNames = formatHelper.getSettingsPropertyNames(product); - return settings.formatting[settingsPropNames.pathName] as string; - } - case ProductType.RefactoringLibrary: return this.translateProductToModuleName(product, ModuleNamePurpose.run); - case ProductType.Linter: { - const linterManager = this.serviceContainer.get(ILinterManager); - return linterManager.getLinterInfo(product).pathName(resource); - } - default: { - throw new Error(`Unrecognized Product '${product}'`); - } - } - } -} diff --git a/src/client/common/installer/productInstaller.ts b/src/client/common/installer/productInstaller.ts index c404906d6cb5..15610472ad80 100644 --- a/src/client/common/installer/productInstaller.ts +++ b/src/client/common/installer/productInstaller.ts @@ -61,6 +61,9 @@ abstract class BaseInstaller { } public async isInstalled(product: Product, resource?: Uri): Promise { + if (product === Product.unittest) { + return true; + } let moduleName: string | undefined; try { moduleName = translateProductToModule(product, ModuleNamePurpose.run); @@ -71,14 +74,12 @@ abstract class BaseInstaller { const executableName = this.getExecutableNameFromSettings(product, resource); const isModule = typeof moduleName === 'string' && moduleName.length > 0 && path.basename(executableName) === executableName; - // Prospector is an exception, it can be installed as a module, but not run as one. - if (product !== Product.prospector && isModule) { + if (isModule) { const pythonProcess = await this.serviceContainer.get(IPythonExecutionFactory).create(resource); return pythonProcess.isModuleInstalled(executableName); } else { const process = this.serviceContainer.get(IProcessService); - const prospectorPath = this.configService.getSettings(resource).linting.prospectorPath; - return process.exec(prospectorPath, ['--version'], { mergeStdOutErr: true }) + return process.exec(executableName, ['--version'], { mergeStdOutErr: true }) .then(() => true) .catch(() => false); } diff --git a/src/client/formatters/helper.ts b/src/client/formatters/helper.ts index e5930c2f0683..b491c40baaa2 100644 --- a/src/client/formatters/helper.ts +++ b/src/client/formatters/helper.ts @@ -39,7 +39,6 @@ export class FormatterHelper implements IFormatterHelper { let moduleName: string | undefined; // If path information is not available, then treat it as a module, - // except for prospector as that needs to be run as an executable (it's a Python package). if (path.basename(execPath) === execPath) { moduleName = execPath; } diff --git a/src/client/linters/linterInfo.ts b/src/client/linters/linterInfo.ts index 10f18affe708..12f3e1835453 100644 --- a/src/client/linters/linterInfo.ts +++ b/src/client/linters/linterInfo.ts @@ -60,7 +60,6 @@ export class LinterInfo implements ILinterInfo { let moduleName: string | undefined; // If path information is not available, then treat it as a module, - // Except for prospector as that needs to be run as an executable (its a python package). if (path.basename(execPath) === execPath) { moduleName = execPath; } diff --git a/src/test/.vscode/settings.json b/src/test/.vscode/settings.json index d0a948e74069..cc64e708bea1 100644 --- a/src/test/.vscode/settings.json +++ b/src/test/.vscode/settings.json @@ -11,10 +11,8 @@ "-p", "*test*.py" ], - "python.formatting.formatOnSave": false, "python.sortImports.args": [], "python.linting.lintOnSave": false, - "python.linting.lintOnTextChange": false, "python.linting.enabled": true, "python.linting.pep8Enabled": false, "python.linting.prospectorEnabled": false, @@ -23,4 +21,4 @@ "python.linting.mypyEnabled": false, "python.formatting.provider": "yapf", "python.linting.pylintUseMinimalCheckers": false -} \ No newline at end of file +} diff --git a/src/test/common/installer.test.ts b/src/test/common/installer.test.ts index d00258561d34..c491d93cd680 100644 --- a/src/test/common/installer.test.ts +++ b/src/test/common/installer.test.ts @@ -75,9 +75,6 @@ suite('Installer', () => { if (args.length > 1 && args[0] === '-c' && args[1] === `import ${moduleName}`) { checkInstalledDef.resolve(true); } - if (product === Product.prospector && args.length > 0 && args[0] === '--version') { - checkInstalledDef.resolve(true); - } callback({ stdout: '' }); }); await installer.isInstalled(product, resource); diff --git a/src/test/common/installer/installer.test.ts b/src/test/common/installer/installer.test.ts index 2d080f672983..84ef685e3d78 100644 --- a/src/test/common/installer/installer.test.ts +++ b/src/test/common/installer/installer.test.ts @@ -6,7 +6,6 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as TypeMoq from 'typemoq'; import { Disposable, OutputChannel, Uri } from 'vscode'; import { EnumEx } from '../../../client/common/enumUtils'; -import { Installer } from '../../../client/common/installer/installer'; import { ProductInstaller } from '../../../client/common/installer/productInstaller'; import { IInstallationChannelManager, IModuleInstaller } from '../../../client/common/installer/types'; import { IDisposableRegistry, ILogger, InstallerResponse, ModuleNamePurpose, Product } from '../../../client/common/types'; @@ -19,16 +18,15 @@ suite('Module Installerx', () => { [undefined, Uri.file('resource')].forEach(resource => { EnumEx.getNamesAndValues(Product).forEach(product => { let disposables: Disposable[] = []; - let installer: Installer; + let installer: ProductInstaller; let installationChannel: TypeMoq.IMock; let moduleInstaller: TypeMoq.IMock; let serviceContainer: TypeMoq.IMock; - let productInstallerFactory: ProductInstaller; setup(() => { serviceContainer = TypeMoq.Mock.ofType(); const outputChannel = TypeMoq.Mock.ofType(); - installer = new Installer(serviceContainer.object, outputChannel.object); + installer = new ProductInstaller(serviceContainer.object, outputChannel.object); disposables = []; serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IDisposableRegistry), TypeMoq.It.isAny())).returns(() => disposables); @@ -41,8 +39,6 @@ suite('Module Installerx', () => { moduleInstaller.setup((x: any) => x.then).returns(() => undefined); installationChannel.setup(i => i.getInstallationChannel(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(moduleInstaller.object)); installationChannel.setup(i => i.getInstallationChannel(TypeMoq.It.isAny())).returns(() => Promise.resolve(moduleInstaller.object)); - - productInstallerFactory = new ProductInstaller(serviceContainer.object, outputChannel.object); }); teardown(() => { disposables.forEach(disposable => { @@ -91,7 +87,7 @@ suite('Module Installerx', () => { moduleInstaller.setup(m => m.installModule(TypeMoq.It.isValue(moduleName), TypeMoq.It.isValue(resource))).returns(() => Promise.reject(new Error('UnitTesting'))); try { - await productInstallerFactory.install(product.value, resource); + await installer.install(product.value, resource); } catch (ex) { moduleInstaller.verify(m => m.installModule(TypeMoq.It.isValue(moduleName), TypeMoq.It.isValue(resource)), TypeMoq.Times.once()); } diff --git a/src/test/common/moduleInstaller.test.ts b/src/test/common/moduleInstaller.test.ts index 91c025d8c49a..768d37afc19f 100644 --- a/src/test/common/moduleInstaller.test.ts +++ b/src/test/common/moduleInstaller.test.ts @@ -5,9 +5,9 @@ import { ConfigurationTarget, Uri } from 'vscode'; import { PythonSettings } from '../../client/common/configSettings'; import { ConfigurationService } from '../../client/common/configuration/service'; import { CondaInstaller } from '../../client/common/installer/condaInstaller'; -import { Installer } from '../../client/common/installer/installer'; import { PipEnvInstaller } from '../../client/common/installer/pipEnvInstaller'; import { PipInstaller } from '../../client/common/installer/pipInstaller'; +import { ProductInstaller } from '../../client/common/installer/productInstaller'; import { IModuleInstaller } from '../../client/common/installer/types'; import { Logger } from '../../client/common/logger'; import { PersistentStateFactory } from '../../client/common/persistentState'; @@ -59,7 +59,7 @@ suite('Module Installer', () => { ioc.serviceManager.addSingleton(IPersistentStateFactory, PersistentStateFactory); ioc.serviceManager.addSingleton(ILogger, Logger); - ioc.serviceManager.addSingleton(IInstaller, Installer); + ioc.serviceManager.addSingleton(IInstaller, ProductInstaller); mockTerminalService = TypeMoq.Mock.ofType(); const mockTerminalFactory = TypeMoq.Mock.ofType(); diff --git a/src/test/linters/lint.test.ts b/src/test/linters/lint.test.ts index 8e73a2878abf..56c0dc88afdf 100644 --- a/src/test/linters/lint.test.ts +++ b/src/test/linters/lint.test.ts @@ -186,10 +186,9 @@ suite('Linting', () => { test('Disable Prospector and test linter', async () => { await testEnablingDisablingOfLinter(Product.prospector, false); }); - // test('Enable Prospector and test linter', async () => { - // Fails on Travis. Can be run locally though. - // await testEnablingDisablingOfLinter(Product.prospector, true); - // }); + test('Enable Prospector and test linter', async () => { + await testEnablingDisablingOfLinter(Product.prospector, true); + }); test('Disable Pydocstyle and test linter', async () => { await testEnablingDisablingOfLinter(Product.pydocstyle, false); }); From 0dbc19400511a3a52777239dbc3762791f08fddb Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Wed, 14 Mar 2018 11:33:23 -0700 Subject: [PATCH 027/433] Add the 2018.2.1 notes --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d746daec36e..c21837fe22c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 2018.2.1 (09 Mar 2018) + +### Fixes + +1. Check for `Pipfile` and not `pipfile` when looking for pipenv usage + (thanks to [Will Thompson for the fix](https://github.com/wjt)) + ## 2018.2.0 (08 Mar 2018) [Release pushed by one week] From f23cff8bb6ad30ae084b9c7ce3427e48930b8c54 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Wed, 14 Mar 2018 11:33:52 -0700 Subject: [PATCH 028/433] Manually added to changelog --- news/2 Fixes/404.md | 1 - 1 file changed, 1 deletion(-) delete mode 100644 news/2 Fixes/404.md diff --git a/news/2 Fixes/404.md b/news/2 Fixes/404.md deleted file mode 100644 index f3c6b68e7ccc..000000000000 --- a/news/2 Fixes/404.md +++ /dev/null @@ -1 +0,0 @@ -Determine pipenv usage based on finding a file named `Pipfile`, not `pipfile` (case-sensitive) From 7fea3f63ee329c91acfec92c8e397f597fdcbb79 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 15 Mar 2018 09:44:12 -0700 Subject: [PATCH 029/433] Trigger incremental build compilation only when typescript files are modified (#1041) * :hammer: improve compilation condition * :memo: change log * Fixes #1040 --- gulpfile.js | 7 ++++++- news/3 Code Health/1040.md | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 news/3 Code Health/1040.md diff --git a/gulpfile.js b/gulpfile.js index 05918ab7bc6d..44a7f54f15fa 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -36,6 +36,11 @@ const all = [ 'src/client/**/*', ]; +const tsFilter = [ + 'src/**/*.ts', + 'src/client/**/*.ts', +]; + const indentationFilter = [ 'src/**/*.ts', '!**/typings/**/*', @@ -68,7 +73,7 @@ gulp.task('compile', () => run({ mode: 'compile', skipFormatCheck: true, skipInd gulp.task('watch', ['hygiene-modified', 'hygiene-watch']); -gulp.task('hygiene-watch', () => gulp.watch(all, debounce(() => run({ mode: 'changes' }), 1000))); +gulp.task('hygiene-watch', () => gulp.watch(tsFilter, debounce(() => run({ mode: 'changes' }), 1000))); gulp.task('hygiene-all', () => run({ mode: 'all' })); diff --git a/news/3 Code Health/1040.md b/news/3 Code Health/1040.md new file mode 100644 index 000000000000..88e881779fbd --- /dev/null +++ b/news/3 Code Health/1040.md @@ -0,0 +1 @@ +Trigger incremental build compilation only when typescript files are modified. From 88b23a98c6ec16379f2ac868106ff91cdbd389fa Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 15 Mar 2018 09:44:58 -0700 Subject: [PATCH 030/433] Support debug console as a debug option (#1047) * :sparkles: support debug console as a debug option * :bug: fix tests * :sparkles: use integrated terminal as the default console * Fixes #950 * Fixes #526 --- news/1 Enhancements/526.md | 1 + news/1 Enhancements/950.md | 1 + package.json | 3 ++- src/client/debugger/configProviders/baseProvider.ts | 2 +- src/client/debugger/configProviders/pythonV2Provider.ts | 4 +--- 5 files changed, 6 insertions(+), 5 deletions(-) create mode 100644 news/1 Enhancements/526.md create mode 100644 news/1 Enhancements/950.md diff --git a/news/1 Enhancements/526.md b/news/1 Enhancements/526.md new file mode 100644 index 000000000000..829c4ac990f7 --- /dev/null +++ b/news/1 Enhancements/526.md @@ -0,0 +1 @@ +When debugging, use `Integrated Terminal` as the default console. diff --git a/news/1 Enhancements/950.md b/news/1 Enhancements/950.md new file mode 100644 index 000000000000..bba2053af7fa --- /dev/null +++ b/news/1 Enhancements/950.md @@ -0,0 +1 @@ +Support `Debug Console` as a `console` option for the Experimental Debugger. diff --git a/package.json b/package.json index a22c95dac590..22a307f5e694 100644 --- a/package.json +++ b/package.json @@ -496,7 +496,7 @@ "externalTerminal" ], "description": "Where to launch the debug target: internal console, integrated terminal, or external terminal.", - "default": "none" + "default": "integratedTerminal" }, "cwd": { "type": "string", @@ -822,6 +822,7 @@ }, "console": { "enum": [ + "none", "integratedTerminal", "externalTerminal" ], diff --git a/src/client/debugger/configProviders/baseProvider.ts b/src/client/debugger/configProviders/baseProvider.ts index 7a8a73240e13..2459fe8f4f1f 100644 --- a/src/client/debugger/configProviders/baseProvider.ts +++ b/src/client/debugger/configProviders/baseProvider.ts @@ -51,7 +51,7 @@ export abstract class BaseConfigurationProvider implements DebugConfigurationPro debugConfiguration.stopOnEntry = false; } if (!debugConfiguration.console) { - debugConfiguration.console = 'none'; + debugConfiguration.console = 'integratedTerminal'; } // If using a terminal, then never open internal console. if (debugConfiguration.console !== 'none' && !debugConfiguration.internalConsoleOptions) { diff --git a/src/client/debugger/configProviders/pythonV2Provider.ts b/src/client/debugger/configProviders/pythonV2Provider.ts index 6f81abdfa1a5..78ad13af10a0 100644 --- a/src/client/debugger/configProviders/pythonV2Provider.ts +++ b/src/client/debugger/configProviders/pythonV2Provider.ts @@ -16,10 +16,8 @@ export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvide } protected provideDefaults(workspaceFolder: Uri, debugConfiguration: PythonDebugConfiguration): void { super.provideDefaults(workspaceFolder, debugConfiguration); + debugConfiguration.stopOnEntry = false; - if (debugConfiguration.console !== 'externalTerminal' && debugConfiguration.console !== 'integratedTerminal') { - debugConfiguration.console = 'integratedTerminal'; - } // Add PTVSD specific flags. const ptvsdDebugConfigurationFlags = debugConfiguration as PTVSDDebugConfiguration; From 59117062e7a4662a52b35cdd055049c1884df7ec Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 15 Mar 2018 09:47:30 -0700 Subject: [PATCH 031/433] Ignore test results when debugging unit tests Fixes #1043 --- news/2 Fixes/1043.md | 1 + src/client/unittests/nosetest/runner.ts | 2 +- src/client/unittests/pytest/runner.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 news/2 Fixes/1043.md diff --git a/news/2 Fixes/1043.md b/news/2 Fixes/1043.md new file mode 100644 index 000000000000..cf0541628a52 --- /dev/null +++ b/news/2 Fixes/1043.md @@ -0,0 +1 @@ +Ignore test results when debugging unit tests. diff --git a/src/client/unittests/nosetest/runner.ts b/src/client/unittests/nosetest/runner.ts index 3627ea308dbc..71b169683a3b 100644 --- a/src/client/unittests/nosetest/runner.ts +++ b/src/client/unittests/nosetest/runner.ts @@ -77,7 +77,7 @@ export function runTest(serviceContainer: IServiceContainer, testResultsService: return run(serviceContainer, 'nosetest', runOptions); } }).then(() => { - return updateResultsFromLogFiles(options.tests, xmlLogFile, testResultsService); + return options.debug ? options.tests : updateResultsFromLogFiles(options.tests, xmlLogFile, testResultsService); }).then(result => { xmlLogFileCleanup(); return result; diff --git a/src/client/unittests/pytest/runner.ts b/src/client/unittests/pytest/runner.ts index 4f7a5fd3053e..1b2178e08f61 100644 --- a/src/client/unittests/pytest/runner.ts +++ b/src/client/unittests/pytest/runner.ts @@ -52,7 +52,7 @@ export function runTest(serviceContainer: IServiceContainer, testResultsService: return run(serviceContainer, 'pytest', runOptions); } }).then(() => { - return updateResultsFromLogFiles(options.tests, xmlLogFile, testResultsService); + return options.debug ? options.tests : updateResultsFromLogFiles(options.tests, xmlLogFile, testResultsService); }).then(result => { xmlLogFileCleanup(); return result; From 7b204bf4fc331d975189bc3178a04abef848af93 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 15 Mar 2018 09:47:59 -0700 Subject: [PATCH 032/433] Ensure conda installer is not used for non-conda environments (#1065) * :bug: ensure conda install is not used for non conda environments * :white_check_mark: add test * :memo: change log * Fixes #969 --- news/2 Fixes/969.md | 1 + src/client/common/installer/condaInstaller.ts | 4 ++-- src/test/common/moduleInstaller.test.ts | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 news/2 Fixes/969.md diff --git a/news/2 Fixes/969.md b/news/2 Fixes/969.md new file mode 100644 index 000000000000..a991de1a919d --- /dev/null +++ b/news/2 Fixes/969.md @@ -0,0 +1 @@ +Ensure conda installer is not used for non-conda environments. diff --git a/src/client/common/installer/condaInstaller.ts b/src/client/common/installer/condaInstaller.ts index a4802817b8d9..10acd9f3bb64 100644 --- a/src/client/common/installer/condaInstaller.ts +++ b/src/client/common/installer/condaInstaller.ts @@ -30,8 +30,8 @@ export class CondaInstaller extends ModuleInstaller implements IModuleInstaller * @returns {Promise} Whether conda is supported as a module installer or not. */ public async isSupported(resource?: Uri): Promise { - if (this.isCondaAvailable !== undefined) { - return this.isCondaAvailable!; + if (this.isCondaAvailable === false) { + return false; } const condaLocator = this.serviceContainer.get(ICondaService); this.isCondaAvailable = await condaLocator.isCondaAvailable(); diff --git a/src/test/common/moduleInstaller.test.ts b/src/test/common/moduleInstaller.test.ts index 768d37afc19f..42e83eb93524 100644 --- a/src/test/common/moduleInstaller.test.ts +++ b/src/test/common/moduleInstaller.test.ts @@ -167,6 +167,22 @@ suite('Module Installer', () => { const condaInstaller = new CondaInstaller(serviceContainer.object); await expect(condaInstaller.isSupported()).to.eventually.equal(true, 'Conda is not supported'); }); + test('Ensure conda is not supported even if conda is available', async () => { + const serviceContainer = TypeMoq.Mock.ofType(); + + const configService = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationService))).returns(() => configService.object); + const settings = TypeMoq.Mock.ofType(); + const pythonPath = 'pythonABC'; + settings.setup(s => s.pythonPath).returns(() => pythonPath); + configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ICondaService))).returns(() => condaService.object); + condaService.setup(c => c.isCondaAvailable()).returns(() => Promise.resolve(true)); + condaService.setup(c => c.isCondaEnvironment(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(false)); + + const condaInstaller = new CondaInstaller(serviceContainer.object); + await expect(condaInstaller.isSupported()).to.eventually.equal(false, 'Conda should not be supported'); + }); test('Validate pip install arguments', async () => { const interpreterPath = await getCurrentPythonPath(); From f92aa5445a6eec733c6603fa4a5dff4b09816e45 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 15 Mar 2018 09:48:25 -0700 Subject: [PATCH 033/433] Fixes issue that display incorrect interpreter briefly before updating it to the right value (#1054) * :bug: fire change event only when changes are detected * :memo: change log * :hammer: fix typos * Fixes #981 --- news/2 Fixes/981.md | 1 + src/client/common/configSettings.ts | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 news/2 Fixes/981.md diff --git a/news/2 Fixes/981.md b/news/2 Fixes/981.md new file mode 100644 index 000000000000..8621551fbbd4 --- /dev/null +++ b/news/2 Fixes/981.md @@ -0,0 +1 @@ +Fixes issue that display incorrect interpreter briefly before updating it to the right value. diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index 9da482ded147..d4a16e776b0c 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -53,6 +53,10 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { this.workspaceRoot = workspaceFolder ? workspaceFolder : vscode.Uri.file(__dirname); this.disposables.push(vscode.workspace.onDidChangeConfiguration(() => { this.initializeSettings(); + + // If workspace config changes, then we could have a cascading effect of on change events. + // Let's defer the change notification. + setTimeout(() => this.emit('change'), 1); })); this.initializeSettings(); @@ -292,10 +296,6 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { launchArgs: [], activateEnvironment: true }; - - // If workspace config changes, then we could have a cascading effect of on change events. - // Let's defer the change notification. - setTimeout(() => this.emit('change'), 1); } public get pythonPath(): string { From 8d29855ae50fca4e5530a074eab80350126cf501 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 15 Mar 2018 09:49:05 -0700 Subject: [PATCH 034/433] Enable unit testing of redirecting stdout and stderr in experimental debugger (#1050) * :memo: change log * :white_check_mark: enable testing * :bug: pass necessary flags to experimental debugger * Fixes #1048 --- news/3 Code Health/1048.md | 1 + src/test/debugger/misc.test.ts | 22 ++++++++++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) create mode 100644 news/3 Code Health/1048.md diff --git a/news/3 Code Health/1048.md b/news/3 Code Health/1048.md new file mode 100644 index 000000000000..c49a6f27e1a1 --- /dev/null +++ b/news/3 Code Health/1048.md @@ -0,0 +1 @@ +Enable unit testing of stdout and stderr redirection for the experimental debugger. diff --git a/src/test/debugger/misc.test.ts b/src/test/debugger/misc.test.ts index 348219134d34..c6dfab6068cd 100644 --- a/src/test/debugger/misc.test.ts +++ b/src/test/debugger/misc.test.ts @@ -51,7 +51,7 @@ const THREAD_TIMEOUT = 10000; await sleep(1000); }); function buildLauncArgs(pythonFile: string, stopOnEntry: boolean = false): LaunchRequestArguments { - return { + const options: LaunchRequestArguments = { program: path.join(debugFilesPath, pythonFile), cwd: debugFilesPath, stopOnEntry, @@ -63,6 +63,13 @@ const THREAD_TIMEOUT = 10000; logToFile: false, type: debuggerType }; + + // Custom experimental debugger options (filled in by DebugConfigurationProvider). + if (debuggerType === 'pythonExperimental') { + (options as any).redirectOutput = true; + } + + return options; } test('Should run program to the end', async () => { @@ -84,23 +91,18 @@ const THREAD_TIMEOUT = 10000; debugClient.waitForEvent('stopped') ]); }); - test('test stderr output', async function () { - if (debuggerType !== 'python') { - return this.skip(); - } + test('test stderr output for Python', async () => { + const output = debuggerType === 'python' ? 'stdout' : 'stderr'; await Promise.all([ debugClient.configurationSequence(), debugClient.launch(buildLauncArgs('stdErrOutput.py', false)), debugClient.waitForEvent('initialized'), //TODO: ptvsd does not differentiate. - debugClient.assertOutput('stdout', 'error output'), + debugClient.assertOutput(output, 'error output'), debugClient.waitForEvent('terminated') ]); }); - test('Test stdout output', async function () { - if (debuggerType !== 'python') { - return this.skip(); - } + test('Test stdout output', async () => { await Promise.all([ debugClient.configurationSequence(), debugClient.launch(buildLauncArgs('stdOutOutput.py', false)), From 746b2aef439e91f4bf5a75fb5bb70556bcb6b575 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 15 Mar 2018 09:49:54 -0700 Subject: [PATCH 035/433] Updated npm dependencies in devDependencies and fix TypeScript compilation issues (#1074) * :package: updated npm dependencies and fix linter issues * :memo: add news entry * :package: updated yarn.lock * Fixes #1042 --- news/3 Code Health/1042.md | 1 + package.json | 58 +- .../common/installer/moduleInstaller.ts | 3 +- src/client/common/process/currentProcess.ts | 7 +- src/client/common/process/proc.ts | 10 +- src/client/debugger/mainV2.ts | 20 +- src/test/index.ts | 2 +- tslint.json | 8 +- typings/memory-stream.d.ts | 30 - yarn.lock | 1256 ++++++++++------- 10 files changed, 808 insertions(+), 587 deletions(-) create mode 100644 news/3 Code Health/1042.md delete mode 100644 typings/memory-stream.d.ts diff --git a/news/3 Code Health/1042.md b/news/3 Code Health/1042.md new file mode 100644 index 000000000000..94b355687cda --- /dev/null +++ b/news/3 Code Health/1042.md @@ -0,0 +1 @@ +Updated npm dependencies in devDependencies and fix TypeScript compilation issues. diff --git a/package.json b/package.json index 22a307f5e694..929b93083d90 100644 --- a/package.json +++ b/package.json @@ -1644,61 +1644,61 @@ "xml2js": "^0.4.17" }, "devDependencies": { - "@types/chai": "4.0.6", + "@types/chai": "^4.1.2", "@types/chai-as-promised": "^7.1.0", "@types/del": "^3.0.0", "@types/event-stream": "^3.3.33", - "@types/fs-extra": "^4.0.2", + "@types/fs-extra": "^5.0.1", "@types/get-port": "^3.2.0", - "@types/glob": "^5.0.34", - "@types/iconv-lite": "0.0.1", + "@types/glob": "^5.0.35", + "@types/iconv-lite": "^0.0.1", "@types/istanbul": "^0.4.29", - "@types/lodash": "^4.14.74", + "@types/lodash": "^4.14.104", "@types/md5": "^2.1.32", - "@types/mocha": "^2.2.43", - "@types/node": "^6.0.40", - "@types/semver": "^5.4.0", - "@types/shortid": "0.0.29", - "@types/sinon": "^2.3.2", + "@types/mocha": "^2.2.48", + "@types/node": "^9.4.7", + "@types/semver": "^5.5.0", + "@types/shortid": "^0.0.29", + "@types/sinon": "^4.3.0", "@types/untildify": "^3.0.0", - "@types/uuid": "^3.3.27", + "@types/uuid": "^3.4.3", "@types/winreg": "^1.2.30", - "@types/xml2js": "^0.4.0", + "@types/xml2js": "^0.4.2", "JSONStream": "^1.3.2", - "azure-storage": "^2.7.0", + "azure-storage": "^2.8.1", "chai": "^4.1.2", "chai-as-promised": "^7.1.1", "codecov": "^3.0.0", - "colors": "^1.1.2", + "colors": "^1.2.1", "debounce": "^1.1.0", - "decache": "^4.3.0", + "decache": "^4.4.0", "del": "^3.0.0", "event-stream": "^3.3.4", "gulp": "^3.9.1", "gulp-debounced-watch": "^1.0.4", - "gulp-filter": "^5.0.1", + "gulp-filter": "^5.1.0", "gulp-gitmodified": "^1.1.1", - "gulp-json-editor": "^2.2.1", + "gulp-json-editor": "^2.2.2", "gulp-sourcemaps": "^2.6.4", - "gulp-typescript": "^3.2.2", - "gulp-watch": "^4.3.11", + "gulp-typescript": "^4.0.1", + "gulp-watch": "^5.0.0", "husky": "^0.14.3", "is-running": "^2.1.0", "istanbul": "^0.4.5", - "mocha": "^2.3.3", + "mocha": "^5.0.4", "relative": "^3.0.2", - "remap-istanbul": "^0.9.5", - "retyped-diff-match-patch-tsd-ambient": "^1.0.0-0", + "remap-istanbul": "^0.10.1", + "retyped-diff-match-patch-tsd-ambient": "^1.0.0-1", "shortid": "^2.2.8", - "sinon": "^2.3.6", - "tslint": "^5.7.0", - "tslint-eslint-rules": "^4.1.1", - "tslint-microsoft-contrib": "^5.0.1", + "sinon": "^4.4.5", + "tslint": "^5.9.1", + "tslint-eslint-rules": "^5.1.0", + "tslint-microsoft-contrib": "^5.0.3", "typemoq": "^2.1.0", - "typescript": "^2.6.2", - "typescript-formatter": "^6.0.0", + "typescript": "^2.7.2", + "typescript-formatter": "^7.1.0", "vscode": "^1.1.5", - "vscode-debugadapter-testsupport": "^1.25.0" + "vscode-debugadapter-testsupport": "^1.27.0" }, "__metadata": { "id": "f1f59ae4-9318-4f3c-a9b5-81b2eaa5f8a5", diff --git a/src/client/common/installer/moduleInstaller.ts b/src/client/common/installer/moduleInstaller.ts index 7f6b028b1a93..39b07c97bed0 100644 --- a/src/client/common/installer/moduleInstaller.ts +++ b/src/client/common/installer/moduleInstaller.ts @@ -12,6 +12,7 @@ import { IInterpreterLocatorService, INTERPRETER_LOCATOR_SERVICE, InterpreterTyp import { IServiceContainer } from '../../ioc/types'; import { PythonSettings } from '../configSettings'; import { STANDARD_OUTPUT_CHANNEL } from '../constants'; +import { noop } from '../core.utils'; import { IFileSystem } from '../platform/types'; import { ITerminalServiceFactory } from '../terminal/types'; import { ExecutionInfo, IOutputChannel } from '../types'; @@ -60,7 +61,7 @@ export abstract class ModuleInstaller { fs.open(filePath, fs.constants.O_CREAT | fs.constants.O_RDWR, (error, fd) => { if (!error) { fs.close(fd, (e) => { - fs.unlink(filePath); + fs.unlink(filePath, noop); }); } return resolve(!error); diff --git a/src/client/common/process/currentProcess.ts b/src/client/common/process/currentProcess.ts index e7f5b5efe293..fc2bb97bb01d 100644 --- a/src/client/common/process/currentProcess.ts +++ b/src/client/common/process/currentProcess.ts @@ -1,3 +1,5 @@ +// tslint:disable:no-any + import { injectable } from 'inversify'; import { ICurrentProcess } from '../types'; import { EnvironmentVariables } from '../variables/types'; @@ -5,12 +7,11 @@ import { EnvironmentVariables } from '../variables/types'; @injectable() export class CurrentProcess implements ICurrentProcess { public on = (event: string | symbol, listener: Function): this => { - process.on(event, listener); - // tslint:disable-next-line:no-any + process.on(event as any, listener as any); return process as any; } public get env(): EnvironmentVariables { - return process.env; + return process.env as any as EnvironmentVariables; } public get argv(): string[] { return process.argv; diff --git a/src/client/common/process/proc.ts b/src/client/common/process/proc.ts index 5d8cefd935cb..426519f2ba68 100644 --- a/src/client/common/process/proc.ts +++ b/src/client/common/process/proc.ts @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +// tslint:disable:no-any + import { spawn } from 'child_process'; import { inject, injectable } from 'inversify'; import { Observable } from 'rxjs/Observable'; @@ -33,8 +35,8 @@ export class ProcessService implements IProcessService { const disposables: Disposable[] = []; const on = (ee: NodeJS.EventEmitter, name: string, fn: Function) => { - ee.on(name, fn); - disposables.push({ dispose: () => ee.removeListener(name, fn) }); + ee.on(name, fn as any); + disposables.push({ dispose: () => ee.removeListener(name, fn as any) }); }; if (options.token) { @@ -90,8 +92,8 @@ export class ProcessService implements IProcessService { const disposables: Disposable[] = []; const on = (ee: NodeJS.EventEmitter, name: string, fn: Function) => { - ee.on(name, fn); - disposables.push({ dispose: () => ee.removeListener(name, fn) }); + ee.on(name, fn as any); + disposables.push({ dispose: () => ee.removeListener(name, fn as any) }); }; if (options.token) { diff --git a/src/client/debugger/mainV2.ts b/src/client/debugger/mainV2.ts index 4706260601b1..53f4d60d17cd 100644 --- a/src/client/debugger/mainV2.ts +++ b/src/client/debugger/mainV2.ts @@ -12,7 +12,7 @@ if ((Reflect as any).metadata === undefined) { import { Socket } from 'net'; import { EOL } from 'os'; import * as path from 'path'; -import { PassThrough } from 'stream'; +import { PassThrough, Writable } from 'stream'; import { Disposable } from 'vscode'; import { DebugSession, ErrorDestination, logger, OutputEvent, TerminatedEvent } from 'vscode-debugadapter'; import { LogLevel } from 'vscode-debugadapter/lib/logger'; @@ -46,7 +46,7 @@ export class PythonDebugger extends DebugSession { public debugServer?: BaseDebugServer; public debugClient?: DebugClient<{}>; public client = createDeferred(); - private supportsRunInTerminalRequest: boolean; + private supportsRunInTerminalRequest: boolean = false; constructor(private readonly serviceContainer: IServiceContainer) { super(false); } @@ -150,8 +150,8 @@ export class PythonDebugger extends DebugSession { */ class DebugManager implements Disposable { // #region VS Code debug Streams. - private inputStream: NodeJS.ReadStream | Socket; - private outputStream: NodeJS.WriteStream | Socket; + private inputStream!: NodeJS.ReadStream | Socket; + private outputStream!: NodeJS.WriteStream | Socket; // #endregion // #region Proxy Streams (used to listen in on the communications). private readonly throughOutputStream: PassThrough; @@ -162,19 +162,19 @@ class DebugManager implements Disposable { private readonly debugSessionInputStream: PassThrough; // #endregion // #region Streams used to communicate with PTVSD. - private ptvsdSocket: Socket; + private ptvsdSocket!: Socket; // #endregion private readonly inputProtocolParser: IProtocolParser; private readonly outputProtocolParser: IProtocolParser; private readonly protocolLogger: IProtocolLogger; private readonly protocolMessageWriter: IProtocolMessageWriter; - private isServerMode: boolean; + private isServerMode: boolean = false; private readonly disposables: Disposable[] = []; - private hasShutdown: boolean; + private hasShutdown: boolean = false; private debugSession?: PythonDebugger; private ptvsdProcessId?: number; - private killPTVSDProcess: boolean; - private terminatedEventSent: boolean; + private killPTVSDProcess: boolean = false; + private terminatedEventSent: boolean = false; private readonly initializeRequestDeferred: Deferred; private get initializeRequest(): Promise { return this.initializeRequestDeferred.promise; @@ -347,7 +347,7 @@ class DebugManager implements Disposable { // Wait for PTVSD to reply back with initialized event. debugSoketProtocolParser.once('event_initialized', (initialized: DebugProtocol.InitializedEvent) => { // Get ready for PTVSD to communicate directly with VS Code. - this.inputStream.unpipe(this.debugSessionInputStream); + (this.inputStream as any as NodeJS.ReadStream).unpipe(this.debugSessionInputStream); this.debugSessionOutputStream.unpipe(this.outputStream); this.inputStream.pipe(this.ptvsdSocket!); diff --git a/src/test/index.ts b/src/test/index.ts index 3717f37fd021..135ca5d8b6c1 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -8,7 +8,7 @@ import { IS_CI_SERVER, IS_CI_SERVER_TEST_DEBUGGER, IS_MULTI_ROOT_TEST } from './ import * as testRunner from './testRunner'; process.env.VSC_PYTHON_CI_TEST = '1'; -process.env.IS_MULTI_ROOT_TEST = IS_MULTI_ROOT_TEST; +process.env.IS_MULTI_ROOT_TEST = IS_MULTI_ROOT_TEST.toString(); // If running on CI server and we're running the debugger tests, then ensure we only run debug tests. // We do this to ensure we only run debugger test, as debugger tests are very flaky on CI. diff --git a/tslint.json b/tslint.json index 4701c1abcbbc..2746486ce48a 100644 --- a/tslint.json +++ b/tslint.json @@ -53,6 +53,12 @@ "no-empty-interface": false, "no-bitwise": false, "eofline": true, - "switch-final-break": false + "switch-final-break": false, + "no-implicit-dependencies": [ + "vscode" + ], + "no-unnecessary-type-assertion": false, + "no-submodule-imports": false, + "no-redundant-jsdoc": false } } diff --git a/typings/memory-stream.d.ts b/typings/memory-stream.d.ts deleted file mode 100644 index 5efd28e0d2dc..000000000000 --- a/typings/memory-stream.d.ts +++ /dev/null @@ -1,30 +0,0 @@ -declare module 'memory-streams' { - export class ReadableStream implements NodeJS.ReadableStream { - readable: boolean; - read(size?: number): string | Buffer; - setEncoding(encoding: string): void; - pause(): this; - resume(): this; - isPaused(): boolean; - pipe(destination: T, options?: { end?: boolean; }): T; - unpipe(destination?: T): void; - unshift(chunk: string): void; - unshift(chunk: Buffer): void; - unshift(chunk: any); - wrap(oldStream: NodeJS.ReadableStream): NodeJS.ReadableStream; - addListener(event: string | symbol, listener: Function): this; - on(event: string | symbol, listener: Function): this; - once(event: string | symbol, listener: Function): this; - removeListener(event: string | symbol, listener: Function): this; - removeAllListeners(event?: string | symbol): this; - setMaxListeners(n: number): this; - getMaxListeners(): number; - listeners(event: string | symbol): Function[]; - emit(event: string | symbol, ...args: any[]): boolean; - listenerCount(type: string | symbol): number; - prependListener(event: string | symbol, listener: Function): this; - prependOnceListener(event: string | symbol, listener: Function): this; - eventNames(): (string | symbol)[]; - constructor(content: string); - } -} diff --git a/yarn.lock b/yarn.lock index 85dbba812292..ed24c3f29707 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19,19 +19,21 @@ normalize-path "^2.0.1" through2 "^2.0.3" +"@sinonjs/formatio@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-2.0.0.tgz#84db7e9eb5531df18a8c5e0bfb6e449e55e654b2" + dependencies: + samsam "1.3.0" + "@types/chai-as-promised@^7.1.0": version "7.1.0" resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.0.tgz#010b04cde78eacfb6e72bfddb3e58fe23c2e78b9" dependencies: "@types/chai" "*" -"@types/chai@*": - version "4.0.10" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.0.10.tgz#0eb222c7353adde8e0980bea04165d4d3b6afef3" - -"@types/chai@4.0.6": - version "4.0.6" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.0.6.tgz#9cb5a7fb7dd83be0cfcaafdbd95a2b5dd351762f" +"@types/chai@*", "@types/chai@^4.1.2": + version "4.1.2" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.1.2.tgz#f1af664769cfb50af805431c407425ed619daa21" "@types/commander@^2.11.0": version "2.12.2" @@ -52,12 +54,12 @@ "@types/node" "*" "@types/events@*": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@types/events/-/events-1.1.0.tgz#93b1be91f63c184450385272c47b6496fd028e02" + version "1.2.0" + resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" -"@types/fs-extra@^4.0.2": - version "4.0.7" - resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-4.0.7.tgz#02533262386b5a6b9a49797dc82feffdf269140a" +"@types/fs-extra@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.0.1.tgz#cd856fbbdd6af2c11f26f8928fd8644c9e9616c9" dependencies: "@types/node" "*" @@ -65,15 +67,15 @@ version "3.2.0" resolved "https://registry.yarnpkg.com/@types/get-port/-/get-port-3.2.0.tgz#f9e0a11443cc21336470185eae3dfba4495d29bc" -"@types/glob@*", "@types/glob@^5.0.34": - version "5.0.34" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-5.0.34.tgz#ee626c9be3da877d717911c6101eee0a9871bbf4" +"@types/glob@*", "@types/glob@^5.0.35": + version "5.0.35" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-5.0.35.tgz#1ae151c802cece940443b5ac246925c85189f32a" dependencies: "@types/events" "*" "@types/minimatch" "*" "@types/node" "*" -"@types/iconv-lite@0.0.1": +"@types/iconv-lite@^0.0.1": version "0.0.1" resolved "https://registry.yarnpkg.com/@types/iconv-lite/-/iconv-lite-0.0.1.tgz#aa3b8bda2be512b1ae0a057b942e869c370a5569" dependencies: @@ -83,9 +85,9 @@ version "0.4.29" resolved "https://registry.yarnpkg.com/@types/istanbul/-/istanbul-0.4.29.tgz#29c8cbb747ac57280965545dc58514ba0dbb99af" -"@types/lodash@^4.14.74": - version "4.14.91" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.91.tgz#794611b28056d16b5436059c6d800b39d573cd3a" +"@types/lodash@^4.14.104": + version "4.14.104" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.104.tgz#53ee2357fa2e6e68379341d92eb2ecea4b11bb80" "@types/md5@^2.1.32": version "2.1.32" @@ -94,40 +96,34 @@ "@types/node" "*" "@types/minimatch@*": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.2.tgz#09c06877e478a5d5f32ce5017c2eb2b33006f6f5" + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" -"@types/mocha@^2.2.43": - version "2.2.45" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.45.tgz#816572b6e45164526a36d4faa123e8267d6d5d0a" - dependencies: - "@types/node" "*" +"@types/mocha@^2.2.48": + version "2.2.48" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.48.tgz#3523b126a0b049482e1c3c11877460f76622ffab" -"@types/node@*": - version "8.5.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.5.2.tgz#83b8103fa9a2c2e83d78f701a9aa7c9539739aa5" +"@types/node@*", "@types/node@^9.4.7": + version "9.4.7" + resolved "https://registry.yarnpkg.com/@types/node/-/node-9.4.7.tgz#57d81cd98719df2c9de118f2d5f3b1120dcd7275" -"@types/node@^6.0.40": - version "6.0.95" - resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.95.tgz#0d027612a77c55b84497ff90a4a7d597e5ac0fab" +"@types/semver@^5.4.0", "@types/semver@^5.5.0": + version "5.5.0" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45" -"@types/semver@^5.4.0": - version "5.4.0" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.4.0.tgz#f3658535af7f1f502acd6da7daf405ffeb1f7ee4" - -"@types/shortid@0.0.29": +"@types/shortid@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/shortid/-/shortid-0.0.29.tgz#8093ee0416a6e2bf2aa6338109114b3fbffa0e9b" -"@types/sinon@^2.3.2": - version "2.3.7" - resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-2.3.7.tgz#e92c2fed3297eae078d78d1da032b26788b4af86" +"@types/sinon@^4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-4.3.0.tgz#7f53915994a00ccea24f4e0c24709822ed11a3b1" "@types/untildify@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/untildify/-/untildify-3.0.0.tgz#cd3e6624e46ccf292d3823fb48fa90dda0deaec0" -"@types/uuid@^3.3.27": +"@types/uuid@^3.4.3": version "3.4.3" resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.3.tgz#121ace265f5569ce40f4f6d0ff78a338c732a754" dependencies: @@ -137,7 +133,7 @@ version "1.2.30" resolved "https://registry.yarnpkg.com/@types/winreg/-/winreg-1.2.30.tgz#91d6710e536d345b9c9b017c574cf6a8da64c518" -"@types/xml2js@^0.4.0": +"@types/xml2js@^0.4.2": version "0.4.2" resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.2.tgz#a4b84b3879ffd4710953fd92cabfde9a8a4e8456" dependencies: @@ -190,6 +186,12 @@ amdefine@>=0.0.4, amdefine@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" +ansi-colors@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-1.1.0.tgz#6374b4dd5d4718ff3ce27a671a3b1cad077132a9" + dependencies: + ansi-wrap "^0.1.0" + ansi-cyan@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/ansi-cyan/-/ansi-cyan-0.1.1.tgz#538ae528af8982f28ae30d86f2f17456d2609873" @@ -224,13 +226,13 @@ ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" -ansi-styles@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88" +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" dependencies: color-convert "^1.9.0" -ansi-wrap@0.1.0: +ansi-wrap@0.1.0, ansi-wrap@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" @@ -241,6 +243,19 @@ anymatch@^1.3.0: micromatch "^2.1.5" normalize-path "^2.0.0" +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + +append-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/append-buffer/-/append-buffer-1.0.2.tgz#d8220cf466081525efea50614f3de6514dfa58f1" + dependencies: + buffer-equal "^1.0.0" + applicationinsights@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.1.tgz#53446b830fe8d5d619eee2a278b31d3d25030927" @@ -269,8 +284,8 @@ are-we-there-yet@~1.1.2: readable-stream "^2.0.6" argparse@^1.0.7: - version "1.0.9" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" dependencies: sprintf-js "~1.0.2" @@ -362,8 +377,8 @@ assert-plus@^0.2.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" assertion-error@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c" + version "1.1.0" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" assign-symbols@^1.0.0: version "1.0.0" @@ -401,20 +416,20 @@ aws4@^1.2.1, aws4@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" -azure-storage@^2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/azure-storage/-/azure-storage-2.7.0.tgz#106c35f0ba6c551f928bd394c026e44d0401ffcf" +azure-storage@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/azure-storage/-/azure-storage-2.8.1.tgz#ecb9d050ef1395e79ffbb652c02fe643687bec63" dependencies: browserify-mime "~1.2.9" extend "~1.2.1" json-edm-parser "0.1.2" md5.js "1.3.4" readable-stream "~2.0.0" - request "~2.81.0" + request "~2.83.0" underscore "~1.8.3" uuid "^3.0.0" - validator "~3.35.0" - xml2js "0.2.7" + validator "~9.4.1" + xml2js "0.2.8" xmlbuilder "0.4.3" babel-code-frame@^6.22.0: @@ -461,6 +476,10 @@ block-stream@*: dependencies: inherits "~2.0.0" +bluebird@^3.0.5: + version "3.5.1" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" + boom@2.x.x: version "2.10.1" resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" @@ -480,8 +499,8 @@ boom@5.x.x: hoek "4.x.x" brace-expansion@^1.0.0, brace-expansion@^1.1.7: - version "1.1.8" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" dependencies: balanced-match "^1.0.0" concat-map "0.0.1" @@ -494,9 +513,9 @@ braces@^1.8.2: preserve "^0.2.0" repeat-element "^1.1.2" -braces@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.0.tgz#a46941cb5fb492156b3d6a656e06c35364e3e66e" +braces@^2.3.0, braces@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.1.tgz#7086c913b4e5a08dbe37ac0ee6a2500c4ba691bb" dependencies: arr-flatten "^1.1.0" array-unique "^0.3.2" @@ -504,6 +523,7 @@ braces@^2.3.0: extend-shallow "^2.0.1" fill-range "^4.0.0" isobject "^3.0.1" + kind-of "^6.0.2" repeat-element "^1.1.2" snapdragon "^0.8.1" snapdragon-node "^2.0.1" @@ -514,6 +534,10 @@ browser-stdout@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" +browser-stdout@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + browserify-mime@~1.2.9: version "1.2.9" resolved "https://registry.yarnpkg.com/browserify-mime/-/browserify-mime-1.2.9.tgz#aeb1af28de6c0d7a6a2ce40adb68ff18422af31f" @@ -522,6 +546,10 @@ buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" +buffer-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" + builtin-modules@^1.0.0, builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -611,13 +639,13 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba" +chalk@^2.3.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.2.tgz#250dc96b07491bfd601e648d66ddf5f60c7a5c65" dependencies: - ansi-styles "^3.1.0" + ansi-styles "^3.2.1" escape-string-regexp "^1.0.5" - supports-color "^4.0.0" + supports-color "^5.3.0" charenc@~0.0.1: version "0.0.2" @@ -642,22 +670,39 @@ chokidar@^1.6.1: optionalDependencies: fsevents "^1.0.0" +chokidar@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.2.tgz#4dc65139eeb2714977735b6a35d06e97b494dfd7" + dependencies: + anymatch "^2.0.0" + async-each "^1.0.0" + braces "^2.3.0" + glob-parent "^3.1.0" + inherits "^2.0.1" + is-binary-path "^1.0.0" + is-glob "^4.0.0" + normalize-path "^2.1.1" + path-is-absolute "^1.0.0" + readdirp "^2.0.0" + upath "^1.0.0" + optionalDependencies: + fsevents "^1.0.0" + ci-info@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.2.tgz#03561259db48d0474c8bdc90f5b47b068b6bbfb4" + version "1.1.3" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.3.tgz#710193264bb05c77b8c90d02f5aaf22216a667b2" circular-json@^0.3.1: version "0.3.3" resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" class-utils@^0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.5.tgz#17e793103750f9627b2176ea34cfd1b565903c80" + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" dependencies: arr-union "^3.1.0" define-property "^0.2.5" isobject "^3.0.0" - lazy-cache "^2.0.2" static-extend "^0.1.1" cliui@^2.1.0: @@ -693,12 +738,12 @@ clone@^2.1.1: resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.1.tgz#d217d1e961118e3ac9a4b8bba3285553bf647cdb" cloneable-readable@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.0.0.tgz#a6290d413f217a61232f95e458ff38418cfb0117" + version "1.1.1" + resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.1.1.tgz#c27a4f3a943ca37bed9b01c7d572ee61b1302b15" dependencies: inherits "^2.0.1" - process-nextick-args "^1.0.6" - through2 "^2.0.1" + process-nextick-args "^2.0.0" + readable-stream "^2.3.5" co@^4.6.0: version "4.6.0" @@ -737,35 +782,27 @@ color-support@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" -colors@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" +colors@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.1.tgz#f4a3d302976aaf042356ba1ade3b1a2c62d9d794" -combined-stream@^1.0.5, combined-stream@~1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" +combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" dependencies: delayed-stream "~1.0.0" -commander@*, commander@^2.11.0, commander@^2.9.0: - version "2.12.2" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.2.tgz#0f5946c427ed9ec0d91a46bb9def53e54650e555" - -commander@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-0.6.1.tgz#fa68a14f6a945d54dbbe50d8cdb3320e9e3b1a06" +commander@*, commander@^2.11.0, commander@^2.12.1, commander@^2.9.0: + version "2.15.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.0.tgz#ad2a23a1c3b036e392469b8012cec6b33b4c1322" commander@2.11.0: version "2.11.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" -commander@2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.3.0.tgz#fd430e889832ec353b9acd1de217c11cb3eef873" - commandpost@^1.0.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/commandpost/-/commandpost-1.2.1.tgz#2e9c4c7508b9dc704afefaa91cab92ee6054cc68" + version "1.3.0" + resolved "https://registry.yarnpkg.com/commandpost/-/commandpost-1.3.0.tgz#e0654e4933abf58406c7d3b77ce747083da178c4" component-emitter@^1.2.1: version "1.2.1" @@ -786,7 +823,7 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" -convert-source-map@1.X, convert-source-map@^1.1.1: +convert-source-map@1.X, convert-source-map@^1.1.1, convert-source-map@^1.5.0: version "1.5.1" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" @@ -841,7 +878,7 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -dateformat@^1.0.11, dateformat@^1.0.7-1.2.3: +dateformat@^1.0.7-1.2.3: version "1.0.12" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-1.0.12.tgz#9f124b67594c937ff706932e4a642cca8dbbfee9" dependencies: @@ -868,12 +905,6 @@ debug-fabulous@1.X: memoizee "0.4.X" object-assign "4.X" -debug@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" - dependencies: - ms "0.7.1" - debug@3.1.0, debug@3.X: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" @@ -886,9 +917,9 @@ debug@^2.2.0, debug@^2.3.3: dependencies: ms "2.0.0" -decache@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/decache/-/decache-4.3.0.tgz#a395e407095698ac8a6def01f2a6a4cb7638f635" +decache@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/decache/-/decache-4.4.0.tgz#6f6df6b85d7e7c4410a932ffc26489b78e9acd13" dependencies: callsite "^1.0.0" @@ -920,9 +951,9 @@ deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" -deepmerge@~0.2.7: - version "0.2.10" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-0.2.10.tgz#8906bf9e525a4fbf1b203b2afcb4640249821219" +deepmerge@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.1.0.tgz#511a54fff405fc346f0240bb270a3e9533a31102" defaults@^1.0.0: version "1.0.3" @@ -930,6 +961,13 @@ defaults@^1.0.0: dependencies: clone "^1.0.2" +define-properties@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" + dependencies: + foreach "^2.0.5" + object-keys "^1.0.8" + define-property@^0.2.5: version "0.2.5" resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" @@ -942,6 +980,13 @@ define-property@^1.0.0: dependencies: is-descriptor "^1.0.0" +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + del@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/del/-/del-3.0.0.tgz#53ecf699ffcbcb39637691ab13baf160819766e5" @@ -969,18 +1014,18 @@ detect-file@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" -detect-indent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-2.0.0.tgz#720ff51e4d97b76884f6bf57292348b13dfde939" - dependencies: - get-stdin "^3.0.0" - minimist "^1.1.0" - repeating "^1.1.0" +detect-indent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" detect-libc@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" +detect-newline@2.X: + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" + diagnostic-channel-publishers@0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3" @@ -991,27 +1036,19 @@ diagnostic-channel@0.2.0: dependencies: semver "^5.3.0" -detect-newline@2.X: - version "2.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" - diff-match-patch@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.0.tgz#1cc3c83a490d67f95d91e39f6ad1f2e086b63048" -diff@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-1.4.0.tgz#7f28d2eb9ee7b15a97efd89ce63dcfdaa3ccbabf" - diff@3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75" -diff@^3.1.0, diff@^3.2.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.4.0.tgz#b1d85507daf3964828de54b37d0d73ba67dda56c" +diff@3.5.0, diff@^3.1.0, diff@^3.2.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" -doctrine@^0.7.2: +doctrine@0.7.2: version "0.7.2" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-0.7.2.tgz#7cb860359ba3be90e040b26b729ce4bfa654c523" dependencies: @@ -1028,9 +1065,9 @@ duplexer@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" -duplexify@^3.2.0: - version "3.5.1" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.1.tgz#4e1516be68838bc90a49994f0b39a6e5960befcd" +duplexify@^3.2.0, duplexify@^3.5.3: + version "3.5.4" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.4.tgz#4bb46c1796eabebeec4ca9a2e66b808cb7a3d8b4" dependencies: end-of-stream "^1.0.0" inherits "^2.0.1" @@ -1043,6 +1080,16 @@ ecc-jsbn@~0.1.1: dependencies: jsbn "~0.1.0" +editorconfig@^0.13.2: + version "0.13.3" + resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.13.3.tgz#e5219e587951d60958fd94ea9a9a008cdeff1b34" + dependencies: + bluebird "^3.0.5" + commander "^2.9.0" + lru-cache "^3.2.0" + semver "^5.1.0" + sigmund "^1.0.1" + editorconfig@^0.15.0: version "0.15.0" resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.0.tgz#b6dd4a0b6b9e76ce48e066bdc15381aebb8804fd" @@ -1054,9 +1101,9 @@ editorconfig@^0.15.0: semver "^5.4.1" sigmund "^1.0.1" -end-of-stream@^1.0.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.0.tgz#7a90d833efda6cfa6eac0f4949dbb0fad3a63206" +end-of-stream@^1.0.0, end-of-stream@^1.1.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" dependencies: once "^1.4.0" @@ -1103,10 +1150,6 @@ es6-weak-map@^2.0.2: es6-iterator "^2.0.1" es6-symbol "^3.1.1" -escape-string-regexp@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz#4dbc2fe674e71949caf3fb2695ce7f2dc1d9a8d1" - escape-string-regexp@1.0.5, escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -1203,7 +1246,7 @@ extend-shallow@^2.0.1: dependencies: is-extendable "^0.1.0" -extend-shallow@^3.0.0: +extend-shallow@^3.0.0, extend-shallow@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" dependencies: @@ -1224,9 +1267,9 @@ extglob@^0.3.1: dependencies: is-extglob "^1.0.0" -extglob@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.3.tgz#55e019d0c95bf873949c737b7e5172dba84ebb29" +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" dependencies: array-unique "^0.3.2" define-property "^1.0.0" @@ -1254,8 +1297,8 @@ fancy-log@^1.1.0: time-stamp "^1.0.0" fast-deep-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" + version "1.1.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" fast-json-stable-stringify@^2.0.0: version "2.0.0" @@ -1338,6 +1381,13 @@ flagged-respawn@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-1.0.0.tgz#4e79ae9b2eb38bf86b3bb56bf3e0a56aa5fcabd7" +flush-write-stream@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.2.tgz#c81b90d8746766f1a609a46809946c45dd8ae417" + dependencies: + inherits "^2.0.1" + readable-stream "^2.0.4" + for-in@^1.0.1, for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -1354,6 +1404,10 @@ for-own@^1.0.0: dependencies: for-in "^1.0.1" +foreach@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -1367,19 +1421,13 @@ form-data@~2.1.1: mime-types "^2.1.12" form-data@~2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf" + version "2.3.2" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" dependencies: asynckit "^0.4.0" - combined-stream "^1.0.5" + combined-stream "1.0.6" mime-types "^2.1.12" -formatio@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.2.0.tgz#f3b2167d9068c4698a8d51f4f760a39a54d818eb" - dependencies: - samsam "1.x" - fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -1398,6 +1446,13 @@ fs-extra@^4.0.2: jsonfile "^4.0.0" universalify "^0.1.0" +fs-mkdirp-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz#0b7815fc3201c6a69e14db98ce098c16935259eb" + dependencies: + graceful-fs "^4.1.11" + through2 "^2.0.3" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -1426,6 +1481,10 @@ fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: mkdirp ">=0.5 0" rimraf "2" +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + fuzzy@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/fuzzy/-/fuzzy-0.1.3.tgz#4c76ec2ff0ac1a36a9dccf9a00df8623078d4ed8" @@ -1467,10 +1526,6 @@ get-port@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" -get-stdin@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-3.0.2.tgz#c1ced24b9039b38ded85bdf161e57713b6dd4abe" - get-stdin@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" @@ -1498,7 +1553,7 @@ glob-parent@^2.0.0: dependencies: is-glob "^2.0.0" -glob-parent@^3.0.0, glob-parent@^3.0.1: +glob-parent@^3.0.0, glob-parent@^3.0.1, glob-parent@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" dependencies: @@ -1529,6 +1584,21 @@ glob-stream@^5.3.2: to-absolute-glob "^0.1.1" unique-stream "^2.0.2" +glob-stream@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-6.1.0.tgz#7045c99413b3eb94888d83ab46d0b404cc7bdde4" + dependencies: + extend "^3.0.0" + glob "^7.1.1" + glob-parent "^3.1.0" + is-negated-glob "^1.0.0" + ordered-read-streams "^1.0.0" + pumpify "^1.3.5" + readable-stream "^2.1.5" + remove-trailing-separator "^1.0.1" + to-absolute-glob "^2.0.0" + unique-stream "^2.0.2" + glob-watcher@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-0.0.6.tgz#b95b4a8df74b39c83298b0c05c978b4d9a3b710b" @@ -1541,13 +1611,6 @@ glob2base@^0.0.12: dependencies: find-index "^0.1.1" -glob@3.2.11: - version "3.2.11" - resolved "https://registry.yarnpkg.com/glob/-/glob-3.2.11.tgz#4a973f635b9190f715d10987d5c00fd2815ebe3d" - dependencies: - inherits "2" - minimatch "0.3" - glob@7.1.2, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" @@ -1623,12 +1686,12 @@ globule@~0.1.0: minimatch "~0.2.11" glogg@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.0.tgz#7fe0f199f57ac906cf512feead8f90ee4a284fc5" + version "1.0.1" + resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.1.tgz#dcf758e44789cc3f3d32c1f3562a3676e6a34810" dependencies: sparkles "^1.0.0" -graceful-fs@4.X, graceful-fs@^4.0.0, graceful-fs@^4.1.2, graceful-fs@^4.1.6: +graceful-fs@4.X, graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -1646,10 +1709,6 @@ growl@1.10.3: version "1.10.3" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.3.tgz#1926ba90cf3edfe2adb4927f5880bc22c66c790f" -growl@1.9.2: - version "1.9.2" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" - gulp-chmod@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/gulp-chmod/-/gulp-chmod-2.0.0.tgz#00c390b928a0799b251accf631aa09e01cc6299c" @@ -1666,7 +1725,7 @@ gulp-debounced-watch@^1.0.4: gulp-watch "^4.3.4" object-assign "^3.0.0" -gulp-filter@^5.0.1: +gulp-filter@^5.0.1, gulp-filter@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/gulp-filter/-/gulp-filter-5.1.0.tgz#a05e11affb07cf7dcf41a7de1cb7b63ac3783e73" dependencies: @@ -1691,15 +1750,15 @@ gulp-gunzip@1.0.0: through2 "~0.6.5" vinyl "~0.4.6" -gulp-json-editor@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/gulp-json-editor/-/gulp-json-editor-2.2.1.tgz#7c4dd7477e8d06dc5dc49c0b81e745cdb04f97bb" +gulp-json-editor@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/gulp-json-editor/-/gulp-json-editor-2.2.2.tgz#8ae627a083a95d86d14d9bffe0527e906d0e61ee" dependencies: - deepmerge "~0.2.7" - detect-indent "^2.0.0" - gulp-util "~3.0.0" - js-beautify "~1.5.4" - through2 "~0.5.0" + deepmerge "^2.0.1" + detect-indent "^5.0.0" + js-beautify "^1.7.5" + plugin-error "^1.0.1" + through2 "^2.0.3" gulp-remote-src@^0.4.3: version "0.4.3" @@ -1746,14 +1805,16 @@ gulp-symdest@^1.1.0: queue "^3.1.0" vinyl-fs "^2.4.3" -gulp-typescript@^3.2.2: - version "3.2.3" - resolved "https://registry.yarnpkg.com/gulp-typescript/-/gulp-typescript-3.2.3.tgz#32d52ab97b97c4ce070c0419db08ea3af514d720" +gulp-typescript@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/gulp-typescript/-/gulp-typescript-4.0.1.tgz#fd9d2e06a06ea3c1c15885b82ebfb037c07d75b2" dependencies: - gulp-util "~3.0.7" - source-map "~0.5.3" - through2 "~2.0.1" - vinyl-fs "~2.4.3" + ansi-colors "^1.0.1" + plugin-error "^0.1.2" + source-map "^0.6.1" + through2 "^2.0.3" + vinyl "^2.1.0" + vinyl-fs "^3.0.0" gulp-untar@^0.0.6: version "0.0.6" @@ -1765,30 +1826,7 @@ gulp-untar@^0.0.6: tar "^2.2.1" through2 "~2.0.3" -gulp-util@3.0.7: - version "3.0.7" - resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.7.tgz#78925c4b8f8b49005ac01a011c557e6218941cbb" - dependencies: - array-differ "^1.0.0" - array-uniq "^1.0.2" - beeper "^1.0.0" - chalk "^1.0.0" - dateformat "^1.0.11" - fancy-log "^1.1.0" - gulplog "^1.0.0" - has-gulplog "^0.1.0" - lodash._reescape "^3.0.0" - lodash._reevaluate "^3.0.0" - lodash._reinterpolate "^3.0.0" - lodash.template "^3.0.0" - minimist "^1.1.0" - multipipe "^0.1.2" - object-assign "^3.0.0" - replace-ext "0.0.1" - through2 "^2.0.0" - vinyl "^0.5.0" - -gulp-util@^3.0.0, gulp-util@^3.0.7, gulp-util@~3.0.0, gulp-util@~3.0.7, gulp-util@~3.0.8: +gulp-util@^3.0.0, gulp-util@^3.0.7, gulp-util@~3.0.8: version "3.0.8" resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f" dependencies: @@ -1836,7 +1874,7 @@ gulp-vinyl-zip@^2.1.0: yauzl "^2.2.1" yazl "^2.2.1" -gulp-watch@^4.3.11, gulp-watch@^4.3.4: +gulp-watch@^4.3.4: version "4.3.11" resolved "https://registry.yarnpkg.com/gulp-watch/-/gulp-watch-4.3.11.tgz#162fc563de9fc770e91f9a7ce3955513a9a118c0" dependencies: @@ -1851,6 +1889,21 @@ gulp-watch@^4.3.11, gulp-watch@^4.3.4: vinyl "^1.2.0" vinyl-file "^2.0.0" +gulp-watch@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/gulp-watch/-/gulp-watch-5.0.0.tgz#6fb03ab1735972e0d2866475b568555836dfd0eb" + dependencies: + anymatch "^1.3.0" + chokidar "^2.0.0" + glob-parent "^3.0.1" + gulp-util "^3.0.7" + object-assign "^4.1.0" + path-is-absolute "^1.0.1" + readable-stream "^2.2.2" + slash "^1.0.0" + vinyl "^2.1.0" + vinyl-file "^2.0.0" + gulp@^3.9.1: version "3.9.1" resolved "https://registry.yarnpkg.com/gulp/-/gulp-3.9.1.tgz#571ce45928dd40af6514fc4011866016c13845b4" @@ -1936,12 +1989,20 @@ has-flag@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + has-gulplog@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/has-gulplog/-/has-gulplog-0.1.0.tgz#6414c82913697da51590397dafb12f22967811ce" dependencies: sparkles "^1.0.0" +has-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" + has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -2007,8 +2068,8 @@ hoek@2.x.x: resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" hoek@4.x.x: - version "4.2.0" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d" + version "4.2.1" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb" homedir-polyfill@^1.0.1: version "1.0.1" @@ -2017,8 +2078,8 @@ homedir-polyfill@^1.0.1: parse-passwd "^1.0.0" hosted-git-info@^2.1.4: - version "2.5.0" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" + version "2.6.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.6.0.tgz#23235b29ab230c576aab0d4f13fc046b0b038222" http-signature@~1.1.0: version "1.1.1" @@ -2065,7 +2126,7 @@ inherits@1: version "1.0.2" resolved "https://registry.yarnpkg.com/inherits/-/inherits-1.0.2.tgz#ca4309dadee6b54cc0b8d247e8d7c7a0975bdc9b" -inherits@2, inherits@^2.0.1, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" @@ -2078,8 +2139,8 @@ interpret@^1.0.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" inversify@^4.5.2: - version "4.9.0" - resolved "https://registry.yarnpkg.com/inversify/-/inversify-4.9.0.tgz#caa01f5856cfa0499aaaed9b1a635ef0c4a9f4d3" + version "4.11.1" + resolved "https://registry.yarnpkg.com/inversify/-/inversify-4.11.1.tgz#9a10635d1fd347da11da96475b3608babd5945a6" is-absolute@^1.0.0: version "1.0.0" @@ -2121,8 +2182,8 @@ is-builtin-module@^1.0.0: builtin-modules "^1.0.0" is-ci@^1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.0.10.tgz#f739336b2632365061a9d48270cd56ae3369318e" + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.1.0.tgz#247e4162e7860cebbdaf30b774d6b0ac7dcfe7a5" dependencies: ci-info "^1.0.0" @@ -2146,7 +2207,7 @@ is-descriptor@^0.1.0: is-data-descriptor "^0.1.4" kind-of "^5.0.0" -is-descriptor@^1.0.0: +is-descriptor@^1.0.0, is-descriptor@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" dependencies: @@ -2178,7 +2239,7 @@ is-extglob@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" -is-extglob@^2.1.0: +is-extglob@^2.1.0, is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -2206,15 +2267,30 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" +is-glob@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" + dependencies: + is-extglob "^2.1.1" + +is-my-ip-valid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824" + is-my-json-valid@^2.12.4: - version "2.17.1" - resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.17.1.tgz#3da98914a70a22f0a8563ef1511a246c6fc55471" + version "2.17.2" + resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz#6b2103a288e94ef3de5cf15d29dd85fc4b78d65c" dependencies: generate-function "^2.0.0" generate-object-property "^1.1.0" + is-my-ip-valid "^1.0.0" jsonpointer "^4.0.0" xtend "^4.0.0" +is-negated-glob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" + is-number@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" @@ -2227,15 +2303,19 @@ is-number@^3.0.0: dependencies: kind-of "^3.0.2" +is-number@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" + is-obj@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" -is-odd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-1.0.0.tgz#3b8a932eb028b3775c39bb09e91767accdb69088" +is-odd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-2.0.0.tgz#7646624671fd7ea558ccd9a2795182f2958f1b24" dependencies: - is-number "^3.0.0" + is-number "^4.0.0" is-path-cwd@^1.0.0: version "1.0.0" @@ -2299,7 +2379,7 @@ is-unc-path@^1.0.0: dependencies: unc-path-regex "^0.1.2" -is-utf8@^0.2.0: +is-utf8@^0.2.0, is-utf8@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" @@ -2307,9 +2387,13 @@ is-valid-glob@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-0.3.0.tgz#d4b55c69f51886f9b65c70d6c2622d37e29f48fe" -is-windows@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.1.tgz#310db70f742d259a16a369202b51af84233310d9" +is-valid-glob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-1.0.0.tgz#29bf3eff701be2d4d315dbacc39bc39fe8f601aa" + +is-windows@^1.0.1, is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" is-wsl@^1.1.0: version "1.1.0" @@ -2364,18 +2448,12 @@ istanbul@0.4.5, istanbul@^0.4.5: which "^1.1.1" wordwrap "^1.0.0" -jade@0.26.3: - version "0.26.3" - resolved "https://registry.yarnpkg.com/jade/-/jade-0.26.3.tgz#8f10d7977d8d79f2f6ff862a81b0513ccb25686c" - dependencies: - commander "0.6.1" - mkdirp "0.3.0" - -js-beautify@~1.5.4: - version "1.5.10" - resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.5.10.tgz#4d95371702699344a516ca26bf59f0a27bb75719" +js-beautify@^1.7.5: + version "1.7.5" + resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.7.5.tgz#69d9651ef60dbb649f65527b53674950138a7919" dependencies: config-chain "~1.1.5" + editorconfig "^0.13.2" mkdirp "~0.5.0" nopt "~3.0.1" @@ -2383,9 +2461,9 @@ js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" -js-yaml@3.x: - version "3.10.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc" +js-yaml@3.x, js-yaml@^3.7.0: + version "3.11.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef" dependencies: argparse "^1.0.7" esprima "^4.0.0" @@ -2449,6 +2527,10 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +just-extend@^1.1.27: + version "1.1.27" + resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-1.1.27.tgz#ec6e79410ff914e472652abfa0e603c03d60e905" + kind-of@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-1.1.0.tgz#140a3d2d41a36d2efcfa9377b62c24f8495a5c44" @@ -2465,7 +2547,7 @@ kind-of@^4.0.0: dependencies: is-buffer "^1.1.5" -kind-of@^5.0.0, kind-of@^5.0.2: +kind-of@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" @@ -2477,18 +2559,18 @@ lazy-cache@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" -lazy-cache@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-2.0.2.tgz#b9190a4f913354694840859f8a8f7084d8822264" - dependencies: - set-getter "^0.1.0" - lazystream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" dependencies: readable-stream "^2.0.5" +lead@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lead/-/lead-1.0.0.tgz#6f14f99a37be3a9dd784f5495690e5903466ee42" + dependencies: + flush-write-stream "^1.0.2" + levn@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" @@ -2665,6 +2747,10 @@ lodash.find@^3.2.1: lodash.isarray "^3.0.0" lodash.keys "^3.0.0" +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + lodash.isarguments@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -2760,16 +2846,16 @@ lodash.values@~2.4.1: lodash.keys "~2.4.1" lodash@^4.17.4: - version "4.17.4" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" + version "4.17.5" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" lodash@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/lodash/-/lodash-1.0.2.tgz#8f57560c83b59fc270bd3d561b690043430e2551" -lolex@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.6.0.tgz#3a9a0283452a47d7439e72731b9e07d7386e49f6" +lolex@^2.2.0, lolex@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.3.2.tgz#85f9450425103bf9e7a60668ea25dc43274ca807" longest@^1.0.1: version "1.0.1" @@ -2786,9 +2872,15 @@ lru-cache@2: version "2.7.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" +lru-cache@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-3.2.0.tgz#71789b3b7f5399bec8565dda38aa30d2a097efee" + dependencies: + pseudomap "^1.0.1" + lru-cache@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" + version "4.1.2" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.2.tgz#45234b2e6e2f2b33da125624c4664929a0224c3f" dependencies: pseudomap "^1.0.2" yallist "^2.1.2" @@ -2890,40 +2982,33 @@ micromatch@^2.1.5, micromatch@^2.3.7: parse-glob "^3.0.4" regex-cache "^0.4.2" -micromatch@^3.0.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.4.tgz#bb812e741a41f982c854e42b421a7eac458796f4" +micromatch@^3.0.4, micromatch@^3.1.4: + version "3.1.9" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.9.tgz#15dc93175ae39e52e93087847096effc73efcf89" dependencies: arr-diff "^4.0.0" array-unique "^0.3.2" - braces "^2.3.0" - define-property "^1.0.0" - extend-shallow "^2.0.1" - extglob "^2.0.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" fragment-cache "^0.2.1" - kind-of "^6.0.0" - nanomatch "^1.2.5" + kind-of "^6.0.2" + nanomatch "^1.2.9" object.pick "^1.3.0" regex-not "^1.0.0" snapdragon "^0.8.1" to-regex "^3.0.1" -mime-db@~1.30.0: - version "1.30.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" +mime-db@~1.33.0: + version "1.33.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.7: - version "2.1.17" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" + version "2.1.18" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" dependencies: - mime-db "~1.30.0" - -minimatch@0.3: - version "0.3.0" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.3.0.tgz#275d8edaac4f1bb3326472089e7949c8394699dd" - dependencies: - lru-cache "2" - sigmund "~1.0.0" + mime-db "~1.33.0" "minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: version "3.0.4" @@ -2961,37 +3046,18 @@ minimist@~0.0.1: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" mixin-deep@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.0.tgz#47a8732ba97799457c8c1eca28f95132d7e8150a" + version "1.3.1" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" dependencies: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e" - mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: minimist "0.0.8" -mocha@^2.3.3: - version "2.5.3" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-2.5.3.tgz#161be5bdeb496771eb9b35745050b622b5aefc58" - dependencies: - commander "2.3.0" - debug "2.2.0" - diff "1.4.0" - escape-string-regexp "1.0.2" - glob "3.2.11" - growl "1.9.2" - jade "0.26.3" - mkdirp "0.5.1" - supports-color "1.2.0" - to-iso-string "0.0.2" - mocha@^4.0.1: version "4.1.0" resolved "https://registry.yarnpkg.com/mocha/-/mocha-4.1.0.tgz#7d86cfbcf35cb829e2754c32e17355ec05338794" @@ -3007,9 +3073,20 @@ mocha@^4.0.1: mkdirp "0.5.1" supports-color "4.4.0" -ms@0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" +mocha@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.0.4.tgz#6b7aa328472da1088e69d47e75925fd3a3bb63c6" + dependencies: + browser-stdout "1.3.1" + commander "2.11.0" + debug "3.1.0" + diff "3.5.0" + escape-string-regexp "1.0.5" + glob "7.1.2" + growl "1.10.3" + he "1.1.1" + mkdirp "0.5.1" + supports-color "4.4.0" ms@2.0.0: version "2.0.0" @@ -3035,29 +3112,26 @@ named-js-regexp@^1.3.1: resolved "https://registry.yarnpkg.com/named-js-regexp/-/named-js-regexp-1.3.3.tgz#a2eb1655c74cb82213a4fc82777dfb67b895d8c8" nan@^2.3.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a" + version "2.9.2" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.9.2.tgz#f564d75f5f8f36a6d9456cca7a6c4fe488ab7866" -nanomatch@^1.2.5: - version "1.2.6" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.6.tgz#f27233e97c34a8706b7e781a4bc611c957a81625" +nanomatch@^1.2.9: + version "1.2.9" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.9.tgz#879f7150cb2dab7a471259066c104eee6e0fa7c2" dependencies: arr-diff "^4.0.0" array-unique "^0.3.2" - define-property "^1.0.0" - extend-shallow "^2.0.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" fragment-cache "^0.2.1" - is-odd "^1.0.0" - kind-of "^5.0.2" + is-odd "^2.0.0" + is-windows "^1.0.2" + kind-of "^6.0.2" object.pick "^1.3.0" regex-not "^1.0.0" snapdragon "^0.8.1" to-regex "^3.0.1" -native-promise-only@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/native-promise-only/-/native-promise-only-0.8.1.tgz#20a318c30cb45f71fe7adfbf7b21c99c1472ef11" - natives@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.1.tgz#011acce1f7cbd87f7ba6b3093d6cd9392be1c574" @@ -3066,6 +3140,16 @@ next-tick@1: version "1.0.0" resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" +nise@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/nise/-/nise-1.3.0.tgz#7d6d506e64a0e37959495157f30a799c0436df72" + dependencies: + "@sinonjs/formatio" "^2.0.0" + just-extend "^1.1.27" + lolex "^2.3.2" + path-to-regexp "^1.7.0" + text-encoding "^0.6.4" + node-pre-gyp@^0.6.39: version "0.6.39" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649" @@ -3120,6 +3204,12 @@ normalize-path@^2.0.0, normalize-path@^2.0.1, normalize-path@^2.1.1: dependencies: remove-trailing-separator "^1.0.1" +now-and-later@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.0.tgz#bc61cbb456d79cb32207ce47ca05136ff2e7d6ee" + dependencies: + once "^1.3.2" + npmlog@^4.0.2: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" @@ -3153,12 +3243,25 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" +object-keys@^1.0.11, object-keys@^1.0.8: + version "1.0.11" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" + object-visit@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" dependencies: isobject "^3.0.0" +object.assign@^4.0.4: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + object.defaults@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf" @@ -3188,7 +3291,7 @@ object.pick@^1.2.0, object.pick@^1.3.0: dependencies: isobject "^3.0.1" -once@1.x, once@^1.3.0, once@^1.3.3, once@^1.4.0: +once@1.x, once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.3.3, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" dependencies: @@ -3201,8 +3304,8 @@ once@~1.3.0: wrappy "1" opn@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/opn/-/opn-5.2.0.tgz#71fdf934d6827d676cecbea1531f95d354641225" + version "5.3.0" + resolved "https://registry.yarnpkg.com/opn/-/opn-5.3.0.tgz#64871565c863875f052cfdf53d3e3cb5adb53b1c" dependencies: is-wsl "^1.1.0" @@ -3243,6 +3346,12 @@ ordered-read-streams@^0.3.0: is-stream "^1.0.1" readable-stream "^2.0.1" +ordered-read-streams@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz#77c0cb37c41525d64166d990ffad7ec6a0e1363e" + dependencies: + readable-stream "^2.0.1" + os-homedir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" @@ -3252,8 +3361,8 @@ os-tmpdir@^1.0.0, os-tmpdir@~1.0.1: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" osenv@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644" + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" dependencies: os-homedir "^1.0.0" os-tmpdir "^1.0.0" @@ -3393,6 +3502,15 @@ plugin-error@^0.1.2: arr-union "^2.0.1" extend-shallow "^1.1.2" +plugin-error@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-1.0.1.tgz#77016bd8919d0ac377fdcdd0322328953ca5781c" + dependencies: + ansi-colors "^1.0.1" + arr-diff "^4.0.0" + arr-union "^3.1.0" + extend-shallow "^3.0.2" + posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" @@ -3413,7 +3531,11 @@ pretty-hrtime@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" -process-nextick-args@^1.0.6, process-nextick-args@~1.0.6: +process-nextick-args@^2.0.0, process-nextick-args@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" + +process-nextick-args@~1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" @@ -3421,10 +3543,25 @@ proto-list@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" -pseudomap@^1.0.2: +pseudomap@^1.0.1, pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" +pump@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pumpify@^1.3.5: + version "1.4.0" + resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.4.0.tgz#80b7c5df7e24153d03f0e7ac8a05a5d068bd07fb" + dependencies: + duplexify "^3.5.3" + inherits "^2.0.3" + pump "^2.0.0" + punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" @@ -3465,8 +3602,8 @@ randomatic@^1.1.3: kind-of "^4.0.0" rc@^1.1.7: - version "1.2.2" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.2.tgz#d8ce9cb57e8d64d9c7badd9876c7c34cbe3c7077" + version "1.2.6" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.6.tgz#eb18989c6d4f4f162c399f79ddd29f3835568092" dependencies: deep-extend "~0.4.0" ini "~1.3.0" @@ -3497,14 +3634,14 @@ read-pkg@^1.0.0: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" +readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5: + version "2.3.5" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.5.tgz#b4f85003a938cbb6ecbce2a124fb1012bd1a838d" dependencies: core-util-is "~1.0.0" inherits "~2.0.3" isarray "~1.0.0" - process-nextick-args "~1.0.6" + process-nextick-args "~2.0.0" safe-buffer "~5.1.1" string_decoder "~1.0.3" util-deprecate "~1.0.1" @@ -3561,11 +3698,12 @@ regex-cache@^0.4.2: dependencies: is-equal-shallow "^0.1.3" -regex-not@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.0.tgz#42f83e39771622df826b02af176525d6a5f157f9" +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" dependencies: - extend-shallow "^2.0.1" + extend-shallow "^3.0.2" + safe-regex "^1.1.0" relative@^3.0.2: version "3.0.2" @@ -3573,17 +3711,32 @@ relative@^3.0.2: dependencies: isobject "^2.0.0" -remap-istanbul@^0.9.5: - version "0.9.5" - resolved "https://registry.yarnpkg.com/remap-istanbul/-/remap-istanbul-0.9.5.tgz#a18617b1f31eec5a7dbee77538298b775606aaa8" +remap-istanbul@^0.10.1: + version "0.10.1" + resolved "https://registry.yarnpkg.com/remap-istanbul/-/remap-istanbul-0.10.1.tgz#3aa58dd5021d499f336d3ba5bf3bbb91c1b88e37" dependencies: amdefine "^1.0.0" - gulp-util "3.0.7" istanbul "0.4.5" minimatch "^3.0.3" - source-map ">=0.5.6" + plugin-error "^0.1.2" + source-map "^0.6.1" through2 "2.0.1" +remove-bom-buffer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz#c2bf1e377520d324f623892e33c10cac2c252b53" + dependencies: + is-buffer "^1.1.5" + is-utf8 "^0.2.1" + +remove-bom-stream@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz#05f1a593f16e42e1fb90ebf59de8e569525f9523" + dependencies: + remove-bom-buffer "^3.0.0" + safe-buffer "^5.1.0" + through2 "^2.0.3" + remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" @@ -3596,12 +3749,6 @@ repeat-string@^1.5.2, repeat-string@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" -repeating@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/repeating/-/repeating-1.1.3.tgz#3d4114218877537494f97f77f9785fab810fa4ac" - dependencies: - is-finite "^1.0.0" - repeating@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" @@ -3616,7 +3763,7 @@ replace-ext@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" -request@2.81.0, request@~2.81.0: +request@2.81.0: version "2.81.0" resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" dependencies: @@ -3644,8 +3791,8 @@ request@2.81.0, request@~2.81.0: uuid "^3.0.0" request@^2.83.0: - version "2.83.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" + version "2.85.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.85.0.tgz#5a03615a47c61420b3eb99b7dba204f83603e1fa" dependencies: aws-sign2 "~0.7.0" aws4 "^1.6.0" @@ -3695,6 +3842,33 @@ request@~2.79.0: tunnel-agent "~0.4.1" uuid "^3.0.0" +request@~2.83.0: + version "2.83.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.6.0" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.1" + forever-agent "~0.6.1" + form-data "~2.3.1" + har-validator "~5.0.3" + hawk "~6.0.2" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.17" + oauth-sign "~0.8.2" + performance-now "^2.1.0" + qs "~6.5.1" + safe-buffer "^5.1.1" + stringstream "~0.0.5" + tough-cookie "~2.3.3" + tunnel-agent "^0.6.0" + uuid "^3.1.0" + requires-port@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" @@ -3706,6 +3880,12 @@ resolve-dir@^1.0.0, resolve-dir@^1.0.1: expand-tilde "^2.0.0" global-modules "^1.0.0" +resolve-options@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/resolve-options/-/resolve-options-1.1.0.tgz#32bb9e39c06d67338dc9378c0d6d6074566ad131" + dependencies: + value-or-function "^3.0.0" + resolve-url@^0.2.1, resolve-url@~0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -3720,7 +3900,11 @@ resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.2: dependencies: path-parse "^1.0.5" -retyped-diff-match-patch-tsd-ambient@^1.0.0-0: +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + +retyped-diff-match-patch-tsd-ambient@^1.0.0-1: version "1.0.0-1" resolved "https://registry.yarnpkg.com/retyped-diff-match-patch-tsd-ambient/-/retyped-diff-match-patch-tsd-ambient-1.0.0-1.tgz#26482bf4915c7ed9f8300bb5cbec48fd4ff5bc62" @@ -3737,30 +3921,36 @@ rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.1: glob "^7.0.5" rxjs@^5.5.2: - version "5.5.6" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.6.tgz#e31fb96d6fd2ff1fd84bcea8ae9c02d007179c02" + version "5.5.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.7.tgz#afb3d1642b069b2fbf203903d6501d1acb4cda27" dependencies: symbol-observable "1.0.1" -safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" -samsam@1.x, samsam@^1.1.3: +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + dependencies: + ret "~0.1.10" + +samsam@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50" -sax@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.2.tgz#735ffaa39a1cff8ffb9598f0223abdb03a9fb2ea" +sax@0.5.x: + version "0.5.8" + resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1" sax@>=0.6.0: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1: - version "5.4.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" +"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0, semver@^5.4.1: + version "5.5.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" semver@^4.1.0: version "4.3.6" @@ -3774,12 +3964,6 @@ set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" -set-getter@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/set-getter/-/set-getter-0.1.0.tgz#d769c182c9d5a51f409145f2fba82e5e86e80376" - dependencies: - to-object-path "^0.3.0" - set-immediate-shim@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" @@ -3814,18 +3998,17 @@ signal-exit@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" -sinon@^2.3.6: - version "2.4.1" - resolved "https://registry.yarnpkg.com/sinon/-/sinon-2.4.1.tgz#021fd64b54cb77d9d2fb0d43cdedfae7629c3a36" +sinon@^4.4.5: + version "4.4.5" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-4.4.5.tgz#b625f992f0f0998d068a270c34e8f50ddcfd846b" dependencies: + "@sinonjs/formatio" "^2.0.0" diff "^3.1.0" - formatio "1.2.0" - lolex "^1.6.0" - native-promise-only "^0.8.1" - path-to-regexp "^1.7.0" - samsam "^1.1.3" - text-encoding "0.6.4" - type-detect "^4.0.0" + lodash.get "^4.4.2" + lolex "^2.2.0" + nise "^1.2.0" + supports-color "^5.1.0" + type-detect "^4.0.5" slash@^1.0.0: version "1.0.0" @@ -3846,8 +4029,8 @@ snapdragon-util@^3.0.1: kind-of "^3.2.0" snapdragon@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.1.tgz#e12b5487faded3e3dea0ac91e9400bf75b401370" + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" dependencies: base "^0.11.1" debug "^2.2.0" @@ -3856,7 +4039,7 @@ snapdragon@^0.8.1: map-cache "^0.2.2" source-map "^0.5.6" source-map-resolve "^0.5.0" - use "^2.0.0" + use "^3.1.0" sntp@1.x.x: version "1.0.9" @@ -3890,8 +4073,8 @@ source-map-resolve@^0.5.0: urix "^0.1.0" source-map-support@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.0.tgz#2018a7ad2bdf8faf2691e5fddab26bed5a2bacab" + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.3.tgz#2b3d5fff298cfa4d1afd7d4352d569e9a0158e76" dependencies: source-map "^0.6.0" @@ -3903,10 +4086,6 @@ source-map-url@~0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.3.0.tgz#7ecaf13b57bcd09da8a40c5d269db33799d4aaf9" -source-map@>=0.5.6, source-map@^0.6.0, source-map@~0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - source-map@^0.1.38: version "0.1.43" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346" @@ -3919,10 +4098,14 @@ source-map@^0.4.4: dependencies: amdefine ">=0.0.4" -source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.3: +source-map@^0.5.6, source-map@~0.5.1: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + source-map@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d" @@ -3933,19 +4116,27 @@ sparkles@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3" -spdx-correct@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" +spdx-correct@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.0.tgz#05a5b4d7153a195bc92c3c425b69f3b2a9524c82" dependencies: - spdx-license-ids "^1.0.2" + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" -spdx-expression-parse@~1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" +spdx-exceptions@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz#2c7ae61056c714a5b9b9b2b2af7d311ef5c78fe9" -spdx-license-ids@^1.0.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" +spdx-expression-parse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz#7a7cd28470cc6d3a1cfe6d66886f6bc430d3ac87" split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" @@ -3964,8 +4155,8 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" sshpk@^1.7.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" + version "1.14.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.1.tgz#130f5975eddad963f1d56f92b9ac6c51fa9f83eb" dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" @@ -3995,8 +4186,8 @@ stream-combiner@~0.0.4: duplexer "~0.1.1" stream-consume@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/stream-consume/-/stream-consume-0.1.0.tgz#a41ead1a6d6081ceb79f65b061901b6d8f3d1d0f" + version "0.1.1" + resolved "https://registry.yarnpkg.com/stream-consume/-/stream-consume-0.1.1.tgz#d3bdb598c2bd0ae82b8cac7ac50b1107a7996c48" stream-shift@^1.0.0: version "1.0.0" @@ -4095,10 +4286,6 @@ sudo-prompt@^8.0.0: version "8.1.0" resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-8.1.0.tgz#62dce8013b80dd242e5b6ca15d8b8cffb7c85472" -supports-color@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-1.2.0.tgz#ff1ed1e61169d06b3cf2d588e188b18d8847e17e" - supports-color@4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e" @@ -4119,11 +4306,11 @@ supports-color@^3.1.0: dependencies: has-flag "^1.0.0" -supports-color@^4.0.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b" +supports-color@^5.1.0, supports-color@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.3.0.tgz#5b24ac15db80fa927cf5227a4a33fd3c4c7676c0" dependencies: - has-flag "^2.0.0" + has-flag "^3.0.0" symbol-observable@1.0.1: version "1.0.1" @@ -4150,7 +4337,7 @@ tar@^2.2.1: fstream "^1.0.2" inherits "2" -text-encoding@0.6.4: +text-encoding@^0.6.4: version "0.6.4" resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19" @@ -4168,14 +4355,14 @@ through2@2.0.1: readable-stream "~2.0.0" xtend "~4.0.0" -through2@2.X, through2@^2.0.0, through2@^2.0.1, through2@^2.0.3, through2@~2.0.0, through2@~2.0.1, through2@~2.0.3: +through2@2.X, through2@^2.0.0, through2@^2.0.3, through2@~2.0.0, through2@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" dependencies: readable-stream "^2.1.5" xtend "~4.0.1" -through2@^0.5.0, through2@~0.5.0: +through2@^0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/through2/-/through2-0.5.1.tgz#dfdd012eb9c700e2323fd334f38ac622ab372da7" dependencies: @@ -4204,8 +4391,8 @@ time-stamp@^1.0.0: resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" timers-ext@^0.1.2: - version "0.1.4" - resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.4.tgz#365781e62b458722be079379fdbfe610a5efa4ba" + version "0.1.5" + resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.5.tgz#77147dd4e76b660c2abb8785db96574cbbd12922" dependencies: es5-ext "~0.10.14" next-tick "1" @@ -4222,9 +4409,12 @@ to-absolute-glob@^0.1.1: dependencies: extend-shallow "^2.0.1" -to-iso-string@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/to-iso-string/-/to-iso-string-0.0.2.tgz#4dc19e664dfccbe25bd8db508b00c6da158255d1" +to-absolute-glob@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1865f43d9e74b0822db9f145b78cff7d0f7c849b" + dependencies: + is-absolute "^1.0.0" + is-negated-glob "^1.0.0" to-object-path@^0.3.0: version "0.3.0" @@ -4240,16 +4430,23 @@ to-regex-range@^2.1.0: repeat-string "^1.6.1" to-regex@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.1.tgz#15358bee4a2c83bd76377ba1dc049d0f18837aae" + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" dependencies: - define-property "^0.2.5" - extend-shallow "^2.0.1" - regex-not "^1.0.0" + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +to-through@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-through/-/to-through-2.0.0.tgz#fc92adaba072647bc0b67d6b03664aa195093af6" + dependencies: + through2 "^2.0.3" tough-cookie@~2.3.0, tough-cookie@~2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561" + version "2.3.4" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" dependencies: punycode "^1.4.1" @@ -4261,47 +4458,50 @@ trim-newlines@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" -tslib@^1.0.0, tslib@^1.7.1, tslib@^1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.8.1.tgz#6946af2d1d651a7b1863b531d6e5afa41aa44eac" +tslib@1.9.0, tslib@^1.7.1, tslib@^1.8.0, tslib@^1.8.1: + version "1.9.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8" -tslint-eslint-rules@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/tslint-eslint-rules/-/tslint-eslint-rules-4.1.1.tgz#7c30e7882f26bc276bff91d2384975c69daf88ba" +tslint-eslint-rules@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/tslint-eslint-rules/-/tslint-eslint-rules-5.1.0.tgz#3232b318da55dbb5a83e3f5d657c1ddbb27b9ff2" dependencies: - doctrine "^0.7.2" - tslib "^1.0.0" - tsutils "^1.4.0" + doctrine "0.7.2" + tslib "1.9.0" + tsutils "2.8.0" -tslint-microsoft-contrib@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/tslint-microsoft-contrib/-/tslint-microsoft-contrib-5.0.1.tgz#328ee9c28d07cdf793293204c96e2ffab9221994" +tslint-microsoft-contrib@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/tslint-microsoft-contrib/-/tslint-microsoft-contrib-5.0.3.tgz#6fc3e238179cd72045c2b422e4d655f4183a8d5c" dependencies: - tsutils "^1.4.0" + tsutils "^2.12.1" -tslint@^5.7.0: - version "5.8.0" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.8.0.tgz#1f49ad5b2e77c76c3af4ddcae552ae4e3612eb13" +tslint@^5.9.1: + version "5.9.1" + resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.9.1.tgz#1255f87a3ff57eb0b0e1f0e610a8b4748046c9ae" dependencies: babel-code-frame "^6.22.0" builtin-modules "^1.1.1" - chalk "^2.1.0" - commander "^2.9.0" + chalk "^2.3.0" + commander "^2.12.1" diff "^3.2.0" glob "^7.1.1" + js-yaml "^3.7.0" minimatch "^3.0.4" resolve "^1.3.2" semver "^5.3.0" - tslib "^1.7.1" + tslib "^1.8.0" tsutils "^2.12.1" -tsutils@^1.4.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-1.9.1.tgz#b9f9ab44e55af9681831d5f28d0aeeaf5c750cb0" +tsutils@2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.8.0.tgz#0160173729b3bf138628dd14a1537e00851d814a" + dependencies: + tslib "^1.7.1" tsutils@^2.12.1: - version "2.15.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.15.0.tgz#90831e5908cca10b28cdaf83a56dcf8156aed7c6" + version "2.22.2" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.22.2.tgz#0b9f3d87aa3eb95bd32d26ce2b88aa329a657951" dependencies: tslib "^1.8.1" @@ -4325,9 +4525,9 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-detect@^4.0.0: - version "4.0.5" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.5.tgz#d70e5bc81db6de2a381bcaca0c6e0cbdc7635de2" +type-detect@^4.0.0, type-detect@^4.0.5: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" typemoq@^2.1.0: version "2.1.0" @@ -4341,16 +4541,16 @@ typescript-char@^0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/typescript-char/-/typescript-char-0.0.0.tgz#558feda737c765a610b737eefbb1775ee9bc8dab" -typescript-formatter@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/typescript-formatter/-/typescript-formatter-6.1.0.tgz#4425ac2bab8aaea9a04251c078f47ab7a0202f13" +typescript-formatter@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/typescript-formatter/-/typescript-formatter-7.1.0.tgz#dd1b5547de211065221f765263e15f18c84c66b8" dependencies: commandpost "^1.0.0" editorconfig "^0.15.0" -typescript@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" +typescript@^2.7.2: + version "2.7.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.7.2.tgz#2d615a1ef4aee4f574425cdff7026edf81919836" uglify-js@^2.6: version "2.8.29" @@ -4420,6 +4620,10 @@ untildify@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/untildify/-/untildify-3.0.2.tgz#7f1f302055b3fea0f3e81dc78eb36766cb65e3f1" +upath@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.0.4.tgz#ee2321ba0a786c50973db043a50b7bcba822361d" + urix@^0.1.0, urix@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" @@ -4435,13 +4639,11 @@ urlgrey@0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/urlgrey/-/urlgrey-0.4.4.tgz#892fe95960805e85519f1cd4389f2cb4cbb7652f" -use@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/use/-/use-2.0.2.tgz#ae28a0d72f93bf22422a18a2e379993112dec8e8" +use@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.0.tgz#14716bf03fdfefd03040aef58d8b4b85f3a7c544" dependencies: - define-property "^0.2.5" - isobject "^3.0.0" - lazy-cache "^2.0.2" + kind-of "^6.0.2" user-home@^1.1.1: version "1.1.1" @@ -4452,8 +4654,8 @@ util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" uuid@^3.0.0, uuid@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" + version "3.2.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14" v8flags@^2.0.2: version "2.1.1" @@ -4466,15 +4668,19 @@ vali-date@^1.0.0: resolved "https://registry.yarnpkg.com/vali-date/-/vali-date-1.0.0.tgz#1b904a59609fb328ef078138420934f6b86709a6" validate-npm-package-license@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" + version "3.0.3" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz#81643bcbef1bdfecd4623793dc4648948ba98338" dependencies: - spdx-correct "~1.0.0" - spdx-expression-parse "~1.0.0" + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +validator@~9.4.1: + version "9.4.1" + resolved "https://registry.yarnpkg.com/validator/-/validator-9.4.1.tgz#abf466d398b561cd243050112c6ff1de6cc12663" -validator@~3.35.0: - version "3.35.0" - resolved "https://registry.yarnpkg.com/validator/-/validator-3.35.0.tgz#3f07249402c1fc8fc093c32c6e43d72a79cca1dc" +value-or-function@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813" verror@1.10.0: version "1.10.0" @@ -4508,7 +4714,7 @@ vinyl-fs@^0.3.0: through2 "^0.6.1" vinyl "^0.4.0" -vinyl-fs@^2.0.0, vinyl-fs@^2.4.3, vinyl-fs@~2.4.3: +vinyl-fs@^2.0.0, vinyl-fs@^2.4.3: version "2.4.4" resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-2.4.4.tgz#be6ff3270cb55dfd7d3063640de81f25d7532239" dependencies: @@ -4530,6 +4736,28 @@ vinyl-fs@^2.0.0, vinyl-fs@^2.4.3, vinyl-fs@~2.4.3: vali-date "^1.0.0" vinyl "^1.0.0" +vinyl-fs@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.2.tgz#1b86258844383f57581fcaac081fe09ef6d6d752" + dependencies: + fs-mkdirp-stream "^1.0.0" + glob-stream "^6.1.0" + graceful-fs "^4.0.0" + is-valid-glob "^1.0.0" + lazystream "^1.0.0" + lead "^1.0.0" + object.assign "^4.0.4" + pumpify "^1.3.5" + readable-stream "^2.3.3" + remove-bom-buffer "^3.0.0" + remove-bom-stream "^1.2.0" + resolve-options "^1.1.0" + through2 "^2.0.0" + to-through "^2.0.0" + value-or-function "^3.0.0" + vinyl "^2.0.0" + vinyl-sourcemap "^1.1.0" + vinyl-source-stream@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/vinyl-source-stream/-/vinyl-source-stream-1.1.2.tgz#62b53a135610a896e98ca96bee3a87f008a8e780" @@ -4537,6 +4765,18 @@ vinyl-source-stream@^1.1.0: through2 "^2.0.3" vinyl "^0.4.3" +vinyl-sourcemap@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz#92a800593a38703a8cdb11d8b300ad4be63b3e16" + dependencies: + append-buffer "^1.0.2" + convert-source-map "^1.5.0" + graceful-fs "^4.1.6" + normalize-path "^2.1.1" + now-and-later "^2.0.0" + remove-bom-buffer "^3.0.0" + vinyl "^2.0.0" + vinyl@^0.2.1: version "0.2.3" resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.2.3.tgz#bca938209582ec5a49ad538a00fa1f125e513252" @@ -4566,7 +4806,7 @@ vinyl@^1.0.0, vinyl@^1.1.0, vinyl@^1.2.0: clone-stats "^0.0.1" replace-ext "0.0.1" -vinyl@^2.0.2: +vinyl@^2.0.0, vinyl@^2.0.2, vinyl@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.1.0.tgz#021f9c2cf951d6b939943c89eb5ee5add4fd924c" dependencies: @@ -4589,63 +4829,63 @@ vinyl@~2.0.1: remove-trailing-separator "^1.0.1" replace-ext "^1.0.0" -vscode-debugadapter-testsupport@^1.25.0: - version "1.25.0" - resolved "https://registry.yarnpkg.com/vscode-debugadapter-testsupport/-/vscode-debugadapter-testsupport-1.25.0.tgz#47172dd5c7a197e9cb6c4a39acc535e5c93c3ccf" +vscode-debugadapter-testsupport@^1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/vscode-debugadapter-testsupport/-/vscode-debugadapter-testsupport-1.27.0.tgz#bab26880ea2f13efb5a120964c4c48ed75d3d15d" dependencies: - vscode-debugprotocol "1.25.0" + vscode-debugprotocol "1.27.0" vscode-debugadapter@^1.0.1: - version "1.25.0" - resolved "https://registry.yarnpkg.com/vscode-debugadapter/-/vscode-debugadapter-1.25.0.tgz#8b39ab4e0f7432a94ef51e3ec67c0c3e9fc98ab8" + version "1.27.0" + resolved "https://registry.yarnpkg.com/vscode-debugadapter/-/vscode-debugadapter-1.27.0.tgz#0688f7d03d7568efd653003ecdb402b7ba37231e" dependencies: - vscode-debugprotocol "1.25.0" + vscode-debugprotocol "1.27.0" -vscode-debugprotocol@1.25.0, vscode-debugprotocol@^1.0.1: - version "1.25.0" - resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.25.0.tgz#7a7e38df4cad8839e37ebcd06ed903902d97a7e3" +vscode-debugprotocol@1.27.0, vscode-debugprotocol@^1.0.1: + version "1.27.0" + resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.27.0.tgz#735a43a3cc1235fe587c0ef93fe4e328def7b17c" -vscode-extension-telemetry@0.0.14: +vscode-extension-telemetry@^0.0.14: version "0.0.14" resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.14.tgz#17454705b6bb8757351b955d812923f02ee895bf" dependencies: applicationinsights "1.0.1" -vscode-jsonrpc@^3.5.0: +vscode-jsonrpc@3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz#87239d9e166b2d7352245b8a813597804c1d63aa" vscode-languageclient@^3.1.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-3.5.0.tgz#36d02cc186a8365a4467719a290fb200a9ae490a" + version "3.5.1" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-3.5.1.tgz#c78e582459c24e58f88020dfa34065e976186a98" dependencies: - vscode-languageserver-protocol "^3.5.0" + vscode-languageserver-protocol "3.5.1" -vscode-languageserver-protocol@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.0.tgz#067c5cbe27709795398d119692c97ebba1452209" +vscode-languageserver-protocol@3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.1.tgz#5144a3a9eeccbd83fe2745bd4ed75fad6cc45f0d" dependencies: - vscode-jsonrpc "^3.5.0" - vscode-languageserver-types "^3.5.0" + vscode-jsonrpc "3.5.0" + vscode-languageserver-types "3.5.0" -vscode-languageserver-types@^3.5.0: +vscode-languageserver-types@3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz#e48d79962f0b8e02de955e3f524908e2b19c0374" vscode-languageserver@^3.1.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-3.5.0.tgz#d28099bc6ddda8c1dd16b707e454e1b1ddae0dba" + version "3.5.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-3.5.1.tgz#e0044b7df4d2447ce12632dfc98f1ab0afacbdff" dependencies: - vscode-languageserver-protocol "^3.5.0" + vscode-languageserver-protocol "3.5.1" vscode-uri "^1.0.1" vscode-uri@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-1.0.1.tgz#11a86befeac3c4aa3ec08623651a3c81a6d0bbc8" + version "1.0.3" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-1.0.3.tgz#631bdbf716dccab0e65291a8dc25c23232085a52" vscode@^1.1.5: - version "1.1.10" - resolved "https://registry.yarnpkg.com/vscode/-/vscode-1.1.10.tgz#d1cba378ab24f1d3ddf9cd470d242ee1472dd35b" + version "1.1.13" + resolved "https://registry.yarnpkg.com/vscode/-/vscode-1.1.13.tgz#dcea0c5f3ec1ff6eca333216b4b20dd994d18d9a" dependencies: glob "^7.1.2" gulp-chmod "^2.0.0" @@ -4702,11 +4942,11 @@ wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" -xml2js@0.2.7: - version "0.2.7" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.2.7.tgz#1838518bb01741cae0878bab4915e494c32306af" +xml2js@0.2.8: + version "0.2.8" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.2.8.tgz#9b81690931631ff09d1957549faf54f4f980b3c2" dependencies: - sax "0.5.2" + sax "0.5.x" xml2js@^0.4.17: version "0.4.19" @@ -4720,8 +4960,8 @@ xmlbuilder@0.4.3: resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-0.4.3.tgz#c4614ba74e0ad196e609c9272cd9e1ddb28a8a58" xmlbuilder@~9.0.1: - version "9.0.4" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.4.tgz#519cb4ca686d005a8420d3496f3f0caeecca580f" + version "9.0.7" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" "xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1: version "4.0.1" From a568f92e0af8b1fe8244610647b329d8460dc82a Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 15 Mar 2018 12:53:28 -0700 Subject: [PATCH 036/433] Exclude news folder from extension (#1081) * exclude news folder from extension * :memo: change log contents * Fixes #1020 --- .vscodeignore | 1 + news/3 Code Health/1020.md | 1 + 2 files changed, 2 insertions(+) create mode 100644 news/3 Code Health/1020.md diff --git a/.vscodeignore b/.vscodeignore index a9397c1a60b1..690c7dace608 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -35,3 +35,4 @@ coverage/** CODE_OF_CONDUCT.md CODING_STANDARDS.md CONTRIBUTING.md +news/** diff --git a/news/3 Code Health/1020.md b/news/3 Code Health/1020.md new file mode 100644 index 000000000000..ddd9b41bfb7e --- /dev/null +++ b/news/3 Code Health/1020.md @@ -0,0 +1 @@ +Exclude 'news' folder from getting packaged into the extension. From 5ebedcde285365f96cdf3ef1ef9f0f1a725e4b2b Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 15 Mar 2018 12:54:00 -0700 Subject: [PATCH 037/433] Add support to use experimental debugger when debugging python unit tests (#1046) * :sparkles: unit test debugging using experimental debugger * :bug: add injectable attribute * :hammer: separate test launcher for experimental debugger * :hammer: update links * :memo: change log * Fixes #906 --- news/1 Enhancements/906.md | 1 + package.json | 6 + pythonFiles/experimental/ptvsd_launcher.py | 2 +- pythonFiles/experimental/testlauncher.py | 64 ++++++++ src/client/common/application/debugService.ts | 15 ++ src/client/common/application/types.ts | 22 +++ src/client/common/application/workspace.ts | 3 + src/client/common/serviceRegistry.ts | 4 +- src/client/common/types.ts | 1 + src/client/unittests/common/debugLauncher.ts | 67 ++++++-- src/client/unittests/common/types.ts | 5 +- src/client/unittests/nosetest/runner.ts | 8 +- src/client/unittests/pytest/runner.ts | 8 +- src/client/unittests/unittest/runner.ts | 4 +- .../unittests/common/debugLauncher.test.ts | 146 ++++++++++++++++++ src/test/unittests/mocks.ts | 4 +- 16 files changed, 328 insertions(+), 32 deletions(-) create mode 100644 news/1 Enhancements/906.md create mode 100644 pythonFiles/experimental/testlauncher.py create mode 100644 src/client/common/application/debugService.ts create mode 100644 src/test/unittests/common/debugLauncher.test.ts diff --git a/news/1 Enhancements/906.md b/news/1 Enhancements/906.md new file mode 100644 index 000000000000..95569b479d9e --- /dev/null +++ b/news/1 Enhancements/906.md @@ -0,0 +1 @@ +Add support for expermental debugger when debugging Python Unit Tests. diff --git a/package.json b/package.json index 929b93083d90..575315a7aad7 100644 --- a/package.json +++ b/package.json @@ -1397,6 +1397,12 @@ "description": "Pattern used to exclude files and folders from ctags See http://ctags.sourceforge.net/ctags.html.", "scope": "resource" }, + "python.unitTest.useExperimentalDebugger": { + "type": "boolean", + "default": false, + "description": "Use the experimental debugger when debugging unit tests.", + "scope": "resource" + }, "python.unitTest.promptToConfigure": { "type": "boolean", "default": true, diff --git a/pythonFiles/experimental/ptvsd_launcher.py b/pythonFiles/experimental/ptvsd_launcher.py index 98c4c5525bcf..3be94266f010 100644 --- a/pythonFiles/experimental/ptvsd_launcher.py +++ b/pythonFiles/experimental/ptvsd_launcher.py @@ -80,7 +80,7 @@ traceback.print_exc() print(''' Internal error detected. Please copy the above traceback and report at -https://go.microsoft.com/fwlink/?LinkId=293415 +https://github.com/Microsoft/vscode-python/issues/new Press Enter to close. . .''') try: diff --git a/pythonFiles/experimental/testlauncher.py b/pythonFiles/experimental/testlauncher.py new file mode 100644 index 000000000000..e8c860ce133d --- /dev/null +++ b/pythonFiles/experimental/testlauncher.py @@ -0,0 +1,64 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import os +import sys + + +def parse_argv(): + """Parses arguments for use with the test launcher. + Arguments are: + 1. Working directory. + 2. Test runner, `pytest` or `nose` + 3. Rest of the arguments are passed into the test runner. + """ + + return (sys.argv[1], sys.argv[2], sys.argv[3:]) + + +def exclude_current_file_from_debugger(): + # Load the debugger package + try: + import ptvsd + import ptvsd.debugger as vspd + vspd.DONT_DEBUG.append(os.path.normcase(__file__)) + except: + traceback.print_exc() + print(''' +Internal error detected. Please copy the above traceback and report at +https://github.com/Microsoft/vscode-python/issues/new + +Press Enter to close. . .''') + try: + raw_input() + except NameError: + input() + sys.exit(1) + + +def run(cwd, testRunner, args): + """Runs the test + cwd -- the current directory to be set + testRuner -- test runner to be used `pytest` or `nose` + args -- arguments passed into the test runner + """ + + sys.path[0] = os.getcwd() + os.chdir(cwd) + + try: + if testRunner == 'pytest': + import pytest + pytest.main(args) + else: + import nose + nose.run(argv=args) + sys.exit(0) + finally: + pass + + +if __name__ == '__main__': + exclude_current_file_from_debugger() + cwd, testRunner, args = parse_argv() + run(cwd, testRunner, args) diff --git a/src/client/common/application/debugService.ts b/src/client/common/application/debugService.ts new file mode 100644 index 000000000000..13ac170a8499 --- /dev/null +++ b/src/client/common/application/debugService.ts @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { injectable } from 'inversify'; +import { debug, DebugConfiguration, WorkspaceFolder } from 'vscode'; +import { IDebugService } from './types'; + +@injectable() +export class DebugService implements IDebugService { + public startDebugging(folder: WorkspaceFolder | undefined, nameOrConfiguration: string | DebugConfiguration): Thenable { + return debug.startDebugging(folder, nameOrConfiguration); + } +} diff --git a/src/client/common/application/types.ts b/src/client/common/application/types.ts index 1808e7c220aa..9ad34b5d8f4e 100644 --- a/src/client/common/application/types.ts +++ b/src/client/common/application/types.ts @@ -434,6 +434,12 @@ export interface IWorkspaceService { * An event that is emitted when the [configuration](#WorkspaceConfiguration) changed. */ readonly onDidChangeConfiguration: Event; + /** + * Whether a workspace folder exists + * @type {boolean} + * @memberof IWorkspaceService + */ + readonly hasWorkspaceFolders: boolean; /** * Returns the [workspace folder](#WorkspaceFolder) that contains a given uri. @@ -524,3 +530,19 @@ export interface ITerminalManager { */ createTerminal(options: TerminalOptions): Terminal; } + +export const IDebugService = Symbol('IDebugManager'); + +export interface IDebugService { + /** + * Start debugging by using either a named launch or named compound configuration, + * or by directly passing a [DebugConfiguration](#DebugConfiguration). + * The named configurations are looked up in '.vscode/launch.json' found in the given folder. + * Before debugging starts, all unsaved files are saved and the launch configurations are brought up-to-date. + * Folder specific variables used in the configuration (e.g. '${workspaceFolder}') are resolved against the given folder. + * @param folder The [workspace folder](#WorkspaceFolder) for looking up named configurations and resolving variables or `undefined` for a non-folder setup. + * @param nameOrConfiguration Either the name of a debug or compound configuration or a [DebugConfiguration](#DebugConfiguration) object. + * @return A thenable that resolves when debugging could be successfully started. + */ + startDebugging(folder: WorkspaceFolder | undefined, nameOrConfiguration: string | vscode.DebugConfiguration): Thenable; +} diff --git a/src/client/common/application/workspace.ts b/src/client/common/application/workspace.ts index 7475ab77c94f..08bd950fe655 100644 --- a/src/client/common/application/workspace.ts +++ b/src/client/common/application/workspace.ts @@ -20,6 +20,9 @@ export class WorkspaceService implements IWorkspaceService { public get onDidChangeWorkspaceFolders(): vscode.Event { return vscode.workspace.onDidChangeWorkspaceFolders; } + public get hasWorkspaceFolders() { + return Array.isArray(vscode.workspace.workspaceFolders) && vscode.workspace.workspaceFolders.length > 0; + } public getConfiguration(section?: string, resource?: vscode.Uri): vscode.WorkspaceConfiguration { return vscode.workspace.getConfiguration(section, resource); } diff --git a/src/client/common/serviceRegistry.ts b/src/client/common/serviceRegistry.ts index b800774eeed4..e50399e644f6 100644 --- a/src/client/common/serviceRegistry.ts +++ b/src/client/common/serviceRegistry.ts @@ -4,9 +4,10 @@ import { IServiceManager } from '../ioc/types'; import { ApplicationShell } from './application/applicationShell'; import { CommandManager } from './application/commandManager'; +import { DebugService } from './application/debugService'; import { DocumentManager } from './application/documentManager'; import { TerminalManager } from './application/terminalManager'; -import { IApplicationShell, ICommandManager, IDocumentManager, ITerminalManager, IWorkspaceService } from './application/types'; +import { IApplicationShell, ICommandManager, IDebugService, IDocumentManager, ITerminalManager, IWorkspaceService } from './application/types'; import { WorkspaceService } from './application/workspace'; import { ConfigurationService } from './configuration/service'; import { ProductInstaller } from './installer/productInstaller'; @@ -38,6 +39,7 @@ export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IWorkspaceService, WorkspaceService); serviceManager.addSingleton(IDocumentManager, DocumentManager); serviceManager.addSingleton(ITerminalManager, TerminalManager); + serviceManager.addSingleton(IDebugService, DebugService); serviceManager.addSingleton(ITerminalHelper, TerminalHelper); serviceManager.addSingleton(ITerminalActivationCommandProvider, Bash, 'bashCShellFish'); diff --git a/src/client/common/types.ts b/src/client/common/types.ts index a53d15370fc1..687f9898c6b4 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -131,6 +131,7 @@ export interface IUnitTestSettings { readonly unittestEnabled: boolean; unittestArgs: string[]; cwd?: string; + readonly useExperimentalDebugger?: boolean; } export interface IPylintCategorySeverity { readonly convention: DiagnosticSeverity; diff --git a/src/client/unittests/common/debugLauncher.ts b/src/client/unittests/common/debugLauncher.ts index 6b2a513d4502..d27dd8af96d7 100644 --- a/src/client/unittests/common/debugLauncher.ts +++ b/src/client/unittests/common/debugLauncher.ts @@ -1,33 +1,72 @@ -import { injectable } from 'inversify'; -import { debug, Uri, workspace } from 'vscode'; -import { ITestDebugLauncher, launchOptions } from './types'; +import { inject, injectable } from 'inversify'; +import * as path from 'path'; +import { Uri } from 'vscode'; +import { IDebugService, IWorkspaceService } from '../../common/application/types'; +import { EXTENSION_ROOT_DIR } from '../../common/constants'; +import { IConfigurationService } from '../../common/types'; +import { IServiceContainer } from '../../ioc/types'; +import { ITestDebugLauncher, LaunchOptions, TestProvider } from './types'; @injectable() export class DebugLauncher implements ITestDebugLauncher { - public async launchDebugger(options: launchOptions) { + constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { } + public async launchDebugger(options: LaunchOptions) { if (options.token && options.token!.isCancellationRequested) { return; } const cwdUri = options.cwd ? Uri.file(options.cwd) : undefined; - - if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0) { + const workspaceService = this.serviceContainer.get(IWorkspaceService); + if (!workspaceService.hasWorkspaceFolders) { throw new Error('Please open a workspace'); } - let workspaceFolder = workspace.getWorkspaceFolder(cwdUri!); + let workspaceFolder = workspaceService.getWorkspaceFolder(cwdUri!); if (!workspaceFolder) { - workspaceFolder = workspace.workspaceFolders[0]; + workspaceFolder = workspaceService.workspaceFolders![0]; } - const args = options.args.slice(); - const program = args.shift(); - return debug.startDebugging(workspaceFolder, { + + const cwd = cwdUri ? cwdUri.fsPath : workspaceFolder.uri.fsPath; + const configurationService = this.serviceContainer.get(IConfigurationService).getSettings(Uri.file(cwd)); + const useExperimentalDebugger = configurationService.unitTest.useExperimentalDebugger === true; + const debugManager = this.serviceContainer.get(IDebugService); + const debuggerType = useExperimentalDebugger ? 'pythonExperimental' : 'python'; + const debugArgs = this.fixArgs(options.args, options.testProvider, useExperimentalDebugger); + const program = this.getTestLauncherScript(options.testProvider, useExperimentalDebugger); + + return debugManager.startDebugging(workspaceFolder, { name: 'Debug Unit Test', - type: 'python', + type: debuggerType, request: 'launch', program, - cwd: cwdUri ? cwdUri.fsPath : workspaceFolder.uri.fsPath, - args, + cwd, + args: debugArgs, console: 'none', debugOptions: ['RedirectOutput'] }).then(() => void (0)); } + private fixArgs(args: string[], testProvider: TestProvider, useExperimentalDebugger: boolean): string[] { + if (testProvider === 'unittest' && useExperimentalDebugger) { + return args.filter(item => item !== '--debug'); + } else { + return args; + } + } + private getTestLauncherScript(testProvider: TestProvider, useExperimentalDebugger: boolean) { + switch (testProvider) { + case 'unittest': { + return path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'PythonTools', 'visualstudio_py_testlauncher.py'); + } + case 'pytest': + case 'nosetest': { + if (useExperimentalDebugger) { + return path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'experimental', 'testlauncher.py'); + } else { + return path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'PythonTools', 'testlauncher.py'); + } + + } + default: { + throw new Error(`Unknown test provider '${testProvider}'`); + } + } + } } diff --git a/src/client/unittests/common/types.ts b/src/client/unittests/common/types.ts index 67dff3bb1427..2b5bc9af48b6 100644 --- a/src/client/unittests/common/types.ts +++ b/src/client/unittests/common/types.ts @@ -185,9 +185,10 @@ export interface ITestResultsService { updateResults(tests: Tests): void; } -export type launchOptions = { +export type LaunchOptions = { cwd: string; args: string[]; + testProvider: TestProvider; token?: CancellationToken; outChannel?: OutputChannel; }; @@ -195,7 +196,7 @@ export type launchOptions = { export const ITestDebugLauncher = Symbol('ITestDebugLauncher'); export interface ITestDebugLauncher { - launchDebugger(options: launchOptions): Promise; + launchDebugger(options: LaunchOptions): Promise; } export const ITestManagerFactory = Symbol('ITestManagerFactory'); diff --git a/src/client/unittests/nosetest/runner.ts b/src/client/unittests/nosetest/runner.ts index 71b169683a3b..512f18b2a151 100644 --- a/src/client/unittests/nosetest/runner.ts +++ b/src/client/unittests/nosetest/runner.ts @@ -1,9 +1,8 @@ 'use strict'; -import * as path from 'path'; import { createTemporaryFile } from '../../common/helpers'; import { IServiceContainer } from '../../ioc/types'; import { Options, run } from '../common/runner'; -import { ITestDebugLauncher, ITestResultsService, TestRunOptions, Tests } from '../common/types'; +import { ITestDebugLauncher, ITestResultsService, LaunchOptions, TestRunOptions, Tests } from '../common/types'; import { PassCalculationFormulae, updateResultsFromXmlLogFile } from '../common/xUnitParser'; const WITH_XUNIT = '--with-xunit'; @@ -59,10 +58,9 @@ export function runTest(serviceContainer: IServiceContainer, testResultsService: return promiseToGetXmlLogFile.then(() => { if (options.debug === true) { const debugLauncher = serviceContainer.get(ITestDebugLauncher); - const testLauncherFile = path.join(__dirname, '..', '..', '..', '..', 'pythonFiles', 'PythonTools', 'testlauncher.py'); const nosetestlauncherargs = [options.cwd, 'nose']; - const debuggerArgs = [testLauncherFile].concat(nosetestlauncherargs).concat(noseTestArgs.concat(testPaths)); - const launchOptions = { cwd: options.cwd, args: debuggerArgs, token: options.token, outChannel: options.outChannel }; + const debuggerArgs = nosetestlauncherargs.concat(noseTestArgs.concat(testPaths)); + const launchOptions: LaunchOptions = { cwd: options.cwd, args: debuggerArgs, token: options.token, outChannel: options.outChannel, testProvider: 'nosetest' }; // tslint:disable-next-line:prefer-type-cast no-any return debugLauncher.launchDebugger(launchOptions) as Promise; } else { diff --git a/src/client/unittests/pytest/runner.ts b/src/client/unittests/pytest/runner.ts index 1b2178e08f61..fee6b1e86619 100644 --- a/src/client/unittests/pytest/runner.ts +++ b/src/client/unittests/pytest/runner.ts @@ -1,9 +1,8 @@ 'use strict'; -import * as path from 'path'; import { createTemporaryFile } from '../../common/helpers'; import { IServiceContainer } from '../../ioc/types'; import { Options, run } from '../common/runner'; -import { ITestDebugLauncher, ITestResultsService, TestRunOptions, Tests } from '../common/types'; +import { ITestDebugLauncher, ITestResultsService, LaunchOptions, TestRunOptions, Tests } from '../common/types'; import { PassCalculationFormulae, updateResultsFromXmlLogFile } from '../common/xUnitParser'; export function runTest(serviceContainer: IServiceContainer, testResultsService: ITestResultsService, options: TestRunOptions): Promise { @@ -35,10 +34,9 @@ export function runTest(serviceContainer: IServiceContainer, testResultsService: const testArgs = testPaths.concat(args, [`--junitxml=${xmlLogFile}`]); if (options.debug) { const debugLauncher = serviceContainer.get(ITestDebugLauncher); - const testLauncherFile = path.join(__dirname, '..', '..', '..', '..', 'pythonFiles', 'PythonTools', 'testlauncher.py'); const pytestlauncherargs = [options.cwd, 'pytest']; - const debuggerArgs = [testLauncherFile].concat(pytestlauncherargs).concat(testArgs); - const launchOptions = { cwd: options.cwd, args: debuggerArgs, token: options.token, outChannel: options.outChannel }; + const debuggerArgs = pytestlauncherargs.concat(testArgs); + const launchOptions: LaunchOptions = { cwd: options.cwd, args: debuggerArgs, token: options.token, outChannel: options.outChannel, testProvider: 'pytest' }; // tslint:disable-next-line:prefer-type-cast no-any return debugLauncher.launchDebugger(launchOptions) as Promise; } else { diff --git a/src/client/unittests/unittest/runner.ts b/src/client/unittests/unittest/runner.ts index d538d640229e..472ba315137f 100644 --- a/src/client/unittests/unittest/runner.ts +++ b/src/client/unittests/unittest/runner.ts @@ -3,7 +3,7 @@ import * as path from 'path'; import { IServiceContainer } from '../../ioc/types'; import { BaseTestManager } from '../common/managers/baseTestManager'; import { Options, run } from '../common/runner'; -import { ITestDebugLauncher, ITestResultsService, IUnitTestSocketServer, TestRunOptions, Tests, TestStatus, TestsToRun } from '../common/types'; +import { ITestDebugLauncher, ITestResultsService, IUnitTestSocketServer, LaunchOptions, TestRunOptions, Tests, TestStatus, TestsToRun } from '../common/types'; type TestStatusMap = { status: TestStatus; @@ -94,7 +94,7 @@ export async function runTest(serviceContainer: IServiceContainer, testManager: if (options.debug === true) { const debugLauncher = serviceContainer.get(ITestDebugLauncher); testArgs.push(...['--debug']); - const launchOptions = { cwd: options.cwd, args: [testLauncherFile].concat(testArgs), token: options.token, outChannel: options.outChannel}; + const launchOptions: LaunchOptions = { cwd: options.cwd, args: testArgs, token: options.token, outChannel: options.outChannel, testProvider: 'unittest' }; // tslint:disable-next-line:prefer-type-cast no-any return debugLauncher.launchDebugger(launchOptions); } else { diff --git a/src/test/unittests/common/debugLauncher.test.ts b/src/test/unittests/common/debugLauncher.test.ts new file mode 100644 index 000000000000..25e76223049b --- /dev/null +++ b/src/test/unittests/common/debugLauncher.test.ts @@ -0,0 +1,146 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:no-any + +import { expect, use } from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import * as path from 'path'; +import * as TypeMoq from 'typemoq'; +import { CancellationTokenSource, Uri, WorkspaceFolder } from 'vscode'; +import { IDebugService, IWorkspaceService } from '../../../client/common/application/types'; +import { EXTENSION_ROOT_DIR } from '../../../client/common/constants'; +import '../../../client/common/extensions'; +import { IConfigurationService, IPythonSettings, IUnitTestSettings } from '../../../client/common/types'; +import { IServiceContainer } from '../../../client/ioc/types'; +import { DebugLauncher } from '../../../client/unittests/common/debugLauncher'; +import { TestProvider } from '../../../client/unittests/common/types'; + +use(chaiAsPromised); + +// tslint:disable-next-line:max-func-body-length +suite('Unit Tests - Debug Launcher', () => { + let unitTestSettings: TypeMoq.IMock; + let debugLauncher: DebugLauncher; + let debugService: TypeMoq.IMock; + let workspaceService: TypeMoq.IMock; + setup(async () => { + const serviceContainer = TypeMoq.Mock.ofType(); + const configService = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationService))).returns(() => configService.object); + + debugService = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IDebugService))).returns(() => debugService.object); + + workspaceService = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IWorkspaceService))).returns(() => workspaceService.object); + + const settings = TypeMoq.Mock.ofType(); + configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); + + unitTestSettings = TypeMoq.Mock.ofType(); + settings.setup(p => p.unitTest).returns(() => unitTestSettings.object); + + debugLauncher = new DebugLauncher(serviceContainer.object); + }); + function setupDebugManager(workspaceFolder: WorkspaceFolder, name: string, type: string, + request: string, program: string, cwd: string, + args: string[], console, debugOptions: string[], + testProvider: TestProvider, useExperimentalDebugger: boolean) { + + const debugArgs = testProvider === 'unittest' && useExperimentalDebugger ? args.filter(item => item !== '--debug') : args; + + debugService.setup(d => d.startDebugging(TypeMoq.It.isValue(workspaceFolder), + TypeMoq.It.isObjectWith({ name, type, request, program, cwd, args: debugArgs, console, debugOptions }))) + .returns(() => Promise.resolve(undefined as any)) + .verifiable(TypeMoq.Times.once()); + } + function createWorkspaceFolder(folderPath: string): WorkspaceFolder { + return { index: 0, name: path.basename(folderPath), uri: Uri.file(folderPath) }; + } + function getTestLauncherScript(testProvider: TestProvider, useExperimentalDebugger: boolean) { + switch (testProvider) { + case 'unittest': { + return path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'PythonTools', 'visualstudio_py_testlauncher.py'); + } + case 'pytest': + case 'nosetest': { + if (useExperimentalDebugger) { + return path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'experimental', 'testlauncher.py'); + } else { + return path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'PythonTools', 'testlauncher.py'); + } + + } + default: { + throw new Error(`Unknown test provider '${testProvider}'`); + } + } + } + const testProviders: TestProvider[] = ['nosetest', 'pytest', 'unittest']; + testProviders.forEach(testProvider => { + [true, false].forEach(useExperimentalDebugger => { + const testTitleSuffix = `(Test Framework '${testProvider}', and use experimental debugger = '${useExperimentalDebugger}'`; + const testLaunchScript = getTestLauncherScript(testProvider, useExperimentalDebugger); + const debuggerType = useExperimentalDebugger ? 'pythonExperimental' : 'python'; + + test(`Must launch debugger ${testTitleSuffix}`, async () => { + unitTestSettings.setup(u => u.useExperimentalDebugger).returns(() => useExperimentalDebugger); + workspaceService.setup(u => u.hasWorkspaceFolders).returns(() => true); + const workspaceFolders = [createWorkspaceFolder('one/two/three'), createWorkspaceFolder('five/six/seven')]; + workspaceService.setup(u => u.workspaceFolders).returns(() => workspaceFolders); + workspaceService.setup(u => u.getWorkspaceFolder(TypeMoq.It.isAny())).returns(() => workspaceFolders[0]); + + const args = ['/one/two/three/testfile.py']; + const cwd = workspaceFolders[0].uri.fsPath; + const program = testLaunchScript; + setupDebugManager(workspaceFolders[0], 'Debug Unit Test', debuggerType, 'launch', program, cwd, args, 'none', ['RedirectOutput'], testProvider, useExperimentalDebugger); + + debugLauncher.launchDebugger({ cwd, args, testProvider }).ignoreErrors(); + debugService.verifyAll(); + }); + test(`Must launch debugger with arguments ${testTitleSuffix}`, async () => { + unitTestSettings.setup(u => u.useExperimentalDebugger).returns(() => useExperimentalDebugger); + workspaceService.setup(u => u.hasWorkspaceFolders).returns(() => true); + const workspaceFolders = [createWorkspaceFolder('one/two/three'), createWorkspaceFolder('five/six/seven')]; + workspaceService.setup(u => u.workspaceFolders).returns(() => workspaceFolders); + workspaceService.setup(u => u.getWorkspaceFolder(TypeMoq.It.isAny())).returns(() => workspaceFolders[0]); + + const args = ['/one/two/three/testfile.py', '--debug', '1']; + const cwd = workspaceFolders[0].uri.fsPath; + const program = testLaunchScript; + setupDebugManager(workspaceFolders[0], 'Debug Unit Test', debuggerType, 'launch', program, cwd, args, 'none', ['RedirectOutput'], testProvider, useExperimentalDebugger); + + debugLauncher.launchDebugger({ cwd, args, testProvider }).ignoreErrors(); + debugService.verifyAll(); + }); + test(`Must not launch debugger if cancelled ${testTitleSuffix}`, async () => { + unitTestSettings.setup(u => u.useExperimentalDebugger).returns(() => false); + workspaceService.setup(u => u.hasWorkspaceFolders).returns(() => true); + + debugService.setup(d => d.startDebugging(TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns(() => Promise.resolve(undefined as any)) + .verifiable(TypeMoq.Times.never()); + + const cancellationToken = new CancellationTokenSource(); + cancellationToken.cancel(); + const token = cancellationToken.token; + expect(debugLauncher.launchDebugger({ cwd: '', args: [], token, testProvider })).to.be.eventually.equal(undefined, 'not undefined'); + debugService.verifyAll(); + }); + test(`Must throw an exception if there are no workspaces ${testTitleSuffix}`, async () => { + unitTestSettings.setup(u => u.useExperimentalDebugger).returns(() => false); + workspaceService.setup(u => u.hasWorkspaceFolders).returns(() => false); + + debugService.setup(d => d.startDebugging(TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns(() => Promise.resolve(undefined as any)) + .verifiable(TypeMoq.Times.never()); + + expect(debugLauncher.launchDebugger({ cwd: '', args: [], testProvider })).to.eventually.throw('Please open a workspace'); + debugService.verifyAll(); + }); + }); + }); +}); diff --git a/src/test/unittests/mocks.ts b/src/test/unittests/mocks.ts index 3e2cdc91d710..1ff69155137f 100644 --- a/src/test/unittests/mocks.ts +++ b/src/test/unittests/mocks.ts @@ -6,7 +6,7 @@ import { Product } from '../../client/common/types'; import { IServiceContainer } from '../../client/ioc/types'; import { CANCELLATION_REASON } from '../../client/unittests/common/constants'; import { BaseTestManager } from '../../client/unittests/common/managers/baseTestManager'; -import { ITestDebugLauncher, ITestDiscoveryService, IUnitTestSocketServer, launchOptions, TestDiscoveryOptions, TestProvider, Tests, TestsToRun } from '../../client/unittests/common/types'; +import { ITestDebugLauncher, ITestDiscoveryService, IUnitTestSocketServer, LaunchOptions, TestDiscoveryOptions, TestProvider, Tests, TestsToRun } from '../../client/unittests/common/types'; @injectable() export class MockDebugLauncher implements ITestDebugLauncher, Disposable { @@ -32,7 +32,7 @@ export class MockDebugLauncher implements ITestDebugLauncher, Disposable { public async getLaunchOptions(resource?: Uri): Promise<{ port: number, host: string }> { return { port: 0, host: 'localhost' }; } - public async launchDebugger(options: launchOptions): Promise { + public async launchDebugger(options: LaunchOptions): Promise { this._launched.resolve(true); // tslint:disable-next-line:no-non-null-assertion this._token = options.token!; From ebf6d9c9f86d4f2f640d12095c56f0d8702689f1 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 15 Mar 2018 13:01:56 -0700 Subject: [PATCH 038/433] Bundle PTVSD with extension for experimental debugger (#1083) * :sparkles: bund ptvsd with extension * comment out bundling release version of PTVSD * :memo: news entry * Fixes #741 --- .travis.yml | 7 ++++++- news/1 Enhancements/741.md | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 news/1 Enhancements/741.md diff --git a/.travis.yml b/.travis.yml index f81ec0a06242..71afd1800f2e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,7 +34,12 @@ script: - if [ $TRAVIS_UPLOAD_COVERAGE == "true" ]; then bash <(curl -s https://codecov.io/bash); fi - - rm -rf ./pythonFiles/experimental/ptvsd + # - rm -rf ./pythonFiles/experimental/ptvsd + # - pip install -t ./pythonFiles/experimental/ptvsd ptvsd + # - yarn run testDebugger --silent + # - if [ $TRAVIS_UPLOAD_COVERAGE == "true" ]; then + # bash <(curl -s https://codecov.io/bash); + # fi - yarn run clean - yarn run vscode:prepublish - yarn run cover:enable diff --git a/news/1 Enhancements/741.md b/news/1 Enhancements/741.md new file mode 100644 index 000000000000..329ff07728af --- /dev/null +++ b/news/1 Enhancements/741.md @@ -0,0 +1 @@ +Bundle python depedencies (PTVSD package) in the extension for the experimental debugger. From 73566e5554be749bb8444a241cf9a4ae4579c74e Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 15 Mar 2018 18:19:53 -0700 Subject: [PATCH 039/433] Enable code coverage for debug adapters (#1018) * :hammer: add instrumentation * :hammer: track files added and modified * build lcov files for upload * rename report * gulp task for yarn run * :bug: fix report generation * :memo: change log * delete debug coverage * :bug: disable code coverage on AppVeyor * Fixes #778 --- .gitignore | 1 + .travis.yml | 1 + .vscodeignore | 1 + gulpfile.js | 29 ++++++++++++++++++-- news/3 Code Health/778.md | 1 + package.json | 7 +++-- src/client/debugger/Main.ts | 3 ++ src/client/debugger/mainV2.ts | 1 + src/test/debugger/debugClient.ts | 47 ++++++++++++++++++++++++++++++++ src/test/debugger/misc.test.ts | 26 ++++++++++++++++-- yarn.lock | 2 +- 11 files changed, 110 insertions(+), 9 deletions(-) create mode 100644 news/3 Code Health/778.md create mode 100644 src/test/debugger/debugClient.ts diff --git a/.gitignore b/.gitignore index 466b005af61d..052afbc54035 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ coverage/ .vscode-test/** .venv pythonFiles/experimental/ptvsd/** +debug_coverage*/** diff --git a/.travis.yml b/.travis.yml index 71afd1800f2e..8c3fb6e4924f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,6 +31,7 @@ script: - yarn run vscode:prepublish - yarn run cover:enable - yarn run testDebugger --silent + - yarn run debugger-coverage - if [ $TRAVIS_UPLOAD_COVERAGE == "true" ]; then bash <(curl -s https://codecov.io/bash); fi diff --git a/.vscodeignore b/.vscodeignore index 690c7dace608..ae242dcf887d 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -36,3 +36,4 @@ CODE_OF_CONDUCT.md CODING_STANDARDS.md CONTRIBUTING.md news/** +debug_coverage*/** diff --git a/gulpfile.js b/gulpfile.js index 44a7f54f15fa..582908f4468d 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -21,6 +21,9 @@ const jeditor = require("gulp-json-editor"); const del = require('del'); const sourcemaps = require('gulp-sourcemaps'); const fs = require('fs'); +const remapIstanbul = require('remap-istanbul'); +const istanbul = require('istanbul'); +const glob = require('glob'); /** * Hygiene works by creating cascading subsets of all our files and @@ -73,6 +76,8 @@ gulp.task('compile', () => run({ mode: 'compile', skipFormatCheck: true, skipInd gulp.task('watch', ['hygiene-modified', 'hygiene-watch']); +gulp.task('debugger-coverage', () => buildDebugAdapterCoverage()); + gulp.task('hygiene-watch', () => gulp.watch(tsFilter, debounce(() => run({ mode: 'changes' }), 1000))); gulp.task('hygiene-all', () => run({ mode: 'all' })); @@ -81,9 +86,9 @@ gulp.task('hygiene-modified', ['compile'], () => run({ mode: 'changes' })); gulp.task('clean', ['output:clean', 'cover:clean'], () => { }); -gulp.task('output:clean', () => del('coverage')); +gulp.task('output:clean', () => del(['coverage', 'debug_coverage*'])); -gulp.task('cover:clean', () => del('coverage')); +gulp.task('cover:clean', () => del(['coverage', 'debug_coverage*'])); gulp.task('cover:enable', () => { return gulp.src("./coverconfig.json") @@ -103,6 +108,24 @@ gulp.task('cover:disable', () => { .pipe(gulp.dest("./out", { 'overwrite': true })); }); +function buildDebugAdapterCoverage() { + const matches = glob.sync(path.join(__dirname, 'debug_coverage*/coverage.json')); + matches.forEach(coverageFile => { + const finalCoverageFile = path.join(path.dirname(coverageFile), 'coverage-final-upload.json'); + const remappedCollector = remapIstanbul.remap(JSON.parse(fs.readFileSync(coverageFile, 'utf8')), { + warn: warning => { + // We expect some warnings as any JS file without a typescript mapping will cause this. + // By default, we'll skip printing these to the console as it clutters it up. + console.warn(warning); + } + }); + + const reporter = new istanbul.Reporter(undefined, path.dirname(coverageFile)); + reporter.add('lcov'); + reporter.write(remappedCollector, true, () => { }); + }); +} + /** * @typedef {Object} hygieneOptions - creates a new type named 'SpecialType' * @property {'changes'|'staged'|'all'|'compile'} [mode=] - Mode. @@ -382,7 +405,7 @@ function getFilesToProcess(options) { // If we need only modified files, then filter the glob. if (options && options.mode === 'changes') { return gulp.src(all, gulpSrcOptions) - .pipe(gitmodified(['M', 'A', 'D', 'R', 'C', 'U', '??'])); + .pipe(gitmodified(['M', 'A', 'AM', 'D', 'R', 'C', 'U', '??'])); } if (options && options.mode === 'staged') { diff --git a/news/3 Code Health/778.md b/news/3 Code Health/778.md new file mode 100644 index 000000000000..91eb90689389 --- /dev/null +++ b/news/3 Code Health/778.md @@ -0,0 +1 @@ +Generate code coverage for debug adapter unit tests. diff --git a/package.json b/package.json index 575315a7aad7..96fbb30580c6 100644 --- a/package.json +++ b/package.json @@ -1614,7 +1614,8 @@ "lint-staged": "node gulpfile.js", "lint": "tslint src/**/*.ts -t verbose", "clean": "gulp clean", - "cover:enable": "gulp cover:enable" + "cover:enable": "gulp cover:enable", + "debugger-coverage": "gulp debugger-coverage" }, "dependencies": { "arch": "^2.1.0", @@ -1694,7 +1695,7 @@ "mocha": "^5.0.4", "relative": "^3.0.2", "remap-istanbul": "^0.10.1", - "retyped-diff-match-patch-tsd-ambient": "^1.0.0-1", + "retyped-diff-match-patch-tsd-ambient": "^1.0.0-0", "shortid": "^2.2.8", "sinon": "^4.4.5", "tslint": "^5.9.1", @@ -1711,4 +1712,4 @@ "publisherDisplayName": "Microsoft", "publisherId": "998b010b-e2af-44a5-a6cd-0b5fd3b9b6f8" } -} \ No newline at end of file +} diff --git a/src/client/debugger/Main.ts b/src/client/debugger/Main.ts index 08dd71b00754..bd8b44dc7aef 100644 --- a/src/client/debugger/Main.ts +++ b/src/client/debugger/Main.ts @@ -57,6 +57,9 @@ export class PythonDebugger extends LoggingDebugSession { this.debuggerLoaded = new Promise(resolve => { this.debuggerLoadedPromiseResolve = resolve; }); + if (!isServer) { + process.on('SIGINT', this.shutdown); + } } // tslint:disable-next-line:no-unnecessary-override @sendPerformanceTelemetry(PerformanceTelemetryCondition.stoppedEvent) diff --git a/src/client/debugger/mainV2.ts b/src/client/debugger/mainV2.ts index 53f4d60d17cd..d09afe43d239 100644 --- a/src/client/debugger/mainV2.ts +++ b/src/client/debugger/mainV2.ts @@ -225,6 +225,7 @@ class DebugManager implements Disposable { if (!this.isServerMode) { const currentProcess = this.serviceContainer.get(ICurrentProcess); currentProcess.on('SIGTERM', this.shutdown); + currentProcess.on('SIGINT', this.shutdown); } this.interceptProtocolMessages(); this.startDebugSession(); diff --git a/src/test/debugger/debugClient.ts b/src/test/debugger/debugClient.ts new file mode 100644 index 000000000000..9c45e7f45824 --- /dev/null +++ b/src/test/debugger/debugClient.ts @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { ChildProcess, spawn, SpawnOptions } from 'child_process'; +import * as path from 'path'; +import { DebugClient } from 'vscode-debugadapter-testsupport'; +import { EXTENSION_ROOT_DIR } from '../../client/common/constants'; +import { noop } from '../../client/common/core.utils'; + +export class DebugClientEx extends DebugClient { + private adapterProcess: ChildProcess | undefined; + constructor(private executable: string, debugType: string, private coverageDirectory: string, private spawnOptions?: SpawnOptions) { + super('node', '', debugType, spawnOptions); + } + /** + * Starts a new debug adapter and sets up communication via stdin/stdout. + * If a port number is specified the adapter is not launched but a connection to + * a debug adapter running in server mode is established. This is useful for debugging + * the adapter while running tests. For this reason all timeouts are disabled in server mode. + */ + public start(port?: number): Promise { + return new Promise((resolve, reject) => { + const runtime = path.join(EXTENSION_ROOT_DIR, 'node_modules', '.bin', 'istanbul'); + const args = ['cover', '--report=json', '--print=none', `--dir=${this.coverageDirectory}`, '--handle-sigint', this.executable]; + this.adapterProcess = spawn(runtime, args, this.spawnOptions); + this.adapterProcess.stderr.on('data', noop); + this.adapterProcess.on('error', (err) => { + console.error(err); + reject(err); + }); + this.adapterProcess.on('exit', noop); + this.connect(this.adapterProcess.stdout, this.adapterProcess.stdin); + resolve(); + }); + } + public stop(): Promise { + return this.disconnectRequest().then(this.stopAdapterProcess).catch(this.stopAdapterProcess); + } + private stopAdapterProcess = () => { + if (this.adapterProcess) { + this.adapterProcess.kill(); + this.adapterProcess = undefined; + } + } +} diff --git a/src/test/debugger/misc.test.ts b/src/test/debugger/misc.test.ts index c6dfab6068cd..ec38042ab728 100644 --- a/src/test/debugger/misc.test.ts +++ b/src/test/debugger/misc.test.ts @@ -9,12 +9,15 @@ import * as path from 'path'; import { ThreadEvent } from 'vscode-debugadapter'; import { DebugClient } from 'vscode-debugadapter-testsupport'; import { DebugProtocol } from 'vscode-debugprotocol'; +import { EXTENSION_ROOT_DIR } from '../../client/common/constants'; import { noop } from '../../client/common/core.utils'; +import { IS_WINDOWS } from '../../client/common/platform/constants'; import { FileSystem } from '../../client/common/platform/fileSystem'; import { PlatformService } from '../../client/common/platform/platformService'; import { LaunchRequestArguments } from '../../client/debugger/Common/Contracts'; import { sleep } from '../common'; import { IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; +import { DebugClientEx } from './debugClient'; const isProcessRunning = require('is-running') as (number) => boolean; @@ -27,6 +30,7 @@ const MAX_SIGNED_INT32 = Math.pow(2, 31) - 1; const EXPERIMENTAL_DEBUG_ADAPTER = path.join(__dirname, '..', '..', 'client', 'debugger', 'mainV2.js'); const THREAD_TIMEOUT = 10000; +let testCounter = 0; [DEBUG_ADAPTER, EXPERIMENTAL_DEBUG_ADAPTER].forEach(testAdapterFilePath => { const debugAdapterFileName = path.basename(testAdapterFilePath); const debuggerType = debugAdapterFileName === 'Main.js' ? 'python' : 'pythonExperimental'; @@ -38,7 +42,7 @@ const THREAD_TIMEOUT = 10000; this.skip(); } await new Promise(resolve => setTimeout(resolve, 1000)); - debugClient = new DebugClient('node', testAdapterFilePath, debuggerType); + debugClient = createDebugAdapter(); await debugClient.start(); }); teardown(async () => { @@ -50,7 +54,25 @@ const THREAD_TIMEOUT = 10000; } catch (ex) { } await sleep(1000); }); + /** + * Creates the debug adapter. + * We do not need to support code coverage on AppVeyor, lets use the standard test adapter. + * @returns {DebugClient} + */ + function createDebugAdapter(): DebugClient { + if (IS_WINDOWS) { + return new DebugClient('node', testAdapterFilePath, debuggerType); + } else { + const coverageDirectory = path.join(EXTENSION_ROOT_DIR, `debug_coverage${testCounter += 1}`); + return new DebugClientEx(testAdapterFilePath, debuggerType, coverageDirectory, { cwd: EXTENSION_ROOT_DIR }); + } + } function buildLauncArgs(pythonFile: string, stopOnEntry: boolean = false): LaunchRequestArguments { + const env = {}; + if (debuggerType === 'pythonExperimental') { + // tslint:disable-next-line:no-string-literal + env['PYTHONPATH'] = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'experimental', 'ptvsd'); + } const options: LaunchRequestArguments = { program: path.join(debugFilesPath, pythonFile), cwd: debugFilesPath, @@ -58,7 +80,7 @@ const THREAD_TIMEOUT = 10000; debugOptions: ['RedirectOutput'], pythonPath: 'python', args: [], - env: {}, + env, envFile: '', logToFile: false, type: debuggerType diff --git a/yarn.lock b/yarn.lock index ed24c3f29707..19893afaaa94 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1826,7 +1826,7 @@ gulp-untar@^0.0.6: tar "^2.2.1" through2 "~2.0.3" -gulp-util@^3.0.0, gulp-util@^3.0.7, gulp-util@~3.0.8: +gulp-util@^3.0.0, gulp-util@^3.0.7, gulp-util@~3.0.0, gulp-util@~3.0.7, gulp-util@~3.0.8: version "3.0.8" resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f" dependencies: From bac5f4774186b9544fac878668941305f1c676e2 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 16 Mar 2018 09:52:18 -0700 Subject: [PATCH 040/433] Prevent debugger stepping into js code (#1091) Fixes #1090 --- .vscode/launch.json | 9 +++++++-- news/3 Code Health/1090.md | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 news/3 Code Health/1090.md diff --git a/.vscode/launch.json b/.vscode/launch.json index 264d49e34f11..4daf91a9249b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -11,9 +11,10 @@ "--extensionDevelopmentPath=${workspaceFolder}" ], "stopOnEntry": false, + "smartStep": true, "sourceMaps": true, "outFiles": [ - "${workspaceFolder}/out/**/*.js" + "${workspaceFolder}/out/**/*" ], "preLaunchTask": "Compile" }, @@ -23,6 +24,7 @@ "request": "launch", "program": "${workspaceFolder}/out/client/debugger/Main.js", "stopOnEntry": false, + "smartStep": true, "args": [ "--server=4711" ], @@ -39,6 +41,7 @@ "request": "launch", "program": "${workspaceFolder}/out/client/debugger/mainV2.js", "stopOnEntry": false, + "smartStep": true, "args": [ "--server=4711" ], @@ -78,8 +81,9 @@ ], "stopOnEntry": false, "sourceMaps": true, + "smartStep": true, "outFiles": [ - "${workspaceFolder}/out/**/*.js" + "${workspaceFolder}/out/**/*" ], "preLaunchTask": "Compile" }, @@ -94,6 +98,7 @@ "--extensionTestsPath=${workspaceFolder}/out/test" ], "stopOnEntry": false, + "smartStep": true, "sourceMaps": true, "outFiles": [ "${workspaceFolder}/out/**/*.js" diff --git a/news/3 Code Health/1090.md b/news/3 Code Health/1090.md new file mode 100644 index 000000000000..49921ad6f4ca --- /dev/null +++ b/news/3 Code Health/1090.md @@ -0,0 +1 @@ +Prevent debugger stepping into js code, when debugging async TypeScript code. From 349858e04e550542d5fa79a02a2041122a74ff7e Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 16 Mar 2018 15:04:20 -0700 Subject: [PATCH 041/433] Increase timeouts of the debugger unit tests (#1100) * :hammer: increase timeout in debugger tests * :hammer: increase timeouts for all debugger tests * :memo: add news entry * Fixes #1094 --- news/3 Code Health/1094.md | 1 + src/test/debugger/attach.test.ts | 2 ++ src/test/debugger/common/constants.ts | 7 +++++++ src/test/debugger/misc.test.ts | 21 +++++++++++---------- src/test/debugger/portAndHost.test.ts | 2 ++ 5 files changed, 23 insertions(+), 10 deletions(-) create mode 100644 news/3 Code Health/1094.md create mode 100644 src/test/debugger/common/constants.ts diff --git a/news/3 Code Health/1094.md b/news/3 Code Health/1094.md new file mode 100644 index 000000000000..9da7257da17b --- /dev/null +++ b/news/3 Code Health/1094.md @@ -0,0 +1 @@ +Increase timeouts for the debugger unit tests. diff --git a/src/test/debugger/attach.test.ts b/src/test/debugger/attach.test.ts index ba393f911a71..5e346c6cb048 100644 --- a/src/test/debugger/attach.test.ts +++ b/src/test/debugger/attach.test.ts @@ -14,6 +14,7 @@ import { ProcessService } from '../../client/common/process/proc'; import { AttachRequestArguments } from '../../client/debugger/Common/Contracts'; import { sleep } from '../common'; import { initialize, IS_APPVEYOR, IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; +import { DEBUGGER_TIMEOUT } from './common/constants'; const fileToDebug = path.join(__dirname, '..', '..', '..', 'src', 'testMultiRootWkspc', 'workspace5', 'remoteDebugger.py'); const ptvsdPath = path.join(__dirname, '..', '..', '..', 'pythonFiles', 'PythonTools'); @@ -30,6 +31,7 @@ suite('Attach Debugger', () => { } await sleep(1000); debugClient = new DebugClient('node', DEBUG_ADAPTER, 'python'); + debugClient.defaultTimeout = DEBUGGER_TIMEOUT; await debugClient.start(); }); teardown(async () => { diff --git a/src/test/debugger/common/constants.ts b/src/test/debugger/common/constants.ts new file mode 100644 index 000000000000..9be293352e34 --- /dev/null +++ b/src/test/debugger/common/constants.ts @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// Sometimes PTVSD can take a while for thread & other events to be reported. +export const DEBUGGER_TIMEOUT = 10000; diff --git a/src/test/debugger/misc.test.ts b/src/test/debugger/misc.test.ts index ec38042ab728..aa26b79acba4 100644 --- a/src/test/debugger/misc.test.ts +++ b/src/test/debugger/misc.test.ts @@ -17,6 +17,7 @@ import { PlatformService } from '../../client/common/platform/platformService'; import { LaunchRequestArguments } from '../../client/debugger/Common/Contracts'; import { sleep } from '../common'; import { IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; +import { DEBUGGER_TIMEOUT } from './common/constants'; import { DebugClientEx } from './debugClient'; const isProcessRunning = require('is-running') as (number) => boolean; @@ -28,7 +29,6 @@ const debugFilesPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'py const DEBUG_ADAPTER = path.join(__dirname, '..', '..', 'client', 'debugger', 'Main.js'); const MAX_SIGNED_INT32 = Math.pow(2, 31) - 1; const EXPERIMENTAL_DEBUG_ADAPTER = path.join(__dirname, '..', '..', 'client', 'debugger', 'mainV2.js'); -const THREAD_TIMEOUT = 10000; let testCounter = 0; [DEBUG_ADAPTER, EXPERIMENTAL_DEBUG_ADAPTER].forEach(testAdapterFilePath => { @@ -43,6 +43,7 @@ let testCounter = 0; } await new Promise(resolve => setTimeout(resolve, 1000)); debugClient = createDebugAdapter(); + debugClient.defaultTimeout = DEBUGGER_TIMEOUT; await debugClient.start(); }); teardown(async () => { @@ -137,7 +138,7 @@ let testCounter = 0; if (debuggerType !== 'python') { return this.skip(); } - const threadIdPromise = debugClient.waitForEvent('thread', THREAD_TIMEOUT); + const threadIdPromise = debugClient.waitForEvent('thread'); await Promise.all([ debugClient.configurationSequence(), @@ -156,7 +157,7 @@ let testCounter = 0; if (debuggerType !== 'python') { return this.skip(); } - const threadIdPromise = debugClient.waitForEvent('thread', THREAD_TIMEOUT); + const threadIdPromise = debugClient.waitForEvent('thread'); await Promise.all([ debugClient.configurationSequence(), @@ -183,7 +184,7 @@ let testCounter = 0; } const launchArgs = buildLauncArgs('sample2.py', false); const breakpointLocation = { path: path.join(debugFilesPath, 'sample2.py'), column: 1, line: 5 }; - const processPromise = debugClient.waitForEvent('process', THREAD_TIMEOUT) as Promise; + const processPromise = debugClient.waitForEvent('process') as Promise; await debugClient.hitBreakpoint(launchArgs, breakpointLocation); const processInfo = await processPromise; const processId = processInfo.body.systemProcessId; @@ -196,7 +197,7 @@ let testCounter = 0; expect(isProcessRunning(processId)).to.be.equal(false, 'Python (debugee) Process is still alive'); }); test('Test conditional breakpoints', async () => { - const threadIdPromise = debugClient.waitForEvent('thread', THREAD_TIMEOUT); + const threadIdPromise = debugClient.waitForEvent('thread'); await Promise.all([ debugClient.configurationSequence(), @@ -227,7 +228,7 @@ let testCounter = 0; expect(vari.value).to.be.equal('3'); }); test('Test variables', async () => { - const threadIdPromise = debugClient.waitForEvent('thread', THREAD_TIMEOUT); + const threadIdPromise = debugClient.waitForEvent('thread'); await Promise.all([ debugClient.configurationSequence(), debugClient.launch(buildLauncArgs('sample2.py', false)), @@ -297,7 +298,7 @@ let testCounter = 0; expect(response.body.value).to.be.equal('1234'); }); test('Test evaluating expressions', async () => { - const threadIdPromise = debugClient.waitForEvent('thread', THREAD_TIMEOUT); + const threadIdPromise = debugClient.waitForEvent('thread'); await Promise.all([ debugClient.configurationSequence(), @@ -323,7 +324,7 @@ let testCounter = 0; expect(response.body.result).to.be.equal('6', 'expression value is incorrect'); }); test('Test stepover', async () => { - const threadIdPromise = debugClient.waitForEvent('thread', THREAD_TIMEOUT); + const threadIdPromise = debugClient.waitForEvent('thread'); await Promise.all([ debugClient.configurationSequence(), @@ -361,7 +362,7 @@ let testCounter = 0; ]); }); test('Test stepin and stepout', async () => { - const threadIdPromise = debugClient.waitForEvent('thread', THREAD_TIMEOUT); + const threadIdPromise = debugClient.waitForEvent('thread'); await Promise.all([ debugClient.configurationSequence(), @@ -418,7 +419,7 @@ let testCounter = 0; debugClient.configurationSequence(), debugClient.launch(buildLauncArgs('forever.py', false)), debugClient.waitForEvent('initialized'), - debugClient.waitForEvent('process', THREAD_TIMEOUT) + debugClient.waitForEvent('process') ]); await sleep(3); diff --git a/src/test/debugger/portAndHost.test.ts b/src/test/debugger/portAndHost.test.ts index 7bbee91fb728..edfd2a2a158e 100644 --- a/src/test/debugger/portAndHost.test.ts +++ b/src/test/debugger/portAndHost.test.ts @@ -10,6 +10,7 @@ import { DebugClient } from 'vscode-debugadapter-testsupport'; import { noop } from '../../client/common/core.utils'; import { LaunchRequestArguments } from '../../client/debugger/Common/Contracts'; import { IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; +import { DEBUGGER_TIMEOUT } from './common/constants'; use(chaiAsPromised); @@ -31,6 +32,7 @@ const EXPERIMENTAL_DEBUG_ADAPTER = path.join(__dirname, '..', '..', 'client', 'd } await new Promise(resolve => setTimeout(resolve, 1000)); debugClient = new DebugClient('node', testAdapterFilePath, debuggerType); + debugClient.defaultTimeout = DEBUGGER_TIMEOUT; await debugClient.start(); }); teardown(async () => { From 9f4ef17a8676ebeda669f6effdfba2c62a19142c Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 16 Mar 2018 17:44:05 -0700 Subject: [PATCH 042/433] Add support to read name of Pipfile from environment variable (#1092) * :sparkles: support for pip environment variable to get pipfile name * :memo: news entry * Fixes #999 --- news/1 Enhancements/999.md | 1 + .../locators/services/pipEnvService.ts | 15 ++- src/test/interpreters/pipEnvService.test.ts | 127 ++++++++++++++++++ 3 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 news/1 Enhancements/999.md create mode 100644 src/test/interpreters/pipEnvService.test.ts diff --git a/news/1 Enhancements/999.md b/news/1 Enhancements/999.md new file mode 100644 index 000000000000..c215b7aadd31 --- /dev/null +++ b/news/1 Enhancements/999.md @@ -0,0 +1 @@ +Add support to read name of Pipfile from environment variable. diff --git a/src/client/interpreter/locators/services/pipEnvService.ts b/src/client/interpreter/locators/services/pipEnvService.ts index 738eba8f605b..c4914bace250 100644 --- a/src/client/interpreter/locators/services/pipEnvService.ts +++ b/src/client/interpreter/locators/services/pipEnvService.ts @@ -7,12 +7,14 @@ import { Uri } from 'vscode'; import { IApplicationShell, IWorkspaceService } from '../../../common/application/types'; import { IFileSystem } from '../../../common/platform/types'; import { IProcessService } from '../../../common/process/types'; +import { ICurrentProcess } from '../../../common/types'; import { getPythonExecutable } from '../../../debugger/Common/Utils'; import { IServiceContainer } from '../../../ioc/types'; import { IInterpreterVersionService, InterpreterType, PythonInterpreter } from '../../contracts'; import { CacheableLocatorService } from './cacheableLocatorService'; const execName = 'pipenv'; +const pipEnvFileNameVariable = 'PIPENV_PIPFILE'; @injectable() export class PipEnvService extends CacheableLocatorService { @@ -69,12 +71,23 @@ export class PipEnvService extends CacheableLocatorService { private async getInterpreterPathFromPipenv(cwd: string): Promise { // Quick check before actually running pipenv - if (!await this.fs.fileExistsAsync(path.join(cwd, 'Pipfile'))) { + if (!await this.checkIfPipFileExists(cwd)) { return; } const venvFolder = await this.invokePipenv('--venv', cwd); return venvFolder && await this.fs.directoryExistsAsync(venvFolder) ? venvFolder : undefined; } + private async checkIfPipFileExists(cwd: string): Promise { + const currentProcess = this.serviceContainer.get(ICurrentProcess); + const pipFileName = currentProcess.env[pipEnvFileNameVariable]; + if (typeof pipFileName === 'string' && await this.fs.fileExistsAsync(path.join(cwd, pipFileName))) { + return true; + } + if (await this.fs.fileExistsAsync(path.join(cwd, 'Pipfile'))) { + return true; + } + return false; + } private async invokePipenv(arg: string, rootPath: string): Promise { try { diff --git a/src/test/interpreters/pipEnvService.test.ts b/src/test/interpreters/pipEnvService.test.ts new file mode 100644 index 000000000000..5fe4cc4f5324 --- /dev/null +++ b/src/test/interpreters/pipEnvService.test.ts @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { expect } from 'chai'; +import * as path from 'path'; +import * as TypeMoq from 'typemoq'; +import { Uri, WorkspaceFolder } from 'vscode'; +import { IApplicationShell, IWorkspaceService } from '../../client/common/application/types'; +import { EnumEx } from '../../client/common/enumUtils'; +import { IFileSystem } from '../../client/common/platform/types'; +import { IProcessService } from '../../client/common/process/types'; +import { ICurrentProcess, IPersistentState, IPersistentStateFactory } from '../../client/common/types'; +import { IInterpreterLocatorService, IInterpreterVersionService } from '../../client/interpreter/contracts'; +import { PipEnvService } from '../../client/interpreter/locators/services/pipEnvService'; +import { IServiceContainer } from '../../client/ioc/types'; + +enum OS { + Mac, Windows, Linux +} + +// tslint:disable-next-line:max-func-body-length +suite('Interpreters - PipEnv', () => { + const rootWorkspace = Uri.file(path.join('usr', 'desktop', 'wkspc1')).fsPath; + EnumEx.getNamesAndValues(OS).forEach(os => { + [undefined, Uri.file(path.join(rootWorkspace, 'one.py'))].forEach(resource => { + const testSuffix = ` (${os.name}, ${resource ? 'with' : 'without'} a workspace)`; + + let pipEnvService: IInterpreterLocatorService; + let serviceContainer: TypeMoq.IMock; + let interpreterVersionService: TypeMoq.IMock; + let processService: TypeMoq.IMock; + let currentProcess: TypeMoq.IMock; + let fileSystem: TypeMoq.IMock; + let appShell: TypeMoq.IMock; + let persistentStateFactory: TypeMoq.IMock; + setup(() => { + serviceContainer = TypeMoq.Mock.ofType(); + const workspaceService = TypeMoq.Mock.ofType(); + interpreterVersionService = TypeMoq.Mock.ofType(); + fileSystem = TypeMoq.Mock.ofType(); + processService = TypeMoq.Mock.ofType(); + appShell = TypeMoq.Mock.ofType(); + currentProcess = TypeMoq.Mock.ofType(); + persistentStateFactory = TypeMoq.Mock.ofType(); + + // tslint:disable-next-line:no-any + const persistentState = TypeMoq.Mock.ofType>(); + persistentStateFactory.setup(p => p.createGlobalPersistentState(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => persistentState.object); + persistentStateFactory.setup(p => p.createWorkspacePersistentState(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => persistentState.object); + persistentState.setup(p => p.value).returns(() => undefined); + persistentState.setup(p => p.updateValue(TypeMoq.It.isAny())).returns(() => Promise.resolve()); + + const workspaceFolder = TypeMoq.Mock.ofType(); + workspaceFolder.setup(w => w.uri).returns(() => Uri.file(rootWorkspace)); + workspaceService.setup(w => w.getWorkspaceFolder(TypeMoq.It.isAny())).returns(() => workspaceFolder.object); + workspaceService.setup(w => w.rootPath).returns(() => rootWorkspace); + + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IWorkspaceService))).returns(() => workspaceService.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInterpreterVersionService))).returns(() => interpreterVersionService.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IProcessService))).returns(() => processService.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ICurrentProcess))).returns(() => currentProcess.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IFileSystem))).returns(() => fileSystem.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IApplicationShell))).returns(() => appShell.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPersistentStateFactory))).returns(() => persistentStateFactory.object); + + pipEnvService = new PipEnvService(serviceContainer.object); + }); + + test(`Should return an empty list'${testSuffix}`, () => { + const environments = pipEnvService.getInterpreters(resource); + expect(environments).to.be.eventually.deep.equal([]); + }); + test(`Should return an empty list if there is a \'PipFile\'${testSuffix}`, async () => { + const env = {}; + currentProcess.setup(c => c.env).returns(() => env); + fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(false)).verifiable(TypeMoq.Times.once()); + const environments = await pipEnvService.getInterpreters(resource); + + expect(environments).to.be.deep.equal([]); + fileSystem.verifyAll(); + }); + test(`Should display wanring message if there is a \'PipFile\' but \'pipenv --venv\' failes ${testSuffix}`, async () => { + const env = {}; + currentProcess.setup(c => c.env).returns(() => env); + processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.reject('')); + fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(true)); + appShell.setup(a => a.showWarningMessage(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('')).verifiable(TypeMoq.Times.once()); + const environments = await pipEnvService.getInterpreters(resource); + + expect(environments).to.be.deep.equal([]); + appShell.verifyAll(); + }); + test(`Should return interpreter information${testSuffix}`, async () => { + const env = {}; + const venvDir = 'one'; + currentProcess.setup(c => c.env).returns(() => env); + processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: venvDir })); + interpreterVersionService.setup(v => v.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('xyz')); + fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(true)).verifiable(); + fileSystem.setup(fs => fs.directoryExistsAsync(TypeMoq.It.isValue(venvDir))).returns(() => Promise.resolve(true)).verifiable(); + const environments = await pipEnvService.getInterpreters(resource); + + expect(environments).to.be.lengthOf(1); + fileSystem.verifyAll(); + }); + test(`Should return interpreter information using PipFile defined in Env variable${testSuffix}`, async () => { + const envPipFile = 'XYZ'; + const env = { + PIPENV_PIPFILE: envPipFile + }; + const venvDir = 'one'; + currentProcess.setup(c => c.env).returns(() => env); + processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: venvDir })); + interpreterVersionService.setup(v => v.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('xyz')); + fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(false)).verifiable(TypeMoq.Times.never()); + fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(path.join(rootWorkspace, envPipFile)))).returns(() => Promise.resolve(true)).verifiable(TypeMoq.Times.once()); + fileSystem.setup(fs => fs.directoryExistsAsync(TypeMoq.It.isValue(venvDir))).returns(() => Promise.resolve(true)).verifiable(); + const environments = await pipEnvService.getInterpreters(resource); + + expect(environments).to.be.lengthOf(1); + fileSystem.verifyAll(); + }); + }); + }); +}); From ede1b1218ee47a03aef3eaa9f34cbfac956c8401 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 16 Mar 2018 18:43:44 -0700 Subject: [PATCH 043/433] Resolve debug configuration information in `launch.json` when debugging without opening a python file (#1099) * :bug: activate extension when resolving python launch config info * :memo: add a news entry * Fixes 1098 --- news/2 Fixes/1098.md | 1 + package.json | 1 + 2 files changed, 2 insertions(+) create mode 100644 news/2 Fixes/1098.md diff --git a/news/2 Fixes/1098.md b/news/2 Fixes/1098.md new file mode 100644 index 000000000000..d7b5b5e35ef1 --- /dev/null +++ b/news/2 Fixes/1098.md @@ -0,0 +1 @@ +Resolve debug configuration information in `launch.json` when debugging without opening a python file. diff --git a/package.json b/package.json index 96fbb30580c6..40c72e287de0 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ ], "activationEvents": [ "onLanguage:python", + "onDebugResolve:python", "onCommand:python.execInTerminal", "onCommand:python.sortImports", "onCommand:python.runtests", From cb4ccf36da69dfb41458fccdfd456a85265c9402 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 16 Mar 2018 21:50:27 -0700 Subject: [PATCH 044/433] Add a PySpark debug configuration for the experimental debugger (#1102) * :sparkles: add pyspark debug configuration * :memo: add news entry * Fixes #1029 --- news/1 Enhancements/1029.md | 1 + package.json | 30 +++++++++++++++++++++--------- 2 files changed, 22 insertions(+), 9 deletions(-) create mode 100644 news/1 Enhancements/1029.md diff --git a/news/1 Enhancements/1029.md b/news/1 Enhancements/1029.md new file mode 100644 index 000000000000..9da8383fe46d --- /dev/null +++ b/news/1 Enhancements/1029.md @@ -0,0 +1 @@ +Add a PySpark debug configuration for the experimental debugger. diff --git a/package.json b/package.json index 40c72e287de0..b0223de08a62 100644 --- a/package.json +++ b/package.json @@ -772,21 +772,33 @@ "description": "%python.snippet.launch.django.description%", "body": { "name": "Django", - "type": "python", + "type": "pythonExperimental", "request": "launch", - "pythonPath": "^\"\\${config:python.pythonPath}\"", "program": "^\"\\${workspaceFolder}/manage.py\"", - "cwd": "^\"\\${workspaceFolder}\"", - "console": "integratedTerminal", - "internalConsoleOptions": "neverOpen", "args": [ "runserver", "--noreload", "--nothreading" - ], - "env": {}, - "envFile": "^\"\\${workspaceFolder}/.env\"", - "debugOptions": [] + ] + } + }, + { + "label": "Python Experimental: PySpark", + "description": "%python.snippet.launch.pyspark.description%", + "body": { + "name": "PySpark", + "type": "pythonExperimental", + "request": "launch", + "osx": { + "pythonPath": "^\"\\${env:SPARK_HOME}/bin/spark-submit\"" + }, + "windows": { + "pythonPath": "^\"\\${env:SPARK_HOME}/bin/spark-submit.cmd\"" + }, + "linux": { + "pythonPath": "^\"\\${env:SPARK_HOME}/bin/spark-submit\"" + }, + "program": "^\"\\${file}\"" } } ], From 4947d30f63702fa28b2ab79f378fe56423f1ad68 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 16 Mar 2018 21:52:26 -0700 Subject: [PATCH 045/433] :sparkles: added watson debug configuration snippet (#1105) Fixes #1031 --- news/1 Enhancements/1031.md | 1 + package.json | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 news/1 Enhancements/1031.md diff --git a/news/1 Enhancements/1031.md b/news/1 Enhancements/1031.md new file mode 100644 index 000000000000..299a112ecfcf --- /dev/null +++ b/news/1 Enhancements/1031.md @@ -0,0 +1 @@ +Add a Watson debug configuration for the experimental debugger. diff --git a/package.json b/package.json index b0223de08a62..d0159e0334be 100644 --- a/package.json +++ b/package.json @@ -800,6 +800,21 @@ }, "program": "^\"\\${file}\"" } + }, + { + "label": "Python Experimental: Watson", + "description": "%python.snippet.launch.watson.description%", + "body": { + "name": "Watson", + "type": "pythonExperimental", + "request": "launch", + "program": "^\"\\${workspaceFolder}/console.py\"", + "args": [ + "dev", + "runserver", + "--noreload=True" + ] + } } ], "configurationAttributes": { From 048f9d37ea88fa705f5435cd77d5fe53a6dd2105 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 16 Mar 2018 21:52:42 -0700 Subject: [PATCH 046/433] :sparkles: add Scrapy debug configuration for experimental debugger (#1106) Fixes #1032 --- news/1 Enhancements/1032.md | 1 + package.json | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 news/1 Enhancements/1032.md diff --git a/news/1 Enhancements/1032.md b/news/1 Enhancements/1032.md new file mode 100644 index 000000000000..d7d5cb280e36 --- /dev/null +++ b/news/1 Enhancements/1032.md @@ -0,0 +1 @@ +Add a Scrapy debug configuration for the experimental debugger. diff --git a/package.json b/package.json index d0159e0334be..4c63c670f85f 100644 --- a/package.json +++ b/package.json @@ -815,6 +815,22 @@ "--noreload=True" ] } + }, + { + "label": "Python Experimental: Scrapy", + "description": "%python.snippet.launch.scrapy.description%", + "body": { + "name": "Scrapy", + "type": "pythonExperimental", + "request": "launch", + "module": "scrapy", + "args": [ + "crawl", + "specs", + "-o", + "bikes.json" + ] + } } ], "configurationAttributes": { @@ -850,7 +866,7 @@ }, "console": { "enum": [ - "none", + "none", "integratedTerminal", "externalTerminal" ], From 39c1c4d85b6dcec53d71e19de61307a277b5426f Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Sat, 17 Mar 2018 23:30:26 -0700 Subject: [PATCH 047/433] Change installation command for pip on appveyor (#1108) Fixes #1107 --- appveyor.yml | 2 +- news/3 Code Health/1107.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 news/3 Code Health/1107.md diff --git a/appveyor.yml b/appveyor.yml index b4eca06c93b4..774b4d93ca18 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,7 +14,7 @@ install: - npm i -g yarn - yarn - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - - pip install -U pip + - python -m pip install -U pip - pip install -t ./pythonFiles/experimental/ptvsd git+https://github.com/Microsoft/ptvsd/ - python --version - python -m easy_install -U setuptools diff --git a/news/3 Code Health/1107.md b/news/3 Code Health/1107.md new file mode 100644 index 000000000000..21e00e5d7662 --- /dev/null +++ b/news/3 Code Health/1107.md @@ -0,0 +1 @@ +Change the command used to install pip on AppVeyor to avoid installation errors. From c7a8a35bc6941fc99cb13b701965d7e90ef4e467 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Sun, 18 Mar 2018 00:47:37 -0700 Subject: [PATCH 048/433] Add a Pyramid debug configuration for the experimental debugger (#1104) * :package: add Pyramid as a valid entry for DebugOptions * merged master * Fixes #1030 --- news/1 Enhancements/1030.md | 1 + package.json | 19 ++++++- src/client/common/platform/fileSystem.ts | 7 ++- src/client/common/platform/types.ts | 5 +- src/client/common/types.ts | 2 +- src/client/debugger/Common/Contracts.ts | 4 +- src/client/debugger/Common/telemetry.ts | 2 +- .../debugger/DebugClients/LocalDebugClient.ts | 15 +++--- src/client/debugger/Main.ts | 33 +++++------- .../debugger/configProviders/baseProvider.ts | 13 ++++- src/test/common/platform/filesystem.test.ts | 3 ++ .../debugger/configProvider/provider.test.ts | 54 ++++++++++++++++++- 12 files changed, 119 insertions(+), 39 deletions(-) create mode 100644 news/1 Enhancements/1030.md diff --git a/news/1 Enhancements/1030.md b/news/1 Enhancements/1030.md new file mode 100644 index 000000000000..5822cd9b67ee --- /dev/null +++ b/news/1 Enhancements/1030.md @@ -0,0 +1 @@ +Add a Pyramid debug configuration for the experimental debugger. diff --git a/package.json b/package.json index 4c63c670f85f..b02bdb43601e 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "activationEvents": [ "onLanguage:python", "onDebugResolve:python", + "onDebugResolve:pythonExperimental", "onCommand:python.execInTerminal", "onCommand:python.sortImports", "onCommand:python.runtests", @@ -831,6 +832,21 @@ "bikes.json" ] } + }, + { + "label": "Python Experimental: Pyramid", + "description": "%python.snippet.launch.pyramid.description%", + "body": { + "name": "Pyramid", + "type": "pythonExperimental", + "request": "launch", + "args": [ + "^\"\\${workspaceFolder}/development.ini\"" + ], + "debugOptions": [ + "Pyramid" + ] + } } ], "configurationAttributes": { @@ -884,7 +900,8 @@ "items": { "type": "string", "enum": [ - "Sudo" + "Sudo", + "Pyramid" ] }, "default": [] diff --git a/src/client/common/platform/fileSystem.ts b/src/client/common/platform/fileSystem.ts index 89ec9aac3e9e..463b1089b6fe 100644 --- a/src/client/common/platform/fileSystem.ts +++ b/src/client/common/platform/fileSystem.ts @@ -29,6 +29,9 @@ export class FileSystem implements IFileSystem { public fileExistsAsync(filePath: string): Promise { return this.objectExistsAsync(filePath, (stats) => stats.isFile()); } + public fileExistsSync(filePath: string): boolean { + return fs.existsSync(filePath); + } /** * Reads the contents of the file using utf8 and returns the string contents. * @param {string} filePath @@ -79,9 +82,9 @@ export class FileSystem implements IFileSystem { } public appendFileSync(filename: string, data: {}, encoding: string): void; - public appendFileSync(filename: string, data: {}, options?: { encoding?: string; mode?: number; flag?: string; }): void; + public appendFileSync(filename: string, data: {}, options?: { encoding?: string; mode?: number; flag?: string }): void; // tslint:disable-next-line:unified-signatures - public appendFileSync(filename: string, data: {}, options?: { encoding?: string; mode?: string; flag?: string; }): void; + public appendFileSync(filename: string, data: {}, options?: { encoding?: string; mode?: string; flag?: string }): void; public appendFileSync(filename: string, data: {}, optionsOrEncoding: {}): void { return fs.appendFileSync(filename, data, optionsOrEncoding); } diff --git a/src/client/common/platform/types.ts b/src/client/common/platform/types.ts index d87722efdd38..6c40a7a6a068 100644 --- a/src/client/common/platform/types.ts +++ b/src/client/common/platform/types.ts @@ -33,14 +33,15 @@ export interface IFileSystem { directorySeparatorChar: string; objectExistsAsync(path: string, statCheck: (s: fs.Stats) => boolean): Promise; fileExistsAsync(path: string): Promise; + fileExistsSync(path: string): boolean; directoryExistsAsync(path: string): Promise; createDirectoryAsync(path: string): Promise; getSubDirectoriesAsync(rootDir: string): Promise; arePathsSame(path1: string, path2: string): boolean; readFile(filePath: string): Promise; appendFileSync(filename: string, data: {}, encoding: string): void; - appendFileSync(filename: string, data: {}, options?: { encoding?: string; mode?: number; flag?: string; }): void; + appendFileSync(filename: string, data: {}, options?: { encoding?: string; mode?: number; flag?: string }): void; // tslint:disable-next-line:unified-signatures - appendFileSync(filename: string, data: {}, options?: { encoding?: string; mode?: string; flag?: string; }): void; + appendFileSync(filename: string, data: {}, options?: { encoding?: string; mode?: string; flag?: string }): void; getRealPathAsync(path: string): Promise; } diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 687f9898c6b4..4083b0965fd9 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -223,5 +223,5 @@ export interface IConfigurationService { export const ISocketServer = Symbol('ISocketServer'); export interface ISocketServer extends Disposable { readonly client: Promise; - Start(options?: { port?: number, host?: string }): Promise; + Start(options?: { port?: number; host?: string }): Promise; } diff --git a/src/client/debugger/Common/Contracts.ts b/src/client/debugger/Common/Contracts.ts index 2fb8e9d17cdf..ef0047be2d42 100644 --- a/src/client/debugger/Common/Contracts.ts +++ b/src/client/debugger/Common/Contracts.ts @@ -1,4 +1,4 @@ -// tslint:disable:interface-name member-access no-single-line-block-comment no-any no-stateless-class member-ordering prefer-method-signature +// tslint:disable:interface-name member-access no-single-line-block-comment no-any no-stateless-class member-ordering prefer-method-signature no-unnecessary-class 'use strict'; import { ChildProcess } from 'child_process'; @@ -8,7 +8,7 @@ import { DebugProtocol } from 'vscode-debugprotocol'; import { DebuggerPerformanceTelemetry, DebuggerTelemetry } from '../../telemetry/types'; export class TelemetryEvent extends OutputEvent { - body: { + body!: { /** The category of output (such as: 'console', 'stdout', 'stderr', 'telemetry'). If not specified, 'console' is assumed. */ category: string; /** The output to report. */ diff --git a/src/client/debugger/Common/telemetry.ts b/src/client/debugger/Common/telemetry.ts index cecde60b2658..2e454c350379 100644 --- a/src/client/debugger/Common/telemetry.ts +++ b/src/client/debugger/Common/telemetry.ts @@ -10,7 +10,7 @@ import { DebuggerPerformanceTelemetry } from '../../telemetry/types'; import { TelemetryEvent } from './Contracts'; type DebugAction = 'stepIn' | 'stepOut' | 'continue' | 'next' | 'launch'; -type DebugPerformanceInformation = { action: DebugAction, timer: StopWatch }; +type DebugPerformanceInformation = { action: DebugAction; timer: StopWatch }; const executionStack: DebugPerformanceInformation[] = []; diff --git a/src/client/debugger/DebugClients/LocalDebugClient.ts b/src/client/debugger/DebugClients/LocalDebugClient.ts index 9e7cedb05727..e1824936ab2d 100644 --- a/src/client/debugger/DebugClients/LocalDebugClient.ts +++ b/src/client/debugger/DebugClients/LocalDebugClient.ts @@ -1,5 +1,4 @@ -import * as child_process from 'child_process'; -import { ChildProcess } from 'child_process'; +import { ChildProcess, spawn } from 'child_process'; import * as path from 'path'; import { DebugSession, OutputEvent } from 'vscode-debugadapter'; import { DebugProtocol } from 'vscode-debugprotocol'; @@ -25,8 +24,8 @@ enum DebugServerStatus { } export class LocalDebugClient extends DebugClient { - protected pyProc: child_process.ChildProcess | undefined; - protected pythonProcess: IPythonProcess; + protected pyProc: ChildProcess | undefined; + protected pythonProcess!: IPythonProcess; protected debugServer: BaseDebugServer | undefined; private get debugServerStatus(): DebugServerStatus { if (this.debugServer && this.debugServer!.IsRunning) { @@ -108,7 +107,7 @@ export class LocalDebugClient extends DebugClient { break; } default: { - this.pyProc = child_process.spawn(pythonPath, args, { cwd: processCwd, env: environmentVariables }); + this.pyProc = spawn(pythonPath, args, { cwd: processCwd, env: environmentVariables }); this.handleProcessOutput(this.pyProc!, reject); // Here we wait for the application to connect to the socket server. @@ -168,7 +167,11 @@ export class LocalDebugClient extends DebugClient { if (typeof this.args.module === 'string' && this.args.module.length > 0) { return [vsDebugOptions.join(','), '-m', this.args.module].concat(programArgs); } - return [vsDebugOptions.join(','), this.args.program].concat(programArgs); + const args = [vsDebugOptions.join(',')]; + if (this.args.program && this.args.program.length > 0) { + args.push(this.args.program); + } + return args.concat(programArgs); } private launchExternalTerminal(sudo: boolean, cwd: string, pythonPath: string, args: string[], env: {}) { return new Promise((resolve, reject) => { diff --git a/src/client/debugger/Main.ts b/src/client/debugger/Main.ts index bd8b44dc7aef..48fe8915bde2 100644 --- a/src/client/debugger/Main.ts +++ b/src/client/debugger/Main.ts @@ -1,4 +1,4 @@ -// tslint:disable:quotemark ordered-imports promise-must-complete member-ordering no-any prefer-template cyclomatic-complexity no-empty no-multiline-string one-line no-invalid-template-strings no-suspicious-comment no-var-self +// tslint:disable:quotemark ordered-imports promise-must-complete member-ordering no-any prefer-template cyclomatic-complexity no-empty no-multiline-string one-line no-invalid-template-strings no-suspicious-comment no-var-self no-duplicate-imports "use strict"; // This line should always be right on top. @@ -9,8 +9,7 @@ if ((Reflect as any).metadata === undefined) { } import * as fs from "fs"; import * as path from "path"; -import { Handles, InitializedEvent, OutputEvent, Scope, Source, StackFrame, StoppedEvent, TerminatedEvent, Thread, Variable, LoggingDebugSession, logger, BreakpointEvent, Breakpoint } from "vscode-debugadapter"; -import { ThreadEvent } from "vscode-debugadapter"; +import { Handles, InitializedEvent, OutputEvent, Scope, Source, StackFrame, StoppedEvent, TerminatedEvent, Thread, Variable, LoggingDebugSession, logger, BreakpointEvent, Breakpoint, ThreadEvent } from "vscode-debugadapter"; import { DebugProtocol } from "vscode-debugprotocol"; import { DEBUGGER } from '../../client/telemetry/constants'; import { DebuggerTelemetry } from '../../client/telemetry/types'; @@ -23,7 +22,6 @@ import { DebugClient } from "./DebugClients/DebugClient"; import { CreateAttachDebugClient, CreateLaunchDebugClient } from "./DebugClients/DebugFactory"; import { BaseDebugServer } from "./DebugServers/BaseDebugServer"; import { PythonProcess } from "./PythonProcess"; -import { IS_WINDOWS } from './Common/Utils'; import { sendPerformanceTelemetry, capturePerformanceTelemetry, PerformanceTelemetryCondition } from "./Common/telemetry"; import { LogLevel } from "vscode-debugadapter/lib/logger"; @@ -41,13 +39,13 @@ export class PythonDebugger extends LoggingDebugSession { private registeredBreakpoints: Map; private registeredBreakpointsByFileName: Map; private debuggerLoaded: Promise; - private debuggerLoadedPromiseResolve: () => void; + private debuggerLoadedPromiseResolve!: () => void; private debugClient?: DebugClient<{}>; - private configurationDone: Promise; + private configurationDone!: Promise; private configurationDonePromiseResolve?: () => void; private lastException?: IPythonException; - private _supportsRunInTerminalRequest: boolean; - private terminateEventSent: boolean; + private _supportsRunInTerminalRequest: boolean = false; + private terminateEventSent: boolean = false; public constructor(debuggerLinesStartAt1: boolean, isServer: boolean) { super(path.join(__dirname, '..', '..', '..', 'debug.log'), debuggerLinesStartAt1, isServer === true); this._variableHandles = new Handles(); @@ -92,7 +90,7 @@ export class PythonDebugger extends LoggingDebugSession { } private pythonProcess?: PythonProcess; - private debugServer: BaseDebugServer; + private debugServer!: BaseDebugServer; private startDebugServer(): Promise { let programDirectory = ''; @@ -208,8 +206,8 @@ export class PythonDebugger extends LoggingDebugSession { this.sendEvent(new OutputEvent(output, outputChannel)); } private entryResponse?: DebugProtocol.LaunchResponse; - private launchArgs: LaunchRequestArguments; - private attachArgs: AttachRequestArguments; + private launchArgs!: LaunchRequestArguments; + private attachArgs!: AttachRequestArguments; private canStartDebugger(): Promise { return Promise.resolve(true); } @@ -230,15 +228,6 @@ export class PythonDebugger extends LoggingDebugSession { } catch (ex) { } - if (Array.isArray(args.debugOptions) && args.debugOptions.indexOf("Pyramid") >= 0) { - const pserve = IS_WINDOWS ? "pserve.exe" : "pserve"; - if (fs.existsSync(args.pythonPath)) { - args.program = path.join(path.dirname(args.pythonPath), pserve); - } - else { - args.program = pserve; - } - } // Confirm the file exists if (typeof args.module !== 'string' || args.module.length === 0) { if (!fs.existsSync(args.program)) { @@ -279,6 +268,7 @@ export class PythonDebugger extends LoggingDebugSession { }); this.entryResponse = response; + // tslint:disable-next-line:no-this-assignment const that = this; this.startDebugServer().then(dbgServer => { @@ -302,6 +292,7 @@ export class PythonDebugger extends LoggingDebugSession { this.attachArgs = args; this.debugClient = CreateAttachDebugClient(args, this); this.entryResponse = response; + // tslint:disable-next-line:no-this-assignment const that = this; this.canStartDebugger().then(() => { @@ -380,7 +371,7 @@ export class PythonDebugger extends LoggingDebugSession { } // VSC needs `id` to uniquely identify each breakpoint (part of the protocol spec). - const breakpoints: { verified: boolean, line: number, id: number }[] = []; + const breakpoints: { verified: boolean; line: number; id: number }[] = []; const linesToAdd = args.breakpoints!.map(b => b.line); const registeredBks = this.registeredBreakpointsByFileName.get(args.source.path!)!; const linesToRemove = registeredBks.map(b => b.LineNo).filter(oldLine => linesToAdd.indexOf(oldLine) === -1); diff --git a/src/client/debugger/configProviders/baseProvider.ts b/src/client/debugger/configProviders/baseProvider.ts index 2459fe8f4f1f..fef1a3c377da 100644 --- a/src/client/debugger/configProviders/baseProvider.ts +++ b/src/client/debugger/configProviders/baseProvider.ts @@ -8,6 +8,7 @@ import * as path from 'path'; import { CancellationToken, DebugConfiguration, DebugConfigurationProvider, ProviderResult, Uri, WorkspaceFolder } from 'vscode'; import { IDocumentManager, IWorkspaceService } from '../../common/application/types'; import { PythonLanguage } from '../../common/constants'; +import { IFileSystem, IPlatformService } from '../../common/platform/types'; import { IConfigurationService } from '../../common/types'; import { IServiceContainer } from '../../ioc/types'; import { DebuggerType, LaunchRequestArguments } from '../Common/Contracts'; @@ -15,7 +16,7 @@ import { DebuggerType, LaunchRequestArguments } from '../Common/Contracts'; // tslint:disable:no-invalid-template-strings export type PythonDebugConfiguration = DebugConfiguration & LaunchRequestArguments; -export type PTVSDDebugConfiguration = PythonDebugConfiguration & { redirectOutput: boolean, fixFilePathCase: boolean }; +export type PTVSDDebugConfiguration = PythonDebugConfiguration & { redirectOutput: boolean; fixFilePathCase: boolean }; @injectable() export abstract class BaseConfigurationProvider implements DebugConfigurationProvider { @@ -64,6 +65,16 @@ export abstract class BaseConfigurationProvider implements DebugConfigurationPro if (debugConfiguration.debugOptions.indexOf('RedirectOutput') === -1) { debugConfiguration.debugOptions.push('RedirectOutput'); } + if (debugConfiguration.debugOptions.indexOf('Pyramid') >= 0) { + const platformService = this.serviceContainer.get(IPlatformService); + const fs = this.serviceContainer.get(IFileSystem); + const pserve = platformService.isWindows ? 'pserve.exe' : 'pserve'; + if (fs.fileExistsSync(debugConfiguration.pythonPath)) { + debugConfiguration.program = path.join(path.dirname(debugConfiguration.pythonPath), pserve); + } else { + debugConfiguration.program = pserve; + } + } } private getWorkspaceFolder(folder: WorkspaceFolder | undefined, config: PythonDebugConfiguration): Uri | undefined { if (folder) { diff --git a/src/test/common/platform/filesystem.test.ts b/src/test/common/platform/filesystem.test.ts index fa9331ce60fa..0e77a631f2c4 100644 --- a/src/test/common/platform/filesystem.test.ts +++ b/src/test/common/platform/filesystem.test.ts @@ -67,6 +67,9 @@ suite('FileSystem', () => { test('Case sensitivity is not ignored when comparing file names on linux', async () => { caseSensitivityFileCheck(false, false, true); }); + test('Check existence of files synchronously', async () => { + expect(fileSystem.fileExistsSync(__filename)).to.be.equal(true, 'file not found'); + }); test('Test appending to file', async () => { const dataToAppend = `Some Data\n${new Date().toString()}\nAnd another line`; diff --git a/src/test/debugger/configProvider/provider.test.ts b/src/test/debugger/configProvider/provider.test.ts index e244e9a28c8c..050631d18d01 100644 --- a/src/test/debugger/configProvider/provider.test.ts +++ b/src/test/debugger/configProvider/provider.test.ts @@ -3,7 +3,7 @@ 'use strict'; -// tslint:disable:max-func-body-length no-invalid-template-strings no-any +// tslint:disable:max-func-body-length no-invalid-template-strings no-any no-object-literal-type-assertion import { expect } from 'chai'; import * as path from 'path'; @@ -11,7 +11,7 @@ import * as TypeMoq from 'typemoq'; import { DebugConfiguration, DebugConfigurationProvider, TextDocument, TextEditor, Uri, WorkspaceFolder } from 'vscode'; import { IDocumentManager, IWorkspaceService } from '../../../client/common/application/types'; import { PythonLanguage } from '../../../client/common/constants'; -import { IPlatformService } from '../../../client/common/platform/types'; +import { IFileSystem, IPlatformService } from '../../../client/common/platform/types'; import { IConfigurationService, IPythonSettings } from '../../../client/common/types'; import { PythonDebugConfigurationProvider, PythonV2DebugConfigurationProvider } from '../../../client/debugger'; import { IServiceContainer } from '../../../client/ioc/types'; @@ -24,6 +24,7 @@ import { IServiceContainer } from '../../../client/ioc/types'; let serviceContainer: TypeMoq.IMock; let debugProvider: DebugConfigurationProvider; let platformService: TypeMoq.IMock; + let fileSystem: TypeMoq.IMock; setup(() => { serviceContainer = TypeMoq.Mock.ofType(); debugProvider = new provider.class(serviceContainer.object); @@ -36,8 +37,10 @@ import { IServiceContainer } from '../../../client/ioc/types'; function setupIoc(pythonPath: string, isWindows: boolean = false, isMac: boolean = false, isLinux: boolean = false) { const confgService = TypeMoq.Mock.ofType(); platformService = TypeMoq.Mock.ofType(); + fileSystem = TypeMoq.Mock.ofType(); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationService))).returns(() => confgService.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPlatformService))).returns(() => platformService.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IFileSystem))).returns(() => fileSystem.object); const settings = TypeMoq.Mock.ofType(); settings.setup(s => s.pythonPath).returns(() => pythonPath); confgService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); @@ -305,5 +308,52 @@ import { IServiceContainer } from '../../../client/ioc/types'; } await testFixFilePathCase(false, true, false); }); + async function testPyramidConfiguration(isWindows: boolean, isLinux: boolean, isMac: boolean, addPyramidDebugOption: boolean = true, pythonPathExists = true, shouldWork = true) { + const workspacePath = path.join('usr', 'development', 'wksp1'); + const pythonPath = path.join(workspacePath, 'env', 'bin', 'python'); + const pserveExecutableName = isWindows ? 'pserve.exe' : 'pserve'; + const pservePath = pythonPathExists ? path.join(path.dirname(pythonPath), pserveExecutableName) : pserveExecutableName; + const workspaceFolder = createMoqWorkspaceFolder(workspacePath); + const pythonFile = 'xyz.py'; + setupIoc(pythonPath, isWindows, isMac, isLinux); + setupActiveEditor(pythonFile, PythonLanguage.language); + + const options = addPyramidDebugOption ? { debugOptions: ['Pyramid'] } : {}; + fileSystem.setup(fs => fs.fileExistsSync(TypeMoq.It.isValue(pythonPath))).returns(() => pythonPathExists); + + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, options as any as DebugConfiguration); + if (shouldWork) { + expect(debugConfig).to.have.property('program', pservePath); + } else { + expect(debugConfig!.program).to.be.not.equal(pservePath); + } + } + test('Program is set for Pyramid (windows)', async () => { + await testPyramidConfiguration(true, false, false); + }); + test('Program is set for Pyramid (Linux)', async () => { + await testPyramidConfiguration(false, true, false); + }); + test('Program is set for Pyramid (Mac)', async () => { + await testPyramidConfiguration(false, false, true); + }); + test('Program is not set for Pyramid when DebugOption is not set (windows)', async () => { + await testPyramidConfiguration(true, false, false, false, false, false); + }); + test('Program is not set for Pyramid when DebugOption is not set (Linux)', async () => { + await testPyramidConfiguration(false, true, false, false, false, false); + }); + test('Program is not set for Pyramid when DebugOption is not set (Mac)', async () => { + await testPyramidConfiguration(false, false, true, false, false, false); + }); + test('Program is set to executable name for Pyramid when python exec does not exist (windows)', async () => { + await testPyramidConfiguration(true, false, false, true, false, true); + }); + test('Program is set to executable name for Pyramid when python exec does not exist (Linux)', async () => { + await testPyramidConfiguration(false, true, false, true, false, true); + }); + test('Program is set to executable name for Pyramid when python exec does not exist (Mac)', async () => { + await testPyramidConfiguration(false, false, true, true, false, true); + }); }); }); From e91c600a7be896d75d75bd121375950f73f15a85 Mon Sep 17 00:00:00 2001 From: Lorenzo Villani <241660+lvillani@users.noreply.github.com> Date: Mon, 19 Mar 2018 22:57:44 +0100 Subject: [PATCH 049/433] Add support for requirements.in files for syntax highlighting (#961) --- news/1 Enhancements/961.md | 2 ++ package.json | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 news/1 Enhancements/961.md diff --git a/news/1 Enhancements/961.md b/news/1 Enhancements/961.md new file mode 100644 index 000000000000..431028ffca82 --- /dev/null +++ b/news/1 Enhancements/961.md @@ -0,0 +1,2 @@ +Enable syntax highlighting for `requirements.in` files as used by +e.g. [pip-tools](https://github.com/jazzband/pip-tools). diff --git a/package.json b/package.json index b02bdb43601e..90e86ffba0fe 100644 --- a/package.json +++ b/package.json @@ -1612,11 +1612,14 @@ "requirements.txt" ], "filenames": [ - "requirements.txt" + "requirements.txt", + "requirements.in" ], "filenamePatterns": [ "*-requirements.txt", - "requirements-*.txt" + "requirements-*.txt", + "*-requirements.in", + "requirements-*.in" ], "configuration": "./languages/pip-requirements.json" }, From af40992fa1be6e56947b9b14a79da5658fcb8430 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 19 Mar 2018 21:02:36 -0700 Subject: [PATCH 050/433] Check whether a document is active when detecthing changes in the active document (#1116) * :bug: check even arg * :white_check_mark: add tests * :memo: add news entry * fix code review * Fixes #1114 --- news/3 Code Health/1114.md | 1 + src/client/interpreter/interpreterService.ts | 2 +- .../interpreters/interpreterService.test.ts | 59 ++++++++++++++++++- 3 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 news/3 Code Health/1114.md diff --git a/news/3 Code Health/1114.md b/news/3 Code Health/1114.md new file mode 100644 index 000000000000..0c77070b860d --- /dev/null +++ b/news/3 Code Health/1114.md @@ -0,0 +1 @@ +Check whether a document is active when detecthing changes in the active document. diff --git a/src/client/interpreter/interpreterService.ts b/src/client/interpreter/interpreterService.ts index 84fa7f8cdb57..3dee82a4ceda 100644 --- a/src/client/interpreter/interpreterService.ts +++ b/src/client/interpreter/interpreterService.ts @@ -36,7 +36,7 @@ export class InterpreterService implements Disposable, IInterpreterService { public initialize() { const disposables = this.serviceContainer.get(IDisposableRegistry); const documentManager = this.serviceContainer.get(IDocumentManager); - disposables.push(documentManager.onDidChangeActiveTextEditor((e) => this.refresh(e.document.uri))); + disposables.push(documentManager.onDidChangeActiveTextEditor((e) => e ? this.refresh(e.document.uri) : undefined)); const configService = this.serviceContainer.get(IConfigurationService); (configService.getSettings() as PythonSettings).addListener('change', this.onConfigChanged); } diff --git a/src/test/interpreters/interpreterService.test.ts b/src/test/interpreters/interpreterService.test.ts index b0c1bc64f1c7..7b41dcf45a94 100644 --- a/src/test/interpreters/interpreterService.test.ts +++ b/src/test/interpreters/interpreterService.test.ts @@ -2,14 +2,18 @@ // Licensed under the MIT License. import { expect } from 'chai'; +import { EventEmitter } from 'events'; import { Container } from 'inversify'; import * as path from 'path'; import * as TypeMoq from 'typemoq'; -import { ConfigurationTarget, Uri, WorkspaceConfiguration } from 'vscode'; -import { IWorkspaceService } from '../../client/common/application/types'; +import { ConfigurationTarget, Disposable, TextDocument, TextEditor, Uri, WorkspaceConfiguration } from 'vscode'; +import { IDocumentManager, IWorkspaceService } from '../../client/common/application/types'; +import { noop } from '../../client/common/core.utils'; import { IFileSystem } from '../../client/common/platform/types'; +import { IConfigurationService, IDisposableRegistry } from '../../client/common/types'; import { IPythonPathUpdaterServiceManager } from '../../client/interpreter/configuration/types'; import { + IInterpreterDisplay, IInterpreterHelper, IInterpreterLocatorService, INTERPRETER_LOCATOR_SERVICE, @@ -35,6 +39,7 @@ suite('Interpreters service', () => { let pipenvLocator: TypeMoq.IMock; let wksLocator: TypeMoq.IMock; let fileSystem: TypeMoq.IMock; + let interpreterDisplay: TypeMoq.IMock; setup(async () => { const cont = new Container(); @@ -47,13 +52,16 @@ suite('Interpreters service', () => { workspace = TypeMoq.Mock.ofType(); config = TypeMoq.Mock.ofType(); fileSystem = TypeMoq.Mock.ofType(); + interpreterDisplay = TypeMoq.Mock.ofType(); workspace.setup(x => x.getConfiguration('python', TypeMoq.It.isAny())).returns(() => config.object); + serviceManager.addSingletonInstance(IDisposableRegistry, []); serviceManager.addSingletonInstance(IInterpreterHelper, helper.object); serviceManager.addSingletonInstance(IPythonPathUpdaterServiceManager, updater.object); serviceManager.addSingletonInstance(IWorkspaceService, workspace.object); serviceManager.addSingletonInstance(IInterpreterLocatorService, locator.object, INTERPRETER_LOCATOR_SERVICE); serviceManager.addSingletonInstance(IFileSystem, fileSystem.object); + serviceManager.addSingletonInstance(IInterpreterDisplay, interpreterDisplay.object); pipenvLocator = TypeMoq.Mock.ofType(); wksLocator = TypeMoq.Mock.ofType(); @@ -183,4 +191,51 @@ suite('Interpreters service', () => { serviceManager.addSingletonInstance(IInterpreterLocatorService, wksLocator.object, WORKSPACE_VIRTUAL_ENV_SERVICE); } + + test('Changes to active document should invoke intrepreter.refresh method', async () => { + const service = new InterpreterService(serviceContainer); + const configService = TypeMoq.Mock.ofType(); + const documentManager = TypeMoq.Mock.ofType(); + + let activeTextEditorChangeHandler: Function | undefined; + documentManager.setup(d => d.onDidChangeActiveTextEditor(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(handler => { + activeTextEditorChangeHandler = handler; + return { dispose: noop }; + }); + serviceManager.addSingletonInstance(IConfigurationService, configService.object); + serviceManager.addSingletonInstance(IDocumentManager, documentManager.object); + + // tslint:disable-next-line:no-any + configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => new EventEmitter() as any); + service.initialize(); + const textEditor = TypeMoq.Mock.ofType(); + const uri = Uri.file(path.join('usr', 'file.py')); + const document = TypeMoq.Mock.ofType(); + textEditor.setup(t => t.document).returns(() => document.object); + document.setup(d => d.uri).returns(() => uri); + activeTextEditorChangeHandler!(textEditor.object); + + interpreterDisplay.verify(i => i.refresh(TypeMoq.It.isValue(uri)), TypeMoq.Times.once()); + }); + + test('If there is no active document then intrepreter.refresh should not be invoked', async () => { + const service = new InterpreterService(serviceContainer); + const configService = TypeMoq.Mock.ofType(); + const documentManager = TypeMoq.Mock.ofType(); + + let activeTextEditorChangeHandler: Function | undefined; + documentManager.setup(d => d.onDidChangeActiveTextEditor(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(handler => { + activeTextEditorChangeHandler = handler; + return { dispose: noop }; + }); + serviceManager.addSingletonInstance(IConfigurationService, configService.object); + serviceManager.addSingletonInstance(IDocumentManager, documentManager.object); + + // tslint:disable-next-line:no-any + configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => new EventEmitter() as any); + service.initialize(); + activeTextEditorChangeHandler!(); + + interpreterDisplay.verify(i => i.refresh(TypeMoq.It.isValue(undefined)), TypeMoq.Times.never()); + }); }); From eb2bb54dc16d01c00245c609ff6cc31b8be61593 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 20 Mar 2018 09:45:04 -0700 Subject: [PATCH 051/433] Beta release (#1120) --- CHANGELOG.md | 68 ++++++++++++++++++++++++++++++++++++++++++++++++ news/announce.py | 5 ++-- package.json | 2 +- 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c21837fe22c5..40bb6b4b5f3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,73 @@ # Changelog +## 2018.3.0-beta (19 Mar 2018) + +### Enhancements + +1. Add a PySpark debug configuration for the experimental debugger. + ([#1029](https://github.com/Microsoft/vscode-python/issues/1029)) +1. Add a Pyramid debug configuration for the experimental debugger. + ([#1030](https://github.com/Microsoft/vscode-python/issues/1030)) +1. Add a Watson debug configuration for the experimental debugger. + ([#1031](https://github.com/Microsoft/vscode-python/issues/1031)) +1. Add a Scrapy debug configuration for the experimental debugger. + ([#1032](https://github.com/Microsoft/vscode-python/issues/1032)) +1. When debugging, use `Integrated Terminal` as the default console. + ([#526](https://github.com/Microsoft/vscode-python/issues/526)) +1. Bundle python depedencies (PTVSD package) in the extension for the experimental debugger. + ([#741](https://github.com/Microsoft/vscode-python/issues/741)) +1. Add support for expermental debugger when debugging Python Unit Tests. + ([#906](https://github.com/Microsoft/vscode-python/issues/906)) +1. Support `Debug Console` as a `console` option for the Experimental Debugger. + ([#950](https://github.com/Microsoft/vscode-python/issues/950)) +1. Enable syntax highlighting for `requirements.in` files as used by +e.g. [pip-tools](https://github.com/jazzband/pip-tools). + ([#961](https://github.com/Microsoft/vscode-python/issues/961)) +1. Add support to read name of Pipfile from environment variable. + ([#999](https://github.com/Microsoft/vscode-python/issues/999)) + +### Fixes + +1. Ignore test results when debugging unit tests. + ([#1043](https://github.com/Microsoft/vscode-python/issues/1043)) +1. Resolve debug configuration information in `launch.json` when debugging without opening a python file. + ([#1098](https://github.com/Microsoft/vscode-python/issues/1098)) +1. Fix occasionally having unverified breakpoints + ([#87](https://github.com/Microsoft/vscode-python/issues/87)) +1. Ensure conda installer is not used for non-conda environments. + ([#969](https://github.com/Microsoft/vscode-python/issues/969)) +1. Fixes issue that display incorrect interpreter briefly before updating it to the right value. + ([#981](https://github.com/Microsoft/vscode-python/issues/981)) + +### Code Health + +1. Exclude 'news' folder from getting packaged into the extension. + ([#1020](https://github.com/Microsoft/vscode-python/issues/1020)) +1. Remove Jupyter commands. + ([#1034](https://github.com/Microsoft/vscode-python/issues/1034)) +1. Trigger incremental build compilation only when typescript files are modified. + ([#1040](https://github.com/Microsoft/vscode-python/issues/1040)) +1. Updated npm dependencies in devDependencies and fix TypeScript compilation issues. + ([#1042](https://github.com/Microsoft/vscode-python/issues/1042)) +1. Enable unit testing of stdout and stderr redirection for the experimental debugger. + ([#1048](https://github.com/Microsoft/vscode-python/issues/1048)) +1. Update npm package `vscode-extension-telemetry` to fix the warning 'os.tmpDir() deprecation'. + ([#1066](https://github.com/Microsoft/vscode-python/issues/1066)) +1. Prevent debugger stepping into js code, when debugging async TypeScript code. + ([#1090](https://github.com/Microsoft/vscode-python/issues/1090)) +1. Increase timeouts for the debugger unit tests. + ([#1094](https://github.com/Microsoft/vscode-python/issues/1094)) +1. Change the command used to install pip on AppVeyor to avoid installation errors. + ([#1107](https://github.com/Microsoft/vscode-python/issues/1107)) +1. Enable unit testing of the experimental debugger on CI servers + ([#742](https://github.com/Microsoft/vscode-python/issues/742)) +1. Generate code coverage for debug adapter unit tests. + ([#778](https://github.com/Microsoft/vscode-python/issues/778)) +1. Execute prospector as a module (using -m). + ([#982](https://github.com/Microsoft/vscode-python/issues/982)) +1. Launch the unit tests in debug mode as opposed to running and attaching the debugger. + ([#983](https://github.com/Microsoft/vscode-python/issues/983)) + ## 2018.2.1 (09 Mar 2018) ### Fixes diff --git a/news/announce.py b/news/announce.py index a17e5d086099..a1081eaa52e0 100644 --- a/news/announce.py +++ b/news/announce.py @@ -46,11 +46,11 @@ def sections(directory): """Yield the sections in their appropriate order.""" found = [] for path in directory.iterdir(): - if not path.is_dir(): + if not path.is_dir() or path.name.startswith('.'): continue position, sep, title = path.name.partition(' ') if not sep: - raise ValueError('directory is missing position part') + raise ValueError(f'directory is missing position part: {path.name!r}') found.append(SectionTitle(int(position), title, path)) return sorted(found, key=operator.attrgetter('index')) @@ -114,6 +114,7 @@ class RunType(enum.Enum): @click.argument('directory', default=pathlib.Path(__file__).parent, type=click.Path(exists=True, file_okay=False)) def main(run_type, directory): + directory = pathlib.Path(directory) data = gather(directory) markdown = changelog_markdown(data) if run_type != RunType.dry_run: diff --git a/package.json b/package.json index 90e86ffba0fe..0b0b476f7372 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2018.3.0-alpha", + "version": "2018.3.0-beta", "publisher": "ms-python", "author": { "name": "Microsoft Corporation" From 70abeae802f094cfb8fb1c692267802ce391298c Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 20 Mar 2018 11:03:32 -0700 Subject: [PATCH 052/433] Remove SIGINT handler in debugger adapter, thereby preventing it from shutting down the debugger (#1124) * :fire: remove SIGINT handler * :memo: news entry * Fixes #1122 --- news/3 Code Health/1122.md | 1 + src/client/debugger/Main.ts | 3 --- src/client/debugger/mainV2.ts | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) create mode 100644 news/3 Code Health/1122.md diff --git a/news/3 Code Health/1122.md b/news/3 Code Health/1122.md new file mode 100644 index 000000000000..79587594dda4 --- /dev/null +++ b/news/3 Code Health/1122.md @@ -0,0 +1 @@ +Remove SIGINT handler in debugger adapter, thereby preventing it from shutting down the debugger. diff --git a/src/client/debugger/Main.ts b/src/client/debugger/Main.ts index 48fe8915bde2..f20b7397c0e3 100644 --- a/src/client/debugger/Main.ts +++ b/src/client/debugger/Main.ts @@ -55,9 +55,6 @@ export class PythonDebugger extends LoggingDebugSession { this.debuggerLoaded = new Promise(resolve => { this.debuggerLoadedPromiseResolve = resolve; }); - if (!isServer) { - process.on('SIGINT', this.shutdown); - } } // tslint:disable-next-line:no-unnecessary-override @sendPerformanceTelemetry(PerformanceTelemetryCondition.stoppedEvent) diff --git a/src/client/debugger/mainV2.ts b/src/client/debugger/mainV2.ts index d09afe43d239..53f4d60d17cd 100644 --- a/src/client/debugger/mainV2.ts +++ b/src/client/debugger/mainV2.ts @@ -225,7 +225,6 @@ class DebugManager implements Disposable { if (!this.isServerMode) { const currentProcess = this.serviceContainer.get(ICurrentProcess); currentProcess.on('SIGTERM', this.shutdown); - currentProcess.on('SIGINT', this.shutdown); } this.interceptProtocolMessages(); this.startDebugSession(); From 57b443b910d269017651319477c0426a88a7ecea Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 20 Mar 2018 13:43:55 -0700 Subject: [PATCH 053/433] Link to our dev process Closes #834 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 46915398f950..4e66c84e5a8f 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ contributors (if you would like to contribute a translation, see the * Any and all feedback is appreciated and welcome! - If someone has already [file an issue](https://github.com/Microsoft/vscode-python) that encompasses your feedback, please leave a 👍/👎 reaction on the issue - Otherwise please file a new issue +* If you're interested in the development of the extension, you can read about our [development process](https://github.com/Microsoft/vscode-python/blob/master/CONTRIBUTING.md#development-process) ## Feature details From 55c4b820086e767dc5019b2393e25e7ad41df477 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Tue, 20 Mar 2018 16:13:21 -0700 Subject: [PATCH 054/433] Add missing operators in tokenizer, handle end-of-comment completion better (#1129) * Basic tokenizer * Fixed property names * Tests, round I * Tests, round II * tokenizer test * Remove temorary change * Fix merge issue * Merge conflict * Merge conflict * Completion test * Fix last line * Fix javascript math * Make test await for results * Add license headers * Rename definitions to types * License headers * Fix typo in completion details (typo) * Fix hover test * Russian translations * Update to better translation * Fix typo * #70 How to get all parameter info when filling in a function param list * Fix #70 How to get all parameter info when filling in a function param list * Clean up * Clean imports * CR feedback * Trim whitespace for test stability * More tests * Better handle no-parameters documentation * Better handle ellipsis and Python3 * #385 Auto-Indentation doesn't work after comment * #141 Auto indentation broken when return keyword involved * Undo changes * #627 Docstrings for builtin methods are not parsed correctly * reStructuredText converter * Fix: period is not an operator * Minor fixes * Restructure * Tests * Tests * Code heuristics * Baselines * HTML handling * Lists * State machine * Baselines * Squash * no message * Whitespace difference * Update Jedi to 0.11.1 * Enable Travis * Test fixes * Undo change * Jedi 0.11 with parser * Undo changes * Undo changes * Test fixes * More tests * Tests * Fix pylint search * Handle quote escapes in strings * Escapes in strings * CR feedback * Discover pylintrc better + tests * Fix .pyenv/versions search * Fix multiple linters output * Better handle markdown underscore * Test * Fix 916: PyLint checks wrong files * Test stability * Try increase timeout * Make sure linting is enabled in tests * Try another way of waiting * Simplify * Fix clear diags on close tests * Try writing settings directly * Increase timeout * Measure test time * Measure time * Simplify * Set timeout * Better venv detection * Add test * More reliable check * Fix pylint switch key * Remove incorrect flag * Disable print * Require pylint 1.8 on CI * Fix working directory for standalone files * Use an 'elif' * Separate file for pylint root config * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * Explicitly disable linter * New buttons * PR feedback * Revert "PR feedback" This reverts commit d0c7ab5da3de35a1febeed153db21531ee7c18b6. * PR feedback * Fix end of comment completion and operator handling * Simplify * Test typo fix --- .gitignore | 1 + src/client/extension.ts | 50 ++++++++++++----------- src/client/language/tokenizer.ts | 8 ++-- src/client/providers/completionSource.ts | 4 +- src/client/providers/providerUtilities.ts | 11 ++++- src/test/autocomplete/base.test.ts | 5 ++- src/test/language/tokenizer.test.ts | 4 +- tslint.json | 5 ++- 8 files changed, 51 insertions(+), 37 deletions(-) diff --git a/.gitignore b/.gitignore index 052afbc54035..cc941e968ccb 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ coverage/ .venv pythonFiles/experimental/ptvsd/** debug_coverage*/** +analysis/** diff --git a/src/client/extension.ts b/src/client/extension.ts index 4788030e98de..1a2eb6b7cb19 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -6,10 +6,12 @@ if ((Reflect as any).metadata === undefined) { require('reflect-metadata'); } import { Container } from 'inversify'; -import * as vscode from 'vscode'; -import { Disposable, Memento, OutputChannel, window } from 'vscode'; +import { + debug, Disposable, DocumentFilter, ExtensionContext, + extensions, IndentAction, languages, Memento, + OutputChannel, window +} from 'vscode'; import { PythonSettings } from './common/configSettings'; -import * as settings from './common/configSettings'; import { STANDARD_OUTPUT_CHANNEL } from './common/constants'; import { FeatureDeprecationManager } from './common/featureDeprecationManager'; import { createDeferred } from './common/helpers'; @@ -61,12 +63,12 @@ import * as tests from './unittests/main'; import { registerTypes as unitTestsRegisterTypes } from './unittests/serviceRegistry'; import { WorkspaceSymbols } from './workspaceSymbols/main'; -const PYTHON: vscode.DocumentFilter = { language: 'python' }; +const PYTHON: DocumentFilter = { language: 'python' }; const activationDeferred = createDeferred(); export const activated = activationDeferred.promise; // tslint:disable-next-line:max-func-body-length -export async function activate(context: vscode.ExtensionContext) { +export async function activate(context: ExtensionContext) { const cont = new Container(); const serviceManager = new ServiceManager(cont); const serviceContainer = new ServiceContainer(cont); @@ -95,7 +97,7 @@ export async function activate(context: vscode.ExtensionContext) { serviceManager.get(ICodeExecutionManager).registerCommands(); const persistentStateFactory = serviceManager.get(IPersistentStateFactory); - const pythonSettings = settings.PythonSettings.getInstance(); + const pythonSettings = PythonSettings.getInstance(); // tslint:disable-next-line:no-floating-promises sendStartupTelemetry(activated, serviceContainer); @@ -125,60 +127,60 @@ export async function activate(context: vscode.ExtensionContext) { // Enable indentAction // tslint:disable-next-line:no-non-null-assertion - vscode.languages.setLanguageConfiguration(PYTHON.language!, { + languages.setLanguageConfiguration(PYTHON.language!, { onEnterRules: [ { beforeText: /^\s*(?:def|class|for|if|elif|else|while|try|with|finally|except|async)\b.*/, - action: { indentAction: vscode.IndentAction.Indent } + action: { indentAction: IndentAction.Indent } }, { beforeText: /^\s*#.*/, afterText: /.+$/, - action: { indentAction: vscode.IndentAction.None, appendText: '# ' } + action: { indentAction: IndentAction.None, appendText: '# ' } }, { beforeText: /^\s+(continue|break|return)\b.*/, afterText: /\s+$/, - action: { indentAction: vscode.IndentAction.Outdent } + action: { indentAction: IndentAction.Outdent } } ] }); context.subscriptions.push(jediFactory); - context.subscriptions.push(vscode.languages.registerRenameProvider(PYTHON, new PythonRenameProvider(serviceContainer))); + context.subscriptions.push(languages.registerRenameProvider(PYTHON, new PythonRenameProvider(serviceContainer))); const definitionProvider = new PythonDefinitionProvider(jediFactory); - context.subscriptions.push(vscode.languages.registerDefinitionProvider(PYTHON, definitionProvider)); - context.subscriptions.push(vscode.languages.registerHoverProvider(PYTHON, new PythonHoverProvider(jediFactory))); - context.subscriptions.push(vscode.languages.registerReferenceProvider(PYTHON, new PythonReferenceProvider(jediFactory))); - context.subscriptions.push(vscode.languages.registerCompletionItemProvider(PYTHON, new PythonCompletionItemProvider(jediFactory, serviceContainer), '.')); - context.subscriptions.push(vscode.languages.registerCodeLensProvider(PYTHON, serviceContainer.get(IShebangCodeLensProvider))); + context.subscriptions.push(languages.registerDefinitionProvider(PYTHON, definitionProvider)); + context.subscriptions.push(languages.registerHoverProvider(PYTHON, new PythonHoverProvider(jediFactory))); + context.subscriptions.push(languages.registerReferenceProvider(PYTHON, new PythonReferenceProvider(jediFactory))); + context.subscriptions.push(languages.registerCompletionItemProvider(PYTHON, new PythonCompletionItemProvider(jediFactory, serviceContainer), '.')); + context.subscriptions.push(languages.registerCodeLensProvider(PYTHON, serviceContainer.get(IShebangCodeLensProvider))); const symbolProvider = new PythonSymbolProvider(jediFactory); - context.subscriptions.push(vscode.languages.registerDocumentSymbolProvider(PYTHON, symbolProvider)); + context.subscriptions.push(languages.registerDocumentSymbolProvider(PYTHON, symbolProvider)); if (pythonSettings.devOptions.indexOf('DISABLE_SIGNATURE') === -1) { - context.subscriptions.push(vscode.languages.registerSignatureHelpProvider(PYTHON, new PythonSignatureProvider(jediFactory), '(', ',')); + context.subscriptions.push(languages.registerSignatureHelpProvider(PYTHON, new PythonSignatureProvider(jediFactory), '(', ',')); } if (pythonSettings.formatting.provider !== 'none') { const formatProvider = new PythonFormattingEditProvider(context, serviceContainer); - context.subscriptions.push(vscode.languages.registerDocumentFormattingEditProvider(PYTHON, formatProvider)); - context.subscriptions.push(vscode.languages.registerDocumentRangeFormattingEditProvider(PYTHON, formatProvider)); + context.subscriptions.push(languages.registerDocumentFormattingEditProvider(PYTHON, formatProvider)); + context.subscriptions.push(languages.registerDocumentRangeFormattingEditProvider(PYTHON, formatProvider)); } const linterProvider = new LinterProvider(context, serviceContainer); context.subscriptions.push(linterProvider); - const jupyterExtension = vscode.extensions.getExtension('donjayamanne.jupyter'); + const jupyterExtension = extensions.getExtension('donjayamanne.jupyter'); const lintingEngine = serviceContainer.get(ILintingEngine); lintingEngine.linkJupiterExtension(jupyterExtension).ignoreErrors(); tests.activate(context, unitTestOutChannel, symbolProvider, serviceContainer); context.subscriptions.push(new WorkspaceSymbols(serviceContainer)); - context.subscriptions.push(vscode.languages.registerOnTypeFormattingEditProvider(PYTHON, new BlockFormatProviders(), ':')); - context.subscriptions.push(vscode.languages.registerOnTypeFormattingEditProvider(PYTHON, new OnEnterFormatter(), '\n')); + context.subscriptions.push(languages.registerOnTypeFormattingEditProvider(PYTHON, new BlockFormatProviders(), ':')); + context.subscriptions.push(languages.registerOnTypeFormattingEditProvider(PYTHON, new OnEnterFormatter(), '\n')); serviceContainer.getAll(IDebugConfigurationProvider).forEach(debugConfig => { - context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider(debugConfig.debugType, debugConfig)); + context.subscriptions.push(debug.registerDebugConfigurationProvider(debugConfig.debugType, debugConfig)); }); activationDeferred.resolve(); diff --git a/src/client/language/tokenizer.ts b/src/client/language/tokenizer.ts index ecd382d96541..c481c4201ac0 100644 --- a/src/client/language/tokenizer.ts +++ b/src/client/language/tokenizer.ts @@ -34,10 +34,10 @@ export class Tokenizer implements ITokenizer { // 'not', 'or', 'pass', 'print', 'raise', 'return', 'True', 'try', // 'while', 'with', 'yield' // ]; - private cs: ICharacterStream; + private cs: ICharacterStream = new CharacterStream(''); private tokens: IToken[] = []; private floatRegex = /[-+]?(?:(?:\d*\.\d+)|(?:\d+\.?))(?:[Ee][+-]?\d+)?/; - private mode: TokenizerMode; + private mode = TokenizerMode.Full; constructor() { //this.floatRegex.compile(); @@ -287,7 +287,7 @@ export class Tokenizer implements ITokenizer { } else if (nextChar === Char.Less) { length = this.cs.lookAhead(2) === Char.Equal ? 3 : 2; } else { - length = 1; + length = nextChar === Char.Equal ? 2 : 1; } break; @@ -295,7 +295,7 @@ export class Tokenizer implements ITokenizer { if (nextChar === Char.Greater) { length = this.cs.lookAhead(2) === Char.Equal ? 3 : 2; } else { - length = 1; + length = nextChar === Char.Equal ? 2 : 1; } break; diff --git a/src/client/providers/completionSource.ts b/src/client/providers/completionSource.ts index 3496f38a6f99..5a2064c9338c 100644 --- a/src/client/providers/completionSource.ts +++ b/src/client/providers/completionSource.ts @@ -61,7 +61,7 @@ export class CompletionSource { const sourceText = `${document.getText(leadingRange)}${itemString}`; const range = new vscode.Range(leadingRange.end, leadingRange.end.translate(0, itemString.length)); - return await this.itemInfoSource.getItemInfoFromText(document.uri, document.fileName, range, sourceText, token); + return this.itemInfoSource.getItemInfoFromText(document.uri, document.fileName, range, sourceText, token); } private async getCompletionResult(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken) @@ -90,7 +90,7 @@ export class CompletionSource { source: source }; - return await this.jediFactory.getJediProxyHandler(document.uri).sendCommand(cmd, token); + return this.jediFactory.getJediProxyHandler(document.uri).sendCommand(cmd, token); } private toVsCodeCompletions(documentPosition: DocumentPosition, data: proxy.ICompletionResult, resource: vscode.Uri): vscode.CompletionItem[] { diff --git a/src/client/providers/providerUtilities.ts b/src/client/providers/providerUtilities.ts index 4b113c61eceb..0a4f8274144d 100644 --- a/src/client/providers/providerUtilities.ts +++ b/src/client/providers/providerUtilities.ts @@ -13,10 +13,19 @@ export function getDocumentTokens(document: vscode.TextDocument, tokenizeTo: vsc export function isPositionInsideStringOrComment(document: vscode.TextDocument, position: vscode.Position): boolean { const tokenizeTo = position.translate(1, 0); const tokens = getDocumentTokens(document, tokenizeTo, TokenizerMode.CommentsAndStrings); - const index = tokens.getItemContaining(document.offsetAt(position)); + const offset = document.offsetAt(position); + let index = tokens.getItemContaining(offset); if (index >= 0) { const token = tokens.getItemAt(index); return token.type === TokenType.String || token.type === TokenType.Comment; } + if (offset > 0) { + // In case position is at the every end of the comment or unterminated string + index = tokens.getItemContaining(offset - 1); + if (index >= 0) { + const token = tokens.getItemAt(index); + return token.end === offset && token.type === TokenType.Comment; + } + } return false; } diff --git a/src/test/autocomplete/base.test.ts b/src/test/autocomplete/base.test.ts index bf07ac4fd783..4c4b8fd65992 100644 --- a/src/test/autocomplete/base.test.ts +++ b/src/test/autocomplete/base.test.ts @@ -195,10 +195,11 @@ suite('Autocomplete', () => { new vscode.Position(3, 0), // false new vscode.Position(4, 2), // false new vscode.Position(4, 8), // false - new vscode.Position(5, 4) // false + new vscode.Position(5, 4), // false + new vscode.Position(5, 10) // false ]; const expected = [ - false, true, false, false, false, false, false, false, false, false + false, true, false, false, false, false, false, false, false, false, false ]; const textDocument = await vscode.workspace.openTextDocument(fileSuppress); await vscode.window.showTextDocument(textDocument); diff --git a/src/test/language/tokenizer.test.ts b/src/test/language/tokenizer.test.ts index e2a5b3f6defb..1d2bf15d2b7b 100644 --- a/src/test/language/tokenizer.test.ts +++ b/src/test/language/tokenizer.test.ts @@ -180,7 +180,7 @@ suite('Language.Tokenizer', () => { }); test('Operators', async () => { const text = '< <> << <<= ' + - '== != > >> >>= ' + + '== != > >> >>= >= <=' + '+ -' + '* ** / /= //=' + '*= += -= **= ' + @@ -188,7 +188,7 @@ suite('Language.Tokenizer', () => { const tokens = new Tokenizer().tokenize(text); const lengths = [ 1, 2, 2, 3, - 2, 2, 1, 2, 3, + 2, 2, 1, 2, 3, 2, 2, 1, 1, 1, 2, 1, 2, 3, 2, 2, 2, 3, diff --git a/tslint.json b/tslint.json index 2746486ce48a..600e28f64075 100644 --- a/tslint.json +++ b/tslint.json @@ -59,6 +59,7 @@ ], "no-unnecessary-type-assertion": false, "no-submodule-imports": false, - "no-redundant-jsdoc": false + "no-redundant-jsdoc": false, + "binary-expression-operand-order": false } -} +} \ No newline at end of file From 30ec6ace21d15443e007e267605c0264ad9119b3 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 21 Mar 2018 10:09:18 -0700 Subject: [PATCH 055/433] Fixes issue of debugging unit tests hanging indefinitely (#1140) * :memo: add news entry * :hammer: rephrase news entry * Fixes #1009 --- news/2 Fixes/1009.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/2 Fixes/1009.md diff --git a/news/2 Fixes/1009.md b/news/2 Fixes/1009.md new file mode 100644 index 000000000000..665fdfc9e709 --- /dev/null +++ b/news/2 Fixes/1009.md @@ -0,0 +1 @@ +Fixes issue that causes debugging of unit tests to hang indefinitely. \ No newline at end of file From 9f9ac394aef90cbb1aebcd152197cd7df56bcd75 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 21 Mar 2018 10:09:41 -0700 Subject: [PATCH 056/433] Add news entry for #1096 and #1123 Fixes #1135 --- news/2 Fixes/1096.md | 1 + news/2 Fixes/1123.md | 1 + 2 files changed, 2 insertions(+) create mode 100644 news/2 Fixes/1096.md create mode 100644 news/2 Fixes/1123.md diff --git a/news/2 Fixes/1096.md b/news/2 Fixes/1096.md new file mode 100644 index 000000000000..d4a4500b38bc --- /dev/null +++ b/news/2 Fixes/1096.md @@ -0,0 +1 @@ +Fixes auto formatting of conditional statements containing expressions with `<=` symbols. diff --git a/news/2 Fixes/1123.md b/news/2 Fixes/1123.md new file mode 100644 index 000000000000..1a23a845690b --- /dev/null +++ b/news/2 Fixes/1123.md @@ -0,0 +1 @@ +Disables auto completion when editing text at the end of a comment string. From 58e25e1eb1209409e99409adca9658ebbe05f114 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 21 Mar 2018 10:44:09 -0700 Subject: [PATCH 057/433] When using pipenv, install packages (such as linters, test frameworks) in dev-packages Fixes #1110 --- news/1 Enhancements/1110.md | 1 + src/client/common/installer/pipEnvInstaller.ts | 2 +- src/test/common/moduleInstaller.test.ts | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 news/1 Enhancements/1110.md diff --git a/news/1 Enhancements/1110.md b/news/1 Enhancements/1110.md new file mode 100644 index 000000000000..95572f4644ec --- /dev/null +++ b/news/1 Enhancements/1110.md @@ -0,0 +1 @@ +When using pipenv, install packages (such as linters, test frameworks) in dev-packages. diff --git a/src/client/common/installer/pipEnvInstaller.ts b/src/client/common/installer/pipEnvInstaller.ts index cbde0ea3335d..23ac3e52ab95 100644 --- a/src/client/common/installer/pipEnvInstaller.ts +++ b/src/client/common/installer/pipEnvInstaller.ts @@ -27,7 +27,7 @@ export class PipEnvInstaller implements IModuleInstaller { public installModule(name: string): Promise { const terminalService = this.serviceContainer.get(ITerminalServiceFactory).getTerminalService(); - return terminalService.sendCommand(pipenvName, ['install', name]); + return terminalService.sendCommand(pipenvName, ['install', name, '--dev']); } public async isSupported(resource?: Uri): Promise { diff --git a/src/test/common/moduleInstaller.test.ts b/src/test/common/moduleInstaller.test.ts index 42e83eb93524..b1b4d5c52fcb 100644 --- a/src/test/common/moduleInstaller.test.ts +++ b/src/test/common/moduleInstaller.test.ts @@ -200,7 +200,7 @@ suite('Module Installer', () => { let argsSent: string[] = []; mockTerminalService - .setup(async t => await t.sendCommand(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())) + .setup(t => t.sendCommand(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())) .returns((cmd: string, args: string[]) => { argsSent = args; return Promise.resolve(void 0); }); await pipInstaller.installModule(moduleName); @@ -255,6 +255,6 @@ suite('Module Installer', () => { await pipInstaller.installModule(moduleName); expect(command!).equal('pipenv', 'Invalid command sent to terminal for installation.'); - expect(argsSent.join(' ')).equal(`install ${moduleName}`, 'Invalid command arguments sent to terminal for installation.'); + expect(argsSent.join(' ')).equal(`install ${moduleName} --dev`, 'Invalid command arguments sent to terminal for installation.'); }); }); From b9860b284f85971521f88a3c2b5357bc7f320692 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 22 Mar 2018 11:27:09 -0700 Subject: [PATCH 058/433] Mention that people should thank themselves --- .github/{pull_request_template.md => PULL_REQUEST_TEMPLATE.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/{pull_request_template.md => PULL_REQUEST_TEMPLATE.md} (89%) diff --git a/.github/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE.md similarity index 89% rename from .github/pull_request_template.md rename to .github/PULL_REQUEST_TEMPLATE.md index c5f76ff975c1..8552f75f33e9 100644 --- a/.github/pull_request_template.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,7 +2,7 @@ Fixes # This pull request: - [ ] Has a title summarizes what is changing -- [ ] Includes a [news entry](https://github.com/Microsoft/vscode-python/tree/master/news) file +- [ ] Includes a [news entry](https://github.com/Microsoft/vscode-python/tree/master/news) file (remember to thank yourself!) - [ ] Has unit tests & [code coverage](https://codecov.io/gh/Microsoft/vscode-python) is not adversely affected (within reason) - [ ] Works on all [actively maintained versions of Python](https://devguide.python.org/#status-of-python-branches) (e.g. Python 2.7 & the latest Python 3 release) - [ ] Works on Windows 10, macOS, and Linux (e.g. considered file system case-sensitivity) From 76b32cfaa879ba024ccf61732eeb80aef7c096ab Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 22 Mar 2018 13:38:56 -0700 Subject: [PATCH 059/433] Make sure contributors thanks themselves --- news/README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/news/README.md b/news/README.md index 8b8350f5ddec..d4e7d7de26a1 100644 --- a/news/README.md +++ b/news/README.md @@ -3,16 +3,18 @@ Our changelog is automatically generate from individual news entries. This alleviates the burden of having to go back and try to figure out what changed in a release. It also helps tie pull requests back to the -issue(s) it addresses. +issue(s) it addresses. Finally, it avoids merge conflicts if multiple +pull requests try to edit the changelog. ## Entries -Each news entries is represented by a Markdown file that contains the +Each news entry is represented by a Markdown file that contains the relevant details of what changed. The file name of the news entry is -the issue that corresponds to the change along with an option nonce in +the issue that corresponds to the change along with an optional nonce in case a single issue corresponds to multiple changes. The directory the news entry is saved in specifies what section of the changelog the -change corresponds to. +change corresponds to. External contributors should also make sure to +thank themselves for taking the time and effort to contribute. As an example, a change corresponding to a bug reported in issue #42 would be saved in the `1 Fixes` directory and named `42.md` @@ -22,6 +24,7 @@ regarding issue #42) and could contain the following: ```markdown [Answer](https://en.wikipedia.org/wiki/42_(number)) to the Ultimate Question of Life, the Universe, and Everything! +(thanks [Don Jaymanne](https://github.com/donjayamanne/)) ``` This would then be made into an entry in the changelog that was in the From 34d15d3e6ca13765cfbe960142457140e6d1beab Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 22 Mar 2018 15:30:42 -0700 Subject: [PATCH 060/433] Improve compilation speed of TypeScript code. (#1154) * :sparkles: faster compilation for windows * merged master * :memo: change log * Fixes #1146 --- gulpfile.js | 86 ++++++++++++++++++++++++++++++-------- news/3 Code Health/1146.md | 1 + 2 files changed, 69 insertions(+), 18 deletions(-) create mode 100644 news/3 Code Health/1146.md diff --git a/gulpfile.js b/gulpfile.js index 582908f4468d..a2abf4e3c86d 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -24,6 +24,7 @@ const fs = require('fs'); const remapIstanbul = require('remap-istanbul'); const istanbul = require('istanbul'); const glob = require('glob'); +const _ = require('lodash'); /** * Hygiene works by creating cascading subsets of all our files and @@ -41,7 +42,6 @@ const all = [ const tsFilter = [ 'src/**/*.ts', - 'src/client/**/*.ts', ]; const indentationFilter = [ @@ -60,7 +60,7 @@ const tslintFilter = [ '!resources/**/*', '!snippets/**/*', '!syntaxes/**/*', - '!**/typings/**/*', + '!**/typings/**/*' ]; const copyrightHeader = [ @@ -78,7 +78,7 @@ gulp.task('watch', ['hygiene-modified', 'hygiene-watch']); gulp.task('debugger-coverage', () => buildDebugAdapterCoverage()); -gulp.task('hygiene-watch', () => gulp.watch(tsFilter, debounce(() => run({ mode: 'changes' }), 1000))); +gulp.task('hygiene-watch', () => gulp.watch(tsFilter, debounce(() => run({ mode: 'changes', skipFormatCheck: true, skipIndentationCheck: true, skipCopyrightCheck: true }), 100))); gulp.task('hygiene-all', () => run({ mode: 'all' })); @@ -131,20 +131,52 @@ function buildDebugAdapterCoverage() { * @property {'changes'|'staged'|'all'|'compile'} [mode=] - Mode. * @property {boolean=} skipIndentationCheck - Skip indentation checks. * @property {boolean=} skipFormatCheck - Skip format checks. +* @property {boolean=} skipCopyrightCheck - Skip copyright checks. * @property {boolean=} skipLinter - Skip linter. */ +const tsProjectMap = {}; +/** + * + * @param {hygieneOptions} options + */ +function getTsProject(options) { + const tsOptions = options.mode === 'compile' ? undefined : { strict: true, noImplicitAny: false, noImplicitThis: false }; + const mode = tsOptions && tsOptions.mode ? tsOptions.mode : ''; + return tsProjectMap[mode] ? tsProjectMap[mode] : tsProjectMap[mode] = ts.createProject('tsconfig.json', tsOptions); +} + +let configuration; +let program; +let linter; +/** + * + * @param {hygieneOptions} options + */ +function getLinter(options) { + configuration = configuration ? configuration : tslint.Configuration.findConfiguration(null, '.'); + program = program ? program : tslint.Linter.createProgram('./tsconfig.json'); + linter = linter ? linter : new tslint.Linter({ formatter: 'json' }, program); + return { linter, configuration }; +} +let compilationInProgress = false; +let reRunCompilation = false; /** * * @param {hygieneOptions} options * @returns {NodeJS.ReadWriteStream} */ const hygiene = (options) => { + if (compilationInProgress) { + reRunCompilation = true; + return; + } + const started = new Date().getTime(); + compilationInProgress = true; options = options || {}; let errorCount = 0; - const addedFiles = getAddedFilesSync(); + const addedFiles = options.skipCopyrightCheck ? [] : getAddedFilesSync(); console.log(colors.blue('Hygiene started.')); - const copyrights = es.through(function (file) { if (addedFiles.indexOf(file.path) !== -1 && file.contents.toString('utf8').indexOf(copyrightHeader) !== 0) { // Use tslint format. @@ -228,15 +260,16 @@ const hygiene = (options) => { .filter(reported => reported === true) .length > 0; } - const configuration = tslint.Configuration.findConfiguration(null, '.'); - const program = tslint.Linter.createProgram('./tsconfig.json'); - const linter = new tslint.Linter({ formatter: 'json' }, program); + + const { linter, configuration } = getLinter(options); const tsl = es.through(function (file) { const contents = file.contents.toString('utf8'); // Don't print anything to the console, we'll do that. // Yes this is a hack, but tslinter doesn't provide an option to prevent this. const oldWarn = console.warn; console.warn = () => { }; + linter.failures = []; + linter.fixes = []; linter.lint(file.relative, contents, configuration.results); console.warn = oldWarn; const result = linter.getResult(); @@ -259,8 +292,7 @@ const hygiene = (options) => { this.emit('data', file); }); - const tsOptions = options.mode === 'compile' ? undefined : { strict: true, noImplicitAny: false, noImplicitThis: false }; - const tsProject = ts.createProject('tsconfig.json', tsOptions); + const tsProject = getTsProject(options); const tsc = function () { function customReporter() { @@ -294,8 +326,11 @@ const hygiene = (options) => { } result = result - .pipe(filter(tslintFilter)) - .pipe(copyrights); + .pipe(filter(tslintFilter)); + + if (!options.skipCopyrightCheck) { + result = result.pipe(copyrights); + } if (!options.skipFormatCheck) { // result = result @@ -306,7 +341,7 @@ const hygiene = (options) => { result = result .pipe(tsl); } - + let totalTime = 0; result = result .pipe(tscFilesTracker) .pipe(sourcemaps.init()) @@ -323,15 +358,22 @@ const hygiene = (options) => { .pipe(gulp.dest(dest)) .pipe(es.through(null, function () { if (errorCount > 0) { - const errorMessage = `Hygiene failed with errors 👎 . Check 'gulpfile.js'.`; + const errorMessage = `Hygiene failed with errors 👎 . Check 'gulpfile.js' (completed in ${new Date().getTime() - started}ms).`; console.error(colors.red(errorMessage)); exitHandler(options); } else { - console.log(colors.green('Hygiene passed with 0 errors 👍.')); + console.log(colors.green(`Hygiene passed with 0 errors 👍 (completed in ${new Date().getTime() - started}ms).`)); } // Reset error counter. errorCount = 0; reportedLinterFailures = []; + compilationInProgress = false; + if (reRunCompilation) { + reRunCompilation = false; + setTimeout(() => { + hygiene(options); + }, 10); + } this.emit('end'); })) .on('error', exitHandler.bind(this, options)); @@ -346,6 +388,7 @@ const hygiene = (options) => { * @property {string[]=} files - Optional list of files to be modified. * @property {boolean=} skipIndentationCheck - Skip indentation checks. * @property {boolean=} skipFormatCheck - Skip format checks. +* @property {boolean=} skipCopyrightCheck - Skip copyright checks. * @property {boolean=} skipLinter - Skip linter. * @property {boolean=} watch - Watch mode. */ @@ -391,7 +434,15 @@ function getAddedFilesSync() { return out .split(/\r?\n/) .filter(l => !!l) - .filter(l => l.startsWith('A') || l.startsWith('??')) + .filter(l => _.intersection(['A', '?'], l.substring(0, 2).trim().split()).length > 0) + .map(l => path.join(__dirname, l.substring(2).trim())); +} +function getModifiedFilesSync() { + const out = cp.execSync('git status -u -s', { encoding: 'utf8' }); + return out + .split(/\r?\n/) + .filter(l => !!l) + .filter(l => _.intersection(['M', 'A', 'R', 'C'], l.substring(0, 2).trim().split()).length > 0) .map(l => path.join(__dirname, l.substring(2).trim())); } @@ -404,8 +455,7 @@ function getFilesToProcess(options) { // If we need only modified files, then filter the glob. if (options && options.mode === 'changes') { - return gulp.src(all, gulpSrcOptions) - .pipe(gitmodified(['M', 'A', 'AM', 'D', 'R', 'C', 'U', '??'])); + return gulp.src(getModifiedFilesSync(), gulpSrcOptions); } if (options && options.mode === 'staged') { diff --git a/news/3 Code Health/1146.md b/news/3 Code Health/1146.md new file mode 100644 index 000000000000..d6e8ac464a69 --- /dev/null +++ b/news/3 Code Health/1146.md @@ -0,0 +1 @@ +Improve compilation speed of TypeScript code. \ No newline at end of file From e66efa7e4281693b03a54d32720e878f6039f805 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 22 Mar 2018 15:33:30 -0700 Subject: [PATCH 061/433] Disable the display of errors messages when rediscovering of tests fail in response to changes to files (#1133) * do not display discovery errors when auto rediscovering tests * :memo: news entry * code review changes [skip ci] * Fixes #704 --- news/1 Enhancements/704.md | 1 + src/client/unittests/display/main.ts | 12 ++++++------ src/client/unittests/main.ts | 19 ++++++++++--------- 3 files changed, 17 insertions(+), 15 deletions(-) create mode 100644 news/1 Enhancements/704.md diff --git a/news/1 Enhancements/704.md b/news/1 Enhancements/704.md new file mode 100644 index 000000000000..49f1c4d9a3b8 --- /dev/null +++ b/news/1 Enhancements/704.md @@ -0,0 +1 @@ +Disable the display of errors messages when rediscovering of tests fail in response to changes to files, e.g. don't show a message if there's a syntax error in the test code. diff --git a/src/client/unittests/display/main.ts b/src/client/unittests/display/main.ts index b6f03f11c62f..aec0139ac2cb 100644 --- a/src/client/unittests/display/main.ts +++ b/src/client/unittests/display/main.ts @@ -11,9 +11,9 @@ export class TestResultDisplay { private discoverCounter = 0; private ticker = ['|', '/', '-', '|', '/', '-', '\\']; private progressTimeout; - private progressPrefix: string; + private progressPrefix!: string; // tslint:disable-next-line:no-any - constructor(private outputChannel: vscode.OutputChannel, private onDidChange?: vscode.EventEmitter) { + constructor(private onDidChange?: vscode.EventEmitter) { this.statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); } public dispose() { @@ -35,10 +35,10 @@ export class TestResultDisplay { // tslint:disable-next-line:no-empty .catch(() => { }); } - public displayDiscoverStatus(testDiscovery: Promise) { + public displayDiscoverStatus(testDiscovery: Promise, quietMode: boolean = false) { this.displayProgress('Discovering Tests', 'Discovering Tests (Click to Stop)', constants.Commands.Tests_Ask_To_Stop_Discovery); return testDiscovery.then(tests => { - this.updateWithDiscoverSuccess(tests); + this.updateWithDiscoverSuccess(tests, quietMode); return tests; }).catch(reason => { this.updateWithDiscoverFailure(reason); @@ -143,7 +143,7 @@ export class TestResultDisplay { return def.promise; } - private updateWithDiscoverSuccess(tests: Tests) { + private updateWithDiscoverSuccess(tests: Tests, quietMode: boolean = false) { this.clearProgressTicker(); const haveTests = tests && (tests.testFunctions.length > 0); this.statusBar.text = '$(zap) Run Tests'; @@ -154,7 +154,7 @@ export class TestResultDisplay { this.onDidChange.fire(); } - if (!haveTests) { + if (!haveTests && !quietMode) { vscode.window.showInformationMessage('No tests discovered, please check the configuration settings for the tests.', 'Disable Tests').then(item => { if (item === 'Disable Tests') { this.disableTests() diff --git a/src/client/unittests/main.ts b/src/client/unittests/main.ts index 2e386edea0b0..71bb3c2339b2 100644 --- a/src/client/unittests/main.ts +++ b/src/client/unittests/main.ts @@ -1,5 +1,6 @@ 'use strict'; import * as vscode from 'vscode'; +// tslint:disable-next-line:no-duplicate-imports import { Disposable, Uri, window, workspace } from 'vscode'; import { PythonSettings } from '../common/configSettings'; import * as constants from '../common/constants'; @@ -77,7 +78,7 @@ async function onDocumentSaved(doc: vscode.TextDocument): Promise { if (timeoutId) { clearTimeout(timeoutId); } - timeoutId = setTimeout(() => discoverTests(CommandSource.auto, doc.uri, true), 1000); + timeoutId = setTimeout(() => discoverTests(CommandSource.auto, doc.uri, true, false, true), 1000); } function dispose() { @@ -157,7 +158,7 @@ async function selectAndRunTestMethod(cmdSource: CommandSource, resource: Uri, d if (!selectedTestFn) { return; } - // tslint:disable-next-line:prefer-type-cast + // tslint:disable-next-line:prefer-type-cast no-object-literal-type-assertion await runTestsImpl(cmdSource, testManager.workspaceFolder, { testFunction: [selectedTestFn.testFunction] } as TestsToRun, false, debug); } async function selectAndRunTestFile(cmdSource: CommandSource) { @@ -177,7 +178,7 @@ async function selectAndRunTestFile(cmdSource: CommandSource) { if (!selectedFile) { return; } - // tslint:disable-next-line:prefer-type-cast + // tslint:disable-next-line:prefer-type-cast no-object-literal-type-assertion await runTestsImpl(cmdSource, testManager.workspaceFolder, { testFile: [selectedFile] } as TestsToRun); } async function runCurrentTestFile(cmdSource: CommandSource) { @@ -200,7 +201,7 @@ async function runCurrentTestFile(cmdSource: CommandSource) { if (testFiles.length < 1) { return; } - // tslint:disable-next-line:prefer-type-cast + // tslint:disable-next-line:prefer-type-cast no-object-literal-type-assertion await runTestsImpl(cmdSource, testManager.workspaceFolder, { testFile: [testFiles[0]] } as TestsToRun); } async function displayStopUI(message: string) { @@ -272,16 +273,16 @@ async function stopTests(resource: Uri) { testManager.stop(); } } -async function discoverTests(cmdSource: CommandSource, resource?: Uri, ignoreCache?: boolean, userInitiated?: boolean) { +async function discoverTests(cmdSource: CommandSource, resource?: Uri, ignoreCache?: boolean, userInitiated?: boolean, quietMode?: boolean) { const testManager = await getTestManager(true, resource); if (!testManager) { return; } if (testManager && (testManager.status !== TestStatus.Discovering && testManager.status !== TestStatus.Running)) { - testResultDisplay = testResultDisplay ? testResultDisplay : new TestResultDisplay(outChannel, onDidChange); - const discoveryPromise = testManager.discoverTests(cmdSource, ignoreCache, false, userInitiated); - testResultDisplay.displayDiscoverStatus(discoveryPromise) + testResultDisplay = testResultDisplay ? testResultDisplay : new TestResultDisplay(onDidChange); + const discoveryPromise = testManager.discoverTests(cmdSource, ignoreCache, quietMode, userInitiated); + testResultDisplay.displayDiscoverStatus(discoveryPromise, quietMode) .catch(ex => console.error('Python Extension: displayDiscoverStatus', ex)); await discoveryPromise; } @@ -292,7 +293,7 @@ async function runTestsImpl(cmdSource: CommandSource, resource?: Uri, testsToRun return; } - testResultDisplay = testResultDisplay ? testResultDisplay : new TestResultDisplay(outChannel, onDidChange); + testResultDisplay = testResultDisplay ? testResultDisplay : new TestResultDisplay(onDidChange); const promise = testManager.runTest(cmdSource, testsToRun, runFailedTests, debug) .catch(reason => { if (reason !== CANCELLATION_REASON) { From f14102f64cea364baf1f8a942b70af7e43036eba Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 22 Mar 2018 16:03:59 -0700 Subject: [PATCH 062/433] Ensures file paths are properly encoded when passing them as arguments to linters. (#1155) * :bug: format file names when passing as command line args * :bug: fix header check * :white_check_mark: tests * :memo: news entry * Fixes #199 --- gulpfile.js | 15 ++- news/2 Fixes/199.md | 1 + src/client/linters/flake8.ts | 6 +- src/client/linters/mypy.ts | 6 +- src/client/linters/pep8.ts | 6 +- src/client/linters/prospector.ts | 6 +- src/client/linters/pydocstyle.ts | 7 +- src/client/linters/pylama.ts | 6 +- src/client/linters/pylint.ts | 6 +- src/test/linters/lint.args.test.ts | 155 +++++++++++++++++++++++++++++ 10 files changed, 188 insertions(+), 26 deletions(-) create mode 100644 news/2 Fixes/199.md create mode 100644 src/test/linters/lint.args.test.ts diff --git a/gulpfile.js b/gulpfile.js index a2abf4e3c86d..6a8cbadb7e77 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -24,6 +24,7 @@ const fs = require('fs'); const remapIstanbul = require('remap-istanbul'); const istanbul = require('istanbul'); const glob = require('glob'); +const os = require('os'); const _ = require('lodash'); /** @@ -68,7 +69,8 @@ const copyrightHeader = [ '// Licensed under the MIT License.', '', '\'use strict\';' -].join('\n'); +]; +const copyrightHeaders = [copyrightHeader.join('\n'), copyrightHeader.join('\r\n')]; gulp.task('hygiene', () => run({ mode: 'all', skipFormatCheck: true, skipIndentationCheck: true })); @@ -178,10 +180,13 @@ const hygiene = (options) => { const addedFiles = options.skipCopyrightCheck ? [] : getAddedFilesSync(); console.log(colors.blue('Hygiene started.')); const copyrights = es.through(function (file) { - if (addedFiles.indexOf(file.path) !== -1 && file.contents.toString('utf8').indexOf(copyrightHeader) !== 0) { - // Use tslint format. - console.error(`ERROR: (copyright) ${file.relative}[1,1]: Missing or bad copyright statement`); - errorCount++; + if (addedFiles.indexOf(file.path) !== -1) { + const contents = file.contents.toString('utf8'); + if (!copyrightHeaders.some(header => contents.indexOf(header) === 0)) { + // Use tslint format. + console.error(`ERROR: (copyright) ${file.relative}[1,1]: Missing or bad copyright statement`); + errorCount++; + } } this.emit('data', file); diff --git a/news/2 Fixes/199.md b/news/2 Fixes/199.md new file mode 100644 index 000000000000..94f155e516b8 --- /dev/null +++ b/news/2 Fixes/199.md @@ -0,0 +1 @@ +Ensures file paths are properly encoded when passing them as arguments to linters. diff --git a/src/client/linters/flake8.ts b/src/client/linters/flake8.ts index efc00becf9a9..494174e15e5d 100644 --- a/src/client/linters/flake8.ts +++ b/src/client/linters/flake8.ts @@ -1,5 +1,5 @@ -import { OutputChannel } from 'vscode'; -import { CancellationToken, TextDocument } from 'vscode'; +import { CancellationToken, OutputChannel, TextDocument } from 'vscode'; +import '../common/extensions'; import { Product } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { BaseLinter } from './baseLinter'; @@ -13,7 +13,7 @@ export class Flake8 extends BaseLinter { } protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise { - const messages = await this.run(['--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', document.uri.fsPath], document, cancellation); + const messages = await this.run(['--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', document.uri.fsPath.fileToCommandArgument()], document, cancellation); messages.forEach(msg => { msg.severity = this.parseMessagesSeverity(msg.type, this.pythonSettings.linting.flake8CategorySeverity); }); diff --git a/src/client/linters/mypy.ts b/src/client/linters/mypy.ts index 15c8046a08d3..1064488700d5 100644 --- a/src/client/linters/mypy.ts +++ b/src/client/linters/mypy.ts @@ -1,5 +1,5 @@ -import { OutputChannel } from 'vscode'; -import { CancellationToken, TextDocument } from 'vscode'; +import { CancellationToken, OutputChannel, TextDocument } from 'vscode'; +import '../common/extensions'; import { Product } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { BaseLinter } from './baseLinter'; @@ -13,7 +13,7 @@ export class MyPy extends BaseLinter { } protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise { - const messages = await this.run([document.uri.fsPath], document, cancellation, REGEX); + const messages = await this.run([document.uri.fsPath.fileToCommandArgument()], document, cancellation, REGEX); messages.forEach(msg => { msg.severity = this.parseMessagesSeverity(msg.type, this.pythonSettings.linting.mypyCategorySeverity); msg.code = msg.type; diff --git a/src/client/linters/pep8.ts b/src/client/linters/pep8.ts index 24bcaa9abeb2..e13d6c91c2b0 100644 --- a/src/client/linters/pep8.ts +++ b/src/client/linters/pep8.ts @@ -1,5 +1,5 @@ -import { OutputChannel } from 'vscode'; -import { CancellationToken, TextDocument } from 'vscode'; +import { CancellationToken, OutputChannel, TextDocument } from 'vscode'; +import '../common/extensions'; import { Product } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { BaseLinter } from './baseLinter'; @@ -13,7 +13,7 @@ export class Pep8 extends BaseLinter { } protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise { - const messages = await this.run(['--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', document.uri.fsPath], document, cancellation); + const messages = await this.run(['--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', document.uri.fsPath.fileToCommandArgument()], document, cancellation); messages.forEach(msg => { msg.severity = this.parseMessagesSeverity(msg.type, this.pythonSettings.linting.pep8CategorySeverity); }); diff --git a/src/client/linters/prospector.ts b/src/client/linters/prospector.ts index 930fa91c458a..8bbef82c46a5 100644 --- a/src/client/linters/prospector.ts +++ b/src/client/linters/prospector.ts @@ -1,5 +1,5 @@ -import { OutputChannel } from 'vscode'; -import { CancellationToken, TextDocument } from 'vscode'; +import { CancellationToken, OutputChannel, TextDocument } from 'vscode'; +import '../common/extensions'; import { Product } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { BaseLinter } from './baseLinter'; @@ -28,7 +28,7 @@ export class Prospector extends BaseLinter { } protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise { - return await this.run(['--absolute-paths', '--output-format=json', document.uri.fsPath], document, cancellation); + return this.run(['--absolute-paths', '--output-format=json', document.uri.fsPath.fileToCommandArgument()], document, cancellation); } protected async parseMessages(output: string, document: TextDocument, token: CancellationToken, regEx: string) { let parsedData: IProspectorResponse; diff --git a/src/client/linters/pydocstyle.ts b/src/client/linters/pydocstyle.ts index f0b05bb16726..c22944f421d6 100644 --- a/src/client/linters/pydocstyle.ts +++ b/src/client/linters/pydocstyle.ts @@ -1,6 +1,6 @@ import * as path from 'path'; -import { OutputChannel } from 'vscode'; -import { CancellationToken, TextDocument } from 'vscode'; +import { CancellationToken, OutputChannel, TextDocument } from 'vscode'; +import '../common/extensions'; import { Product } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { IS_WINDOWS } from './../common/utils'; @@ -13,7 +13,7 @@ export class PyDocStyle extends BaseLinter { } protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise { - const messages = await this.run([document.uri.fsPath], document, cancellation); + const messages = await this.run([document.uri.fsPath.fileToCommandArgument()], document, cancellation); // All messages in pep8 are treated as warnings for now. messages.forEach(msg => { msg.severity = LintMessageSeverity.Warning; @@ -61,6 +61,7 @@ export class PyDocStyle extends BaseLinter { const trmmedSourceLine = sourceLine.trim(); const sourceStart = sourceLine.indexOf(trmmedSourceLine); + // tslint:disable-next-line:no-object-literal-type-assertion return { code: code, message: message, diff --git a/src/client/linters/pylama.ts b/src/client/linters/pylama.ts index ab29fd9c55ec..ef66bc5446c3 100644 --- a/src/client/linters/pylama.ts +++ b/src/client/linters/pylama.ts @@ -1,5 +1,5 @@ -import { OutputChannel } from 'vscode'; -import { CancellationToken, TextDocument } from 'vscode'; +import { CancellationToken, OutputChannel, TextDocument } from 'vscode'; +import '../common/extensions'; import { Product } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { BaseLinter } from './baseLinter'; @@ -14,7 +14,7 @@ export class PyLama extends BaseLinter { } protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise { - const messages = await this.run(['--format=parsable', document.uri.fsPath], document, cancellation, REGEX); + const messages = await this.run(['--format=parsable', document.uri.fsPath.fileToCommandArgument()], document, cancellation, REGEX); // All messages in pylama are treated as warnings for now. messages.forEach(msg => { msg.severity = LintMessageSeverity.Warning; diff --git a/src/client/linters/pylint.ts b/src/client/linters/pylint.ts index 4111c17f83bc..4998fe22a46a 100644 --- a/src/client/linters/pylint.ts +++ b/src/client/linters/pylint.ts @@ -4,8 +4,8 @@ import * as os from 'os'; import * as path from 'path'; -import { OutputChannel } from 'vscode'; -import { CancellationToken, TextDocument } from 'vscode'; +import { CancellationToken, OutputChannel, TextDocument } from 'vscode'; +import '../common/extensions'; import { IFileSystem, IPlatformService } from '../common/platform/types'; import { Product } from '../common/types'; import { IServiceContainer } from '../ioc/types'; @@ -70,7 +70,7 @@ export class Pylint extends BaseLinter { '--msg-template=\'{line},{column},{category},{msg_id}:{msg}\'', '--reports=n', '--output-format=text', - uri.fsPath + uri.fsPath.fileToCommandArgument() ]; const messages = await this.run(minArgs.concat(args), document, cancellation); messages.forEach(msg => { diff --git a/src/test/linters/lint.args.test.ts b/src/test/linters/lint.args.test.ts new file mode 100644 index 000000000000..259f87e38ef7 --- /dev/null +++ b/src/test/linters/lint.args.test.ts @@ -0,0 +1,155 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:no-any + +import { expect } from 'chai'; +import { Container } from 'inversify'; +import * as path from 'path'; +import * as TypeMoq from 'typemoq'; +import { CancellationTokenSource, OutputChannel, TextDocument, Uri } from 'vscode'; +import { IDocumentManager, IWorkspaceService } from '../../client/common/application/types'; +import '../../client/common/extensions'; +import { IFileSystem, IPlatformService } from '../../client/common/platform/types'; +import { IConfigurationService, IInstaller, ILintingSettings, ILogger, IOutputChannel, IPythonSettings } from '../../client/common/types'; +import { IInterpreterService } from '../../client/interpreter/contracts'; +import { ServiceContainer } from '../../client/ioc/container'; +import { ServiceManager } from '../../client/ioc/serviceManager'; +import { BaseLinter } from '../../client/linters/baseLinter'; +import { Flake8 } from '../../client/linters/flake8'; +import { LinterManager } from '../../client/linters/linterManager'; +import { MyPy } from '../../client/linters/mypy'; +import { Pep8 } from '../../client/linters/pep8'; +import { Prospector } from '../../client/linters/prospector'; +import { PyDocStyle } from '../../client/linters/pydocstyle'; +import { PyLama } from '../../client/linters/pylama'; +import { Pylint } from '../../client/linters/pylint'; +import { ILinterManager, ILintingEngine } from '../../client/linters/types'; +import { initialize } from '../initialize'; + +// tslint:disable-next-line:max-func-body-length +suite('Linting - Arguments', () => { + let interpreterService: TypeMoq.IMock; + let engine: TypeMoq.IMock; + let configService: TypeMoq.IMock; + let docManager: TypeMoq.IMock; + let settings: TypeMoq.IMock; + let lm: ILinterManager; + let serviceContainer: ServiceContainer; + let document: TypeMoq.IMock; + let outputChannel: TypeMoq.IMock; + let workspaceService: TypeMoq.IMock; + const cancellationToken = new CancellationTokenSource().token; + + suiteSetup(initialize); + setup(async () => { + const cont = new Container(); + const serviceManager = new ServiceManager(cont); + + serviceContainer = new ServiceContainer(cont); + outputChannel = TypeMoq.Mock.ofType(); + + const fs = TypeMoq.Mock.ofType(); + fs.setup(x => x.fileExistsAsync(TypeMoq.It.isAny())).returns(() => new Promise((resolve, reject) => resolve(true))); + fs.setup(x => x.arePathsSame(TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString())).returns(() => true); + serviceManager.addSingletonInstance(IFileSystem, fs.object); + + serviceManager.addSingletonInstance(IOutputChannel, outputChannel.object); + + interpreterService = TypeMoq.Mock.ofType(); + serviceManager.addSingletonInstance(IInterpreterService, interpreterService.object); + + engine = TypeMoq.Mock.ofType(); + serviceManager.addSingletonInstance(ILintingEngine, engine.object); + + docManager = TypeMoq.Mock.ofType(); + serviceManager.addSingletonInstance(IDocumentManager, docManager.object); + + const lintSettings = TypeMoq.Mock.ofType(); + lintSettings.setup(x => x.enabled).returns(() => true); + lintSettings.setup(x => x.lintOnSave).returns(() => true); + + settings = TypeMoq.Mock.ofType(); + settings.setup(x => x.linting).returns(() => lintSettings.object); + + configService = TypeMoq.Mock.ofType(); + configService.setup(x => x.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); + serviceManager.addSingletonInstance(IConfigurationService, configService.object); + + workspaceService = TypeMoq.Mock.ofType(); + serviceManager.addSingletonInstance(IWorkspaceService, workspaceService.object); + + const logger = TypeMoq.Mock.ofType(); + serviceManager.addSingletonInstance(ILogger, logger.object); + + const installer = TypeMoq.Mock.ofType(); + serviceManager.addSingletonInstance(IInstaller, installer.object); + + const platformService = TypeMoq.Mock.ofType(); + serviceManager.addSingletonInstance(IPlatformService, platformService.object); + + lm = new LinterManager(serviceContainer); + serviceManager.addSingletonInstance(ILinterManager, lm); + document = TypeMoq.Mock.ofType(); + }); + + async function testLinter(linter: BaseLinter, fileUri: Uri, expectedArgs: string[]) { + document.setup(d => d.uri).returns(() => fileUri); + + let invoked = false; + (linter as any).run = (args, doc, token) => { + expect(args).to.deep.equal(expectedArgs); + invoked = true; + return Promise.resolve([]); + }; + await linter.lint(document.object, cancellationToken); + expect(invoked).to.be.equal(true, 'method not invoked'); + } + [Uri.file(path.join('users', 'development path to', 'one.py')), Uri.file(path.join('users', 'development', 'one.py'))].forEach(fileUri => { + test(`Flake8 (${fileUri.fsPath.indexOf(' ') > 0 ? 'with spaces' : 'without spaces'})`, async () => { + const linter = new Flake8(outputChannel.object, serviceContainer); + const expectedArgs = ['--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', fileUri.fsPath.fileToCommandArgument()]; + await testLinter(linter, fileUri, expectedArgs); + }); + test(`Pep8 (${fileUri.fsPath.indexOf(' ') > 0 ? 'with spaces' : 'without spaces'})`, async () => { + const linter = new Pep8(outputChannel.object, serviceContainer); + const expectedArgs = ['--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', fileUri.fsPath.fileToCommandArgument()]; + await testLinter(linter, fileUri, expectedArgs); + }); + test(`Prospector (${fileUri.fsPath.indexOf(' ') > 0 ? 'with spaces' : 'without spaces'})`, async () => { + const linter = new Prospector(outputChannel.object, serviceContainer); + const expectedArgs = ['--absolute-paths', '--output-format=json', fileUri.fsPath.fileToCommandArgument()]; + await testLinter(linter, fileUri, expectedArgs); + }); + test(`Pylama (${fileUri.fsPath.indexOf(' ') > 0 ? 'with spaces' : 'without spaces'})`, async () => { + const linter = new PyLama(outputChannel.object, serviceContainer); + const expectedArgs = ['--format=parsable', fileUri.fsPath.fileToCommandArgument()]; + await testLinter(linter, fileUri, expectedArgs); + }); + test(`MyPy (${fileUri.fsPath.indexOf(' ') > 0 ? 'with spaces' : 'without spaces'})`, async () => { + const linter = new MyPy(outputChannel.object, serviceContainer); + const expectedArgs = [fileUri.fsPath.fileToCommandArgument()]; + await testLinter(linter, fileUri, expectedArgs); + }); + test(`Pydocstyle (${fileUri.fsPath.indexOf(' ') > 0 ? 'with spaces' : 'without spaces'})`, async () => { + const linter = new PyDocStyle(outputChannel.object, serviceContainer); + const expectedArgs = [fileUri.fsPath.fileToCommandArgument()]; + await testLinter(linter, fileUri, expectedArgs); + }); + test(`Pylint (${fileUri.fsPath.indexOf(' ') > 0 ? 'with spaces' : 'without spaces'})`, async () => { + const linter = new Pylint(outputChannel.object, serviceContainer); + document.setup(d => d.uri).returns(() => fileUri); + + let invoked = false; + (linter as any).run = (args, doc, token) => { + expect(args[args.length - 1]).to.equal(fileUri.fsPath.fileToCommandArgument()); + invoked = true; + return Promise.resolve([]); + }; + await linter.lint(document.object, cancellationToken); + expect(invoked).to.be.equal(true, 'method not invoked'); + }); + }); +}); From f0b0361203b52009e38db26696c832065874386f Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 22 Mar 2018 16:42:52 -0700 Subject: [PATCH 063/433] Add ability to disable the check on memory usage of language server (Jedi) process (#1156) * :bug: ability to turn off check and add threshold check * :memo: news entry * Fixes #1036 --- news/2 Fixes/1036.md | 5 + package.json | 4 +- src/client/{telemetry => common}/stopWatch.ts | 3 + src/client/debugger/Common/telemetry.ts | 2 +- src/client/extension.ts | 2 +- src/client/formatters/autoPep8Formatter.ts | 2 +- src/client/formatters/yapfFormatter.ts | 2 +- .../configuration/pythonPathUpdaterService.ts | 2 +- src/client/linters/lintingEngine.ts | 304 +++++++++--------- src/client/providers/jediProxy.ts | 33 +- .../providers/simpleRefactorProvider.ts | 2 +- src/client/telemetry/index.ts | 2 +- src/client/telemetry/types.ts | 6 +- 13 files changed, 199 insertions(+), 170 deletions(-) create mode 100644 news/2 Fixes/1036.md rename src/client/{telemetry => common}/stopWatch.ts (80%) diff --git a/news/2 Fixes/1036.md b/news/2 Fixes/1036.md new file mode 100644 index 000000000000..3992f412b521 --- /dev/null +++ b/news/2 Fixes/1036.md @@ -0,0 +1,5 @@ +Add ability to disable the check on memory usage of language server (Jedi) process. +To turn off this check, add the following setting into your user or workspace settings (`settings.json`) file: +```json +"python.jediMemoryLimit": -1 +``` diff --git a/package.json b/package.json index 0b0b476f7372..1f02e3e4b730 100644 --- a/package.json +++ b/package.json @@ -1007,8 +1007,8 @@ }, "python.jediMemoryLimit": { "type": "number", - "default": "0", - "description": "Memory limit for the Jedi completion engine in megabytes. Zero (default) means 1024 MB", + "default": 0, + "description": "Memory limit for the Jedi completion engine in megabytes. Zero (default) means 1024 MB. -1 means unlimited (disable memory limit check)", "scope": "resource" }, "python.sortImports.path": { diff --git a/src/client/telemetry/stopWatch.ts b/src/client/common/stopWatch.ts similarity index 80% rename from src/client/telemetry/stopWatch.ts rename to src/client/common/stopWatch.ts index 3e2a2132e94d..a72b3f3ae349 100644 --- a/src/client/telemetry/stopWatch.ts +++ b/src/client/common/stopWatch.ts @@ -8,4 +8,7 @@ export class StopWatch { public get elapsedTime() { return Date.now() - this.started; } + public reset(){ + this.started = Date.now(); + } } diff --git a/src/client/debugger/Common/telemetry.ts b/src/client/debugger/Common/telemetry.ts index 2e454c350379..9cb9fd3dc24a 100644 --- a/src/client/debugger/Common/telemetry.ts +++ b/src/client/debugger/Common/telemetry.ts @@ -4,8 +4,8 @@ // tslint:disable:no-function-expression no-any no-invalid-this no-use-before-declare import { DebugSession, StoppedEvent } from 'vscode-debugadapter'; +import { StopWatch } from '../../common/stopWatch'; import { DEBUGGER_PERFORMANCE } from '../../telemetry/constants'; -import { StopWatch } from '../../telemetry/stopWatch'; import { DebuggerPerformanceTelemetry } from '../../telemetry/types'; import { TelemetryEvent } from './Contracts'; diff --git a/src/client/extension.ts b/src/client/extension.ts index 1a2eb6b7cb19..e680bfb21553 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -20,6 +20,7 @@ import { registerTypes as installerRegisterTypes } from './common/installer/serv import { registerTypes as platformRegisterTypes } from './common/platform/serviceRegistry'; import { registerTypes as processRegisterTypes } from './common/process/serviceRegistry'; import { registerTypes as commonRegisterTypes } from './common/serviceRegistry'; +import { StopWatch } from './common/stopWatch'; import { GLOBAL_MEMENTO, IDisposableRegistry, ILogger, IMemento, IOutputChannel, IPersistentStateFactory, WORKSPACE_MEMENTO } from './common/types'; import { registerTypes as variableRegisterTypes } from './common/variables/serviceRegistry'; import { BaseConfigurationProvider } from './debugger/configProviders/baseProvider'; @@ -53,7 +54,6 @@ import { activateUpdateSparkLibraryProvider } from './providers/updateSparkLibra import * as sortImports from './sortImports'; import { sendTelemetryEvent } from './telemetry'; import { EDITOR_LOAD } from './telemetry/constants'; -import { StopWatch } from './telemetry/stopWatch'; import { registerTypes as commonRegisterTerminalTypes } from './terminals/serviceRegistry'; import { ICodeExecutionManager } from './terminals/types'; import { BlockFormatProviders } from './typeFormatters/blockFormatProvider'; diff --git a/src/client/formatters/autoPep8Formatter.ts b/src/client/formatters/autoPep8Formatter.ts index 316fab349cc9..b3818c8eef0e 100644 --- a/src/client/formatters/autoPep8Formatter.ts +++ b/src/client/formatters/autoPep8Formatter.ts @@ -1,10 +1,10 @@ import * as vscode from 'vscode'; import { Product } from '../common/installer/productInstaller'; +import { StopWatch } from '../common/stopWatch'; import { IConfigurationService } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { sendTelemetryWhenDone } from '../telemetry'; import { FORMAT } from '../telemetry/constants'; -import { StopWatch } from '../telemetry/stopWatch'; import { BaseFormatter } from './baseFormatter'; export class AutoPep8Formatter extends BaseFormatter { diff --git a/src/client/formatters/yapfFormatter.ts b/src/client/formatters/yapfFormatter.ts index 31a768ca0a57..7482970c35d0 100644 --- a/src/client/formatters/yapfFormatter.ts +++ b/src/client/formatters/yapfFormatter.ts @@ -1,9 +1,9 @@ import * as vscode from 'vscode'; +import { StopWatch } from '../common/stopWatch'; import { IConfigurationService, Product } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { sendTelemetryWhenDone } from '../telemetry'; import { FORMAT } from '../telemetry/constants'; -import { StopWatch } from '../telemetry/stopWatch'; import { BaseFormatter } from './baseFormatter'; export class YapfFormatter extends BaseFormatter { diff --git a/src/client/interpreter/configuration/pythonPathUpdaterService.ts b/src/client/interpreter/configuration/pythonPathUpdaterService.ts index 67a6e0f5009d..812fcd48b6de 100644 --- a/src/client/interpreter/configuration/pythonPathUpdaterService.ts +++ b/src/client/interpreter/configuration/pythonPathUpdaterService.ts @@ -1,10 +1,10 @@ import { inject, injectable } from 'inversify'; import * as path from 'path'; import { ConfigurationTarget, Uri, window } from 'vscode'; +import { StopWatch } from '../../common/stopWatch'; import { IServiceContainer } from '../../ioc/types'; import { sendTelemetryEvent } from '../../telemetry'; import { PYTHON_INTERPRETER } from '../../telemetry/constants'; -import { StopWatch } from '../../telemetry/stopWatch'; import { PythonInterpreterTelemetry } from '../../telemetry/types'; import { IInterpreterVersionService } from '../contracts'; import { IPythonPathUpdaterServiceFactory, IPythonPathUpdaterServiceManager } from './types'; diff --git a/src/client/linters/lintingEngine.ts b/src/client/linters/lintingEngine.ts index 650df50fa913..d5323c697c43 100644 --- a/src/client/linters/lintingEngine.ts +++ b/src/client/linters/lintingEngine.ts @@ -8,12 +8,12 @@ import * as vscode from 'vscode'; import { IDocumentManager, IWorkspaceService } from '../common/application/types'; import { LinterErrors, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; import { IFileSystem } from '../common/platform/types'; +import { StopWatch } from '../common/stopWatch'; import { IConfigurationService, IOutputChannel } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { JupyterProvider } from '../jupyter/provider'; import { sendTelemetryWhenDone } from '../telemetry'; import { LINTING } from '../telemetry/constants'; -import { StopWatch } from '../telemetry/stopWatch'; import { LinterTrigger, LintingTelemetry } from '../telemetry/types'; import { ILinterInfo, ILinterManager, ILintingEngine, ILintMessage, LintMessageSeverity } from './types'; @@ -27,176 +27,176 @@ lintSeverityToVSSeverity.set(LintMessageSeverity.Warning, vscode.DiagnosticSever // tslint:disable-next-line:interface-name interface DocumentHasJupyterCodeCells { - // tslint:disable-next-line:callable-types - (doc: vscode.TextDocument, token: vscode.CancellationToken): Promise; + // tslint:disable-next-line:callable-types + (doc: vscode.TextDocument, token: vscode.CancellationToken): Promise; } @injectable() export class LintingEngine implements ILintingEngine { - private documentHasJupyterCodeCells: DocumentHasJupyterCodeCells; - private workspace: IWorkspaceService; - private documents: IDocumentManager; - private configurationService: IConfigurationService; - private linterManager: ILinterManager; - private diagnosticCollection: vscode.DiagnosticCollection; - private pendingLintings = new Map(); - private outputChannel: vscode.OutputChannel; - private fileSystem: IFileSystem; - - constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { - this.documentHasJupyterCodeCells = (a, b) => Promise.resolve(false); - this.documents = serviceContainer.get(IDocumentManager); - this.workspace = serviceContainer.get(IWorkspaceService); - this.configurationService = serviceContainer.get(IConfigurationService); - this.outputChannel = serviceContainer.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); - this.linterManager = serviceContainer.get(ILinterManager); - this.fileSystem = serviceContainer.get(IFileSystem); - this.diagnosticCollection = vscode.languages.createDiagnosticCollection('python'); - } - - public get diagnostics(): vscode.DiagnosticCollection { - return this.diagnosticCollection; - } - - public clearDiagnostics(document: vscode.TextDocument): void { - if (this.diagnosticCollection.has(document.uri)) { - this.diagnosticCollection.delete(document.uri); + private documentHasJupyterCodeCells: DocumentHasJupyterCodeCells; + private workspace: IWorkspaceService; + private documents: IDocumentManager; + private configurationService: IConfigurationService; + private linterManager: ILinterManager; + private diagnosticCollection: vscode.DiagnosticCollection; + private pendingLintings = new Map(); + private outputChannel: vscode.OutputChannel; + private fileSystem: IFileSystem; + + constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { + this.documentHasJupyterCodeCells = (a, b) => Promise.resolve(false); + this.documents = serviceContainer.get(IDocumentManager); + this.workspace = serviceContainer.get(IWorkspaceService); + this.configurationService = serviceContainer.get(IConfigurationService); + this.outputChannel = serviceContainer.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); + this.linterManager = serviceContainer.get(ILinterManager); + this.fileSystem = serviceContainer.get(IFileSystem); + this.diagnosticCollection = vscode.languages.createDiagnosticCollection('python'); } - } - public async lintOpenPythonFiles(): Promise { - this.diagnosticCollection.clear(); - const promises = this.documents.textDocuments.map(async document => await this.lintDocument(document, 'auto')); - await Promise.all(promises); - return this.diagnosticCollection; - } - - public async lintDocument(document: vscode.TextDocument, trigger: LinterTrigger): Promise { - this.diagnosticCollection.set(document.uri, []); + public get diagnostics(): vscode.DiagnosticCollection { + return this.diagnosticCollection; + } - // Check if we need to lint this document - if (!await this.shouldLintDocument(document)) { - return; + public clearDiagnostics(document: vscode.TextDocument): void { + if (this.diagnosticCollection.has(document.uri)) { + this.diagnosticCollection.delete(document.uri); + } } - if (this.pendingLintings.has(document.uri.fsPath)) { - this.pendingLintings.get(document.uri.fsPath)!.cancel(); - this.pendingLintings.delete(document.uri.fsPath); + public async lintOpenPythonFiles(): Promise { + this.diagnosticCollection.clear(); + const promises = this.documents.textDocuments.map(async document => await this.lintDocument(document, 'auto')); + await Promise.all(promises); + return this.diagnosticCollection; } - const cancelToken = new vscode.CancellationTokenSource(); - cancelToken.token.onCancellationRequested(() => { - if (this.pendingLintings.has(document.uri.fsPath)) { - this.pendingLintings.delete(document.uri.fsPath); - } - }); - - this.pendingLintings.set(document.uri.fsPath, cancelToken); - this.outputChannel.clear(); - - const promises: Promise[] = this.linterManager.getActiveLinters(document.uri) - .map(info => { - const stopWatch = new StopWatch(); - const linter = this.linterManager.createLinter(info.product, this.outputChannel, this.serviceContainer, document.uri); - const promise = linter.lint(document, cancelToken.token); - this.sendLinterRunTelemetry(info, document.uri, promise, stopWatch, trigger); - return promise; - }); - - const hasJupyterCodeCells = await this.documentHasJupyterCodeCells(document, cancelToken.token); - // linters will resolve asynchronously - keep a track of all - // diagnostics reported as them come in. - let diagnostics: vscode.Diagnostic[] = []; - const settings = this.configurationService.getSettings(document.uri); - - for (const p of promises) { - const msgs = await p; - if (cancelToken.token.isCancellationRequested) { - break; - } - - if (this.isDocumentOpen(document.uri)) { - // Build the message and suffix the message with the name of the linter used. - for (const m of msgs) { - // Ignore magic commands from jupyter. - if (hasJupyterCodeCells && document.lineAt(m.line - 1).text.trim().startsWith('%') && - (m.code === LinterErrors.pylint.InvalidSyntax || - m.code === LinterErrors.prospector.InvalidSyntax || - m.code === LinterErrors.flake8.InvalidSyntax)) { - continue; - } - diagnostics.push(this.createDiagnostics(m, document)); + public async lintDocument(document: vscode.TextDocument, trigger: LinterTrigger): Promise { + this.diagnosticCollection.set(document.uri, []); + + // Check if we need to lint this document + if (!await this.shouldLintDocument(document)) { + return; } - // Limit the number of messages to the max value. - diagnostics = diagnostics.filter((value, index) => index <= settings.linting.maxNumberOfProblems); - } - } - // Set all diagnostics found in this pass, as this method always clears existing diagnostics. - this.diagnosticCollection.set(document.uri, diagnostics); - } - - // tslint:disable-next-line:no-any - public async linkJupiterExtension(jupiter: vscode.Extension | undefined): Promise { - if (!jupiter) { - return; + + if (this.pendingLintings.has(document.uri.fsPath)) { + this.pendingLintings.get(document.uri.fsPath)!.cancel(); + this.pendingLintings.delete(document.uri.fsPath); + } + + const cancelToken = new vscode.CancellationTokenSource(); + cancelToken.token.onCancellationRequested(() => { + if (this.pendingLintings.has(document.uri.fsPath)) { + this.pendingLintings.delete(document.uri.fsPath); + } + }); + + this.pendingLintings.set(document.uri.fsPath, cancelToken); + this.outputChannel.clear(); + + const promises: Promise[] = this.linterManager.getActiveLinters(document.uri) + .map(info => { + const stopWatch = new StopWatch(); + const linter = this.linterManager.createLinter(info.product, this.outputChannel, this.serviceContainer, document.uri); + const promise = linter.lint(document, cancelToken.token); + this.sendLinterRunTelemetry(info, document.uri, promise, stopWatch, trigger); + return promise; + }); + + const hasJupyterCodeCells = await this.documentHasJupyterCodeCells(document, cancelToken.token); + // linters will resolve asynchronously - keep a track of all + // diagnostics reported as them come in. + let diagnostics: vscode.Diagnostic[] = []; + const settings = this.configurationService.getSettings(document.uri); + + for (const p of promises) { + const msgs = await p; + if (cancelToken.token.isCancellationRequested) { + break; + } + + if (this.isDocumentOpen(document.uri)) { + // Build the message and suffix the message with the name of the linter used. + for (const m of msgs) { + // Ignore magic commands from jupyter. + if (hasJupyterCodeCells && document.lineAt(m.line - 1).text.trim().startsWith('%') && + (m.code === LinterErrors.pylint.InvalidSyntax || + m.code === LinterErrors.prospector.InvalidSyntax || + m.code === LinterErrors.flake8.InvalidSyntax)) { + continue; + } + diagnostics.push(this.createDiagnostics(m, document)); + } + // Limit the number of messages to the max value. + diagnostics = diagnostics.filter((value, index) => index <= settings.linting.maxNumberOfProblems); + } + } + // Set all diagnostics found in this pass, as this method always clears existing diagnostics. + this.diagnosticCollection.set(document.uri, diagnostics); } - if (!jupiter.isActive) { - await jupiter.activate(); + + // tslint:disable-next-line:no-any + public async linkJupiterExtension(jupiter: vscode.Extension | undefined): Promise { + if (!jupiter) { + return; + } + if (!jupiter.isActive) { + await jupiter.activate(); + } + // tslint:disable-next-line:no-unsafe-any + jupiter.exports.registerLanguageProvider(PYTHON.language, new JupyterProvider()); + // tslint:disable-next-line:no-unsafe-any + this.documentHasJupyterCodeCells = jupiter.exports.hasCodeCells; } - // tslint:disable-next-line:no-unsafe-any - jupiter.exports.registerLanguageProvider(PYTHON.language, new JupyterProvider()); - // tslint:disable-next-line:no-unsafe-any - this.documentHasJupyterCodeCells = jupiter.exports.hasCodeCells; - } - - private sendLinterRunTelemetry(info: ILinterInfo, resource: vscode.Uri, promise: Promise, stopWatch: StopWatch, trigger: LinterTrigger): void { - const linterExecutablePathName = info.pathName(resource); - const properties: LintingTelemetry = { - tool: info.id, - hasCustomArgs: info.linterArgs(resource).length > 0, - trigger, - executableSpecified: linterExecutablePathName.length > 0 - }; - sendTelemetryWhenDone(LINTING, promise, stopWatch, properties); - } - - private isDocumentOpen(uri: vscode.Uri): boolean { - return this.documents.textDocuments.some(document => document.uri.fsPath === uri.fsPath); - } - - private createDiagnostics(message: ILintMessage, document: vscode.TextDocument): vscode.Diagnostic { - const position = new vscode.Position(message.line - 1, message.column); - const range = new vscode.Range(position, position); - - const severity = lintSeverityToVSSeverity.get(message.severity!)!; - const diagnostic = new vscode.Diagnostic(range, `${message.code}:${message.message}`, severity); - diagnostic.code = message.code; - diagnostic.source = message.provider; - return diagnostic; - } - - private async shouldLintDocument(document: vscode.TextDocument): Promise { - if (!this.linterManager.isLintingEnabled(document.uri)) { - this.diagnosticCollection.set(document.uri, []); - return false; + + private sendLinterRunTelemetry(info: ILinterInfo, resource: vscode.Uri, promise: Promise, stopWatch: StopWatch, trigger: LinterTrigger): void { + const linterExecutablePathName = info.pathName(resource); + const properties: LintingTelemetry = { + tool: info.id, + hasCustomArgs: info.linterArgs(resource).length > 0, + trigger, + executableSpecified: linterExecutablePathName.length > 0 + }; + sendTelemetryWhenDone(LINTING, promise, stopWatch, properties); } - if (document.languageId !== PYTHON.language) { - return false; + private isDocumentOpen(uri: vscode.Uri): boolean { + return this.documents.textDocuments.some(document => document.uri.fsPath === uri.fsPath); } - const workspaceFolder = this.workspace.getWorkspaceFolder(document.uri); - const workspaceRootPath = (workspaceFolder && typeof workspaceFolder.uri.fsPath === 'string') ? workspaceFolder.uri.fsPath : undefined; - const relativeFileName = typeof workspaceRootPath === 'string' ? path.relative(workspaceRootPath, document.fileName) : document.fileName; + private createDiagnostics(message: ILintMessage, document: vscode.TextDocument): vscode.Diagnostic { + const position = new vscode.Position(message.line - 1, message.column); + const range = new vscode.Range(position, position); - const settings = this.configurationService.getSettings(document.uri); - const ignoreMinmatches = settings.linting.ignorePatterns.map(pattern => new Minimatch(pattern)); - if (ignoreMinmatches.some(matcher => matcher.match(document.fileName) || matcher.match(relativeFileName))) { - return false; + const severity = lintSeverityToVSSeverity.get(message.severity!)!; + const diagnostic = new vscode.Diagnostic(range, `${message.code}:${message.message}`, severity); + diagnostic.code = message.code; + diagnostic.source = message.provider; + return diagnostic; } - if (document.uri.scheme !== 'file' || !document.uri.fsPath) { - return false; + + private async shouldLintDocument(document: vscode.TextDocument): Promise { + if (!this.linterManager.isLintingEnabled(document.uri)) { + this.diagnosticCollection.set(document.uri, []); + return false; + } + + if (document.languageId !== PYTHON.language) { + return false; + } + + const workspaceFolder = this.workspace.getWorkspaceFolder(document.uri); + const workspaceRootPath = (workspaceFolder && typeof workspaceFolder.uri.fsPath === 'string') ? workspaceFolder.uri.fsPath : undefined; + const relativeFileName = typeof workspaceRootPath === 'string' ? path.relative(workspaceRootPath, document.fileName) : document.fileName; + + const settings = this.configurationService.getSettings(document.uri); + const ignoreMinmatches = settings.linting.ignorePatterns.map(pattern => new Minimatch(pattern)); + if (ignoreMinmatches.some(matcher => matcher.match(document.fileName) || matcher.match(relativeFileName))) { + return false; + } + if (document.uri.scheme !== 'file' || !document.uri.fsPath) { + return false; + } + return await this.fileSystem.fileExistsAsync(document.uri.fsPath); } - return await this.fileSystem.fileExistsAsync(document.uri.fsPath); - } } diff --git a/src/client/providers/jediProxy.ts b/src/client/providers/jediProxy.ts index fcf9b7c7e523..f0136585786f 100644 --- a/src/client/providers/jediProxy.ts +++ b/src/client/providers/jediProxy.ts @@ -14,6 +14,7 @@ import { debounce, swallowExceptions } from '../common/decorators'; import '../common/extensions'; import { createDeferred, Deferred } from '../common/helpers'; import { IPythonExecutionFactory } from '../common/process/types'; +import { StopWatch } from '../common/stopWatch'; import { ILogger } from '../common/types'; import { IEnvironmentVariablesProvider } from '../common/variables/types'; import { IServiceContainer } from '../ioc/types'; @@ -131,7 +132,7 @@ commandNames.set(CommandType.Usages, 'usages'); commandNames.set(CommandType.Symbols, 'names'); export class JediProxy implements vscode.Disposable { - private proc: ChildProcess | null; + private proc?: ChildProcess; private pythonSettings: PythonSettings; private cmdId: number = 0; private lastKnownPythonInterpreter: string; @@ -141,10 +142,12 @@ export class JediProxy implements vscode.Disposable { private spawnRetryAttempts = 0; private additionalAutoCompletePaths: string[] = []; private workspacePath: string; - private languageServerStarted: Deferred; + private languageServerStarted!: Deferred; private initialized: Deferred; - private environmentVariablesProvider: IEnvironmentVariablesProvider; + private environmentVariablesProvider!: IEnvironmentVariablesProvider; private logger: ILogger; + private ignoreJediMemoryFootprint: boolean = false; + private pidUsageFailures = { timer: new StopWatch(), counter: 0 }; public constructor(private extensionRootDir: string, workspacePath: string, private serviceContainer: IServiceContainer) { this.workspacePath = workspacePath; @@ -158,7 +161,9 @@ export class JediProxy implements vscode.Disposable { // Check memory footprint periodically. Do not check on every request due to // the performance impact. See https://github.com/soyuka/pidusage - on Windows // it is using wmic which means spawning cmd.exe process on every request. - setInterval(() => this.checkJediMemoryFootprint(), 2000); + if (this.shouldCheckJediMemoryFootprint()) { + setInterval(() => this.checkJediMemoryFootprint(), 2000); + } } private static getProperty(o: object, name: string): T { @@ -212,13 +217,28 @@ export class JediProxy implements vscode.Disposable { this.handleError('spawnProcess', ex); }); } - + private shouldCheckJediMemoryFootprint() { + if (this.ignoreJediMemoryFootprint || this.pythonSettings.jediMemoryLimit === -1) { + return false; + } + return true; + } private checkJediMemoryFootprint() { if (!this.proc || this.proc.killed) { return; } + if (!this.shouldCheckJediMemoryFootprint()) { + return; + } pidusage.stat(this.proc.pid, async (err, result) => { if (err) { + this.pidUsageFailures.counter += 1; + // If this function fails 5 times in the last 30 seconds, lets not try ever again. + if (this.pidUsageFailures.timer.elapsedTime > 30 * 1000) { + this.ignoreJediMemoryFootprint = this.pidUsageFailures.counter > 5; + this.pidUsageFailures.counter = 0; + this.pidUsageFailures.timer.reset(); + } return console.error('Python Extension: (pidusage)', err); } const limit = Math.min(Math.max(this.pythonSettings.jediMemoryLimit, 1024), 8192); @@ -276,7 +296,7 @@ export class JediProxy implements vscode.Disposable { } // tslint:disable-next-line:no-empty } catch (ex) { } - this.proc = null; + this.proc = undefined; } private handleError(source: string, errorMessage: string) { @@ -512,6 +532,7 @@ export class JediProxy implements vscode.Disposable { private onArguments(command: IExecutionCommand, response: object): void { // tslint:disable-next-line:no-any const defs = JediProxy.getProperty(response, 'results'); + // tslint:disable-next-line:no-object-literal-type-assertion this.safeResolve(command, { requestId: command.id, definitions: defs diff --git a/src/client/providers/simpleRefactorProvider.ts b/src/client/providers/simpleRefactorProvider.ts index f6b1772dc19f..513ae8ff9c85 100644 --- a/src/client/providers/simpleRefactorProvider.ts +++ b/src/client/providers/simpleRefactorProvider.ts @@ -1,12 +1,12 @@ import * as vscode from 'vscode'; import { PythonSettings } from '../common/configSettings'; import { getTextEditsFromPatch } from '../common/editor'; +import { StopWatch } from '../common/stopWatch'; import { IInstaller, Product } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { RefactorProxy } from '../refactor/proxy'; import { sendTelemetryWhenDone } from '../telemetry'; import { REFACTOR_EXTRACT_FUNCTION, REFACTOR_EXTRACT_VAR } from '../telemetry/constants'; -import { StopWatch } from '../telemetry/stopWatch'; type RenameResponse = { results: [{ diff: string }]; diff --git a/src/client/telemetry/index.ts b/src/client/telemetry/index.ts index 559e7b61d99a..adf0952fa7d5 100644 --- a/src/client/telemetry/index.ts +++ b/src/client/telemetry/index.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { StopWatch } from './stopWatch'; +import { StopWatch } from '../common/stopWatch'; import { getTelemetryReporter } from './telemetry'; import { TelemetryProperties } from './types'; diff --git a/src/client/telemetry/types.ts b/src/client/telemetry/types.ts index ed1cedaa1777..f8964590a324 100644 --- a/src/client/telemetry/types.ts +++ b/src/client/telemetry/types.ts @@ -30,7 +30,7 @@ export type CodeExecutionTelemetry = { scope: 'file' | 'selection'; }; export type DebuggerTelemetry = { - trigger: 'launch' | 'attach' + trigger: 'launch' | 'attach'; console?: 'none' | 'integratedTerminal' | 'externalTerminal'; debugOptions?: string; pyspark?: boolean; @@ -41,14 +41,14 @@ export type DebuggerPerformanceTelemetry = { action: 'stepIn' | 'stepOut' | 'continue' | 'next' | 'launch'; }; export type TestRunTelemetry = { - tool: 'nosetest' | 'pytest' | 'unittest' + tool: 'nosetest' | 'pytest' | 'unittest'; scope: 'currentFile' | 'all' | 'file' | 'class' | 'function' | 'failed'; debugging: boolean; trigger: 'ui' | 'codelens' | 'commandpalette' | 'auto'; failed: boolean; }; export type TestDiscoverytTelemetry = { - tool: 'nosetest' | 'pytest' | 'unittest' + tool: 'nosetest' | 'pytest' | 'unittest'; trigger: 'ui' | 'commandpalette'; failed: boolean; }; From 188808fa50b642830fdfe5ea8ae4965eced4a4a4 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 26 Mar 2018 10:34:36 -0700 Subject: [PATCH 064/433] Link the Q&A tab on the marketplace to SO (#1157) --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 1f02e3e4b730..b996bfd75f93 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "bugs": { "url": "https://github.com/Microsoft/vscode-python/issues" }, + "qna": "https://stackoverflow.com/questions/tagged/visual-studio-code+python", "badges": [ { "url": "https://travis-ci.org/Microsoft/vscode-python.svg?branch=master", From a09d178a0c41e3716dd11df6757f2e0b7391f64f Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 26 Mar 2018 11:36:09 -0700 Subject: [PATCH 065/433] TPN updates for 2018.3.0 release (#1196) --- ThirdPartyNotices-Distribution.txt | 589 ++++++++++++++++++++++++++++- ThirdPartyNotices-Repository.txt | 29 +- 2 files changed, 597 insertions(+), 21 deletions(-) diff --git a/ThirdPartyNotices-Distribution.txt b/ThirdPartyNotices-Distribution.txt index 3b8e433c8a9b..2ce55b59b5bd 100644 --- a/ThirdPartyNotices-Distribution.txt +++ b/ThirdPartyNotices-Distribution.txt @@ -1,4 +1,4 @@ - + THIRD-PARTY SOFTWARE NOTICES AND INFORMATION Do Not Translate or Localize @@ -10,8 +10,8 @@ Microsoft Python extension for Visual Studio Code incorporates components from t 3. Files from the Python Project (https://www.python.org/) 4. fuzzy (https://github.com/mattyork/fuzzy) 5. Get-port (https://github.com/sindresorhus/get-port) -6. Go for Visual Studio Code (https://github.com/Microsoft/vscode-go) -7. Google Diff Match and Patch (https://github.com/GerHobbelt/google-diff-match-patch) +6. Go for Visual Studio Code (https://github.com/Microsoft/vscode-go) +7. Google Diff Match and Patch (https://github.com/GerHobbelt/google-diff-match-patch) 8. Iconv-lite (https://github.com/ashtuchkin/iconv-lite) 9. Inversify (https://github.com/inversify/InversifyJS) 10. isort (https://github.com/timothycrosley/isort) @@ -32,22 +32,30 @@ Microsoft Python extension for Visual Studio Code incorporates components from t 23. omnisharp-vscode (https://github.com/OmniSharp/omnisharp-vscode) 24. opn (https://github.com/sindresorhus/opn) 25. pidusage (https://github.com/soyuka/pidusage) -26. Python documentation (https://docs.python.org/) -27. python-functools32 (https://github.com/MiCHiLU/python-functools32/blob/master/functools32/functools32.py) -28. pythonVSCode (https://github.com/DonJayamanne/pythonVSCode) -29. Reflect-metadata (https://github.com/rbuckton/reflect-metadata) -30. RxJS (https://github.com/ReactiveX/RxJS) +26. PTVS (https://github.com/Microsoft/PTVS) +27. PTVSD (https://github.com/Microsoft/PTVSD) +28. PyDev.Debugger (https://github.com/fabioz/PyDev.Debugger) + Includes:Files copyright Yuli Fitterman + Includes:IPython + Includes:py2app + Includes:Python (various files) +29. Python documentation (https://docs.python.org/) +30. python-functools32 (https://github.com/MiCHiLU/python-functools32/blob/master/functools32/functools32.py) +31. pythonVSCode (https://github.com/DonJayamanne/pythonVSCode) +32. Reflect-metadata (https://github.com/rbuckton/reflect-metadata) +33. RxJS (https://github.com/ReactiveX/RxJS) Includes:Contributor Covenant v1.1.0, v1.4 Includes:File from Angular.io Includes:File from setImmediate -31. Sphinx (http://sphinx-doc.org/) -32. uint64be (https://github.com/mafintosh/uint64be) -33. untildify (https://github.com/sindresorhus/untildify) -34. vscode-debugadapter (https://github.com/Microsoft/vscode-debugadapter-node/tree/master/adapter) -35. vscode-debugprotocol (https://github.com/Microsoft/vscode-debugadapter-node/tree/master/protocol) -36. vscode-extension-telemetry (https://github.com/Microsoft/vscode-extension-telemetry) -37. vscode-languageclient (https://github.com/Microsoft/vscode-languageserver-node) -38. vscode-languageserver (https://github.com/Microsoft/vscode-languageserver-node/) +34. Sphinx (http://sphinx-doc.org/) +35. uint64be (https://github.com/mafintosh/uint64be) +36. untangle (https://github.com/stchris/untangle) +37. untildify (https://github.com/sindresorhus/untildify) +38. vscode-debugadapter (https://github.com/Microsoft/vscode-debugadapter-node/tree/master/adapter) +39. vscode-debugprotocol (https://github.com/Microsoft/vscode-debugadapter-node/tree/master/protocol) +40. vscode-extension-telemetry (https://github.com/Microsoft/vscode-extension-telemetry) +41. vscode-languageclient (https://github.com/Microsoft/vscode-languageserver-node) +42. vscode-languageserver (https://github.com/Microsoft/vscode-languageserver-node/) %% Arch NOTICES AND INFORMATION BEGIN HERE @@ -898,6 +906,520 @@ SOFTWARE. ========================================= END OF pidusage NOTICES AND INFORMATION +%% PTVS NOTICES AND INFORMATION BEGIN HERE +========================================= +Copyright (c) Microsoft Corporation. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +========================================= +END OF PTVS NOTICES AND INFORMATION + +%% PTVSD NOTICES AND INFORMATION BEGIN HERE +========================================= + ptvsd + + Copyright (c) Microsoft Corporation + All rights reserved. + + MIT License + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +========================================= +END OF PTVSD NOTICES AND INFORMATION + +%% PyDev.Debugger NOTICES AND INFORMATION BEGIN HERE +========================================= +Eclipse Public License - v 1.0 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM +CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + +a) in the case of the initial Contributor, the initial code and documentation + distributed under this Agreement, and +b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + + where such changes and/or additions to the Program originate from and are + distributed by that particular Contributor. A Contribution 'originates' + from a Contributor if it was added to the Program by such Contributor + itself or anyone acting on such Contributor's behalf. Contributions do not + include additions to the Program which: (i) are separate modules of + software distributed in conjunction with the Program under their own + license agreement, and (ii) are not derivative works of the Program. + +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which are +necessarily infringed by the use or sale of its Contribution alone or when +combined with the Program. + +"Program" means the Contributions distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, +including all Contributors. + +2. GRANT OF RIGHTS + a) Subject to the terms of this Agreement, each Contributor hereby grants + Recipient a non-exclusive, worldwide, royalty-free copyright license to + reproduce, prepare derivative works of, publicly display, publicly + perform, distribute and sublicense the Contribution of such Contributor, + if any, and such derivative works, in source code and object code form. + b) Subject to the terms of this Agreement, each Contributor hereby grants + Recipient a non-exclusive, worldwide, royalty-free patent license under + Licensed Patents to make, use, sell, offer to sell, import and otherwise + transfer the Contribution of such Contributor, if any, in source code and + object code form. This patent license shall apply to the combination of + the Contribution and the Program if, at the time the Contribution is + added by the Contributor, such addition of the Contribution causes such + combination to be covered by the Licensed Patents. The patent license + shall not apply to any other combinations which include the Contribution. + No hardware per se is licensed hereunder. + c) Recipient understands that although each Contributor grants the licenses + to its Contributions set forth herein, no assurances are provided by any + Contributor that the Program does not infringe the patent or other + intellectual property rights of any other entity. Each Contributor + disclaims any liability to Recipient for claims brought by any other + entity based on infringement of intellectual property rights or + otherwise. As a condition to exercising the rights and licenses granted + hereunder, each Recipient hereby assumes sole responsibility to secure + any other intellectual property rights needed, if any. For example, if a + third party patent license is required to allow Recipient to distribute + the Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + d) Each Contributor represents that to its knowledge it has sufficient + copyright rights in its Contribution, if any, to grant the copyright + license set forth in this Agreement. + +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under +its own license agreement, provided that: + + a) it complies with the terms and conditions of this Agreement; and + b) its license agreement: + i) effectively disclaims on behalf of all Contributors all warranties + and conditions, express and implied, including warranties or + conditions of title and non-infringement, and implied warranties or + conditions of merchantability and fitness for a particular purpose; + ii) effectively excludes on behalf of all Contributors all liability for + damages, including direct, indirect, special, incidental and + consequential damages, such as lost profits; + iii) states that any provisions which differ from this Agreement are + offered by that Contributor alone and not by any other party; and + iv) states that source code for the Program is available from such + Contributor, and informs licensees how to obtain it in a reasonable + manner on or through a medium customarily used for software exchange. + +When the Program is made available in source code form: + + a) it must be made available under this Agreement; and + b) a copy of this Agreement must be included with each copy of the Program. + Contributors may not remove or alter any copyright notices contained + within the Program. + +Each Contributor must identify itself as the originator of its Contribution, +if +any, in a manner that reasonably allows subsequent Recipients to identify the +originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with +respect to end users, business partners and the like. While this license is +intended to facilitate the commercial use of the Program, the Contributor who +includes the Program in a commercial product offering should do so in a manner +which does not create potential liability for other Contributors. Therefore, +if a Contributor includes the Program in a commercial product offering, such +Contributor ("Commercial Contributor") hereby agrees to defend and indemnify +every other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits and +other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such Commercial +Contributor in connection with its distribution of the Program in a commercial +product offering. The obligations in this section do not apply to any claims +or Losses relating to any actual or alleged intellectual property +infringement. In order to qualify, an Indemnified Contributor must: +a) promptly notify the Commercial Contributor in writing of such claim, and +b) allow the Commercial Contributor to control, and cooperate with the +Commercial Contributor in, the defense and any related settlement +negotiations. The Indemnified Contributor may participate in any such claim at +its own expense. + +For example, a Contributor might include the Program in a commercial product +offering, Product X. That Contributor is then a Commercial Contributor. If +that Commercial Contributor then makes performance claims, or offers +warranties related to Product X, those performance claims and warranties are +such Commercial Contributor's responsibility alone. Under this section, the +Commercial Contributor would have to defend claims against the other +Contributors related to those performance claims and warranties, and if a +court requires any other Contributor to pay any damages as a result, the +Commercial Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, +NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each +Recipient is solely responsible for determining the appropriateness of using +and distributing the Program and assumes all risks associated with its +exercise of rights under this Agreement , including but not limited to the +risks and costs of program errors, compliance with applicable laws, damage to +or loss of data, programs or equipment, and unavailability or interruption of +operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY +CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION +LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of the +remainder of the terms of this Agreement, and without further action by the +parties hereto, such provision shall be reformed to the minimum extent +necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Program itself +(excluding combinations of the Program with other software or hardware) +infringes such Recipient's patent(s), then such Recipient's rights granted +under Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to +comply with any of the material terms or conditions of this Agreement and does +not cure such failure in a reasonable period of time after becoming aware of +such noncompliance. If all Recipient's rights under this Agreement terminate, +Recipient agrees to cease use and distribution of the Program as soon as +reasonably practicable. However, Recipient's obligations under this Agreement +and any licenses granted by Recipient relating to the Program shall continue +and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in +order to avoid inconsistency the Agreement is copyrighted and may only be +modified in the following manner. The Agreement Steward reserves the right to +publish new versions (including revisions) of this Agreement from time to +time. No one other than the Agreement Steward has the right to modify this +Agreement. The Eclipse Foundation is the initial Agreement Steward. The +Eclipse Foundation may assign the responsibility to serve as the Agreement +Steward to a suitable separate entity. Each new version of the Agreement will +be given a distinguishing version number. The Program (including +Contributions) may always be distributed subject to the version of the +Agreement under which it was received. In addition, after a new version of the +Agreement is published, Contributor may elect to distribute the Program +(including its Contributions) under the new version. Except as expressly +stated in Sections 2(a) and 2(b) above, Recipient receives no rights or +licenses to the intellectual property of any Contributor under this Agreement, +whether expressly, by implication, estoppel or otherwise. All rights in the +Program not expressly granted under this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the +intellectual property laws of the United States of America. No party to this +Agreement will bring a legal action under this Agreement more than one year +after the cause of action arose. Each party waives its rights to a jury trial in +any resulting litigation. +========================================= +Includes files copyright Yuli Fitterman + +Copyright (c) Yuli Fitterman + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +========================================= +Includes Ipython + +Copyright (c) 2008-2010, IPython Development Team +Copyright (c) 2001-2007, Fernando Perez. +Copyright (c) 2001, Janko Hauser +Copyright (c) 2001, Nathaniel Gray + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of the IPython Development Team nor the names of its +contributors may be used to endorse or promote products derived from this +software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +========================================= +Includes py2app + +This is the MIT license. This software may also be distributed under the same terms as Python (the PSF license). + +Copyright (c) 2004 Bob Ippolito. + +Some parts copyright (c) 2010-2014 Ronald Oussoren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +========================================= +Includes lib2to3 and other Python files + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012 Python Software Foundation; All Rights Reserved" are retained in Python +alone or in any derivative version prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the Internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the Internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +========================================= +END OF PyDev.Debugger NOTICES AND INFORMATION + %% Python documentation NOTICES AND INFORMATION BEGIN HERE ========================================= Terms and conditions for accessing or otherwise using Python @@ -1442,6 +1964,41 @@ THE SOFTWARE. ========================================= END OF uint64be NOTICES AND INFORMATION +%% untangle NOTICES AND INFORMATION BEGIN HERE +========================================= +# Author: Christian Stefanescu + +# Contributions from: + +Florian Idelberger +Apalala + +// Copyright (c) 2011 + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + +========================================= +END OF untangle NOTICES AND INFORMATION + %% untildify NOTICES AND INFORMATION BEGIN HERE ========================================= The MIT License (MIT) diff --git a/ThirdPartyNotices-Repository.txt b/ThirdPartyNotices-Repository.txt index 8d0449fa5d4f..29bbf7b86c56 100644 --- a/ThirdPartyNotices-Repository.txt +++ b/ThirdPartyNotices-Repository.txt @@ -2,7 +2,7 @@ THIRD-PARTY SOFTWARE NOTICES AND INFORMATION Do Not Translate or Localize -Microsoft Python extension for Visual Studio Code incorporates third party material from the projects listed below. The original copyright notice and the license under which Microsoft received such third party material are set forth below. Microsoft reserves all other rights not expressly granted, whether by implication, estoppel or otherwise. +Microsoft Python extension for Visual Studio Code incorporates third party material from the projects listed below. The original copyright notice and the license under which Microsoft received such third party material are set forth below. Microsoft reserves all other rights not expressly granted, whether by implication, estoppel or otherwise. 1. Go for Visual Studio Code (https://github.com/Microsoft/vscode-go) @@ -12,10 +12,11 @@ Go for Visual Studio Code (https://github.com/Microsoft/vscode-go) 5. jedi (https://github.com/davidhalter/jedi) 6. omnisharp-vscode (https://github.com/OmniSharp/omnisharp-vscode) 7. parso (https://github.com/davidhalter/parso) -8. Python documentation (https://docs.python.org/) -9. python-functools32 (https://github.com/MiCHiLU/python-functools32/blob/master/functools32/functools32.py) -10. pythonVSCode (https://github.com/DonJayamanne/pythonVSCode) -11. Sphinx (http://sphinx-doc.org/) +8. PTVS (https://github.com/Microsoft/PTVS) +9. Python documentation (https://docs.python.org/) +10. python-functools32 (https://github.com/MiCHiLU/python-functools32/blob/master/functools32/functools32.py) +11. pythonVSCode (https://github.com/DonJayamanne/pythonVSCode) +12. Sphinx (http://sphinx-doc.org/) %% @@ -427,6 +428,24 @@ Agreement. ========================================= END OF parso NOTICES, INFORMATION, AND LICENSE +%% PTVS NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +Copyright (c) Microsoft Corporation. All rights reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +========================================= +END OF PTVS NOTICES, INFORMATION, AND LICENSE + %% Python documentation NOTICES, INFORMATION, AND LICENSE BEGIN HERE ========================================= Terms and conditions for accessing or otherwise using Python From 6062a54f09715ba4bdbcca1710477e4696dd5f97 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 26 Mar 2018 11:40:51 -0700 Subject: [PATCH 066/433] Changes to how debug options are passed into the experimental version of PTVSD (debugger) (#1195) * :hammer: change how debug options are passed to new PTVSD * :memo: news entry * Fixes #1168 --- news/3 Code Health/1168.md | 1 + package.json | 4 +++ src/client/debugger/Common/Contracts.ts | 20 ++++++++----- .../debugger/DebugClients/LocalDebugClient.ts | 4 +-- .../debugger/configProviders/baseProvider.ts | 9 +++--- .../configProviders/pythonV2Provider.ts | 10 ++++--- src/client/unittests/common/debugLauncher.ts | 3 +- .../debugger/configProvider/provider.test.ts | 29 +++++-------------- src/test/debugger/misc.test.ts | 4 +-- src/test/debugger/portAndHost.test.ts | 4 +-- .../unittests/common/debugLauncher.test.ts | 7 +++-- 11 files changed, 47 insertions(+), 48 deletions(-) create mode 100644 news/3 Code Health/1168.md diff --git a/news/3 Code Health/1168.md b/news/3 Code Health/1168.md new file mode 100644 index 000000000000..191f91bdd741 --- /dev/null +++ b/news/3 Code Health/1168.md @@ -0,0 +1 @@ +Changes to how debug options are passed into the experimental version of PTVSD (debugger). diff --git a/package.json b/package.json index b996bfd75f93..62ca582bcf96 100644 --- a/package.json +++ b/package.json @@ -901,6 +901,10 @@ "items": { "type": "string", "enum": [ + "RedirectOutput", + "DebugStdLib", + "DjangoDebugging", + "FlaskDebugging", "Sudo", "Pyramid" ] diff --git a/src/client/debugger/Common/Contracts.ts b/src/client/debugger/Common/Contracts.ts index ef0047be2d42..bc1820f4725f 100644 --- a/src/client/debugger/Common/Contracts.ts +++ b/src/client/debugger/Common/Contracts.ts @@ -36,13 +36,17 @@ export enum DebugFlags { IgnoreCommandBursts = 1 } -export class DebugOptions { - public static get WaitOnAbnormalExit(): string { return 'WaitOnAbnormalExit'; } - public static get WaitOnNormalExit(): string { return 'WaitOnNormalExit'; } - public static get RedirectOutput(): string { return 'RedirectOutput'; } - public static get DjangoDebugging(): string { return 'DjangoDebugging'; } - public static get DebugStdLib(): string { return 'DebugStdLib'; } - public static get BreakOnSystemExitZero(): string { return 'BreakOnSystemExitZero'; } +export enum DebugOptions { + WaitOnAbnormalExit = 'WaitOnAbnormalExit', + WaitOnNormalExit = 'WaitOnNormalExit', + RedirectOutput = 'RedirectOutput', + DjangoDebugging = 'DjangoDebugging', + FlaskDebugging = 'FlaskDebugging', + DebugStdLib = 'DebugStdLib', + BreakOnSystemExitZero = 'BreakOnSystemExitZero', + Sudo = 'Sudo', + Pyramid = 'Pyramid', + FixFilePathCase = 'FixFilePathCase' } export interface ExceptionHandling { @@ -64,7 +68,7 @@ export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArgum args: string[]; applicationType?: string; cwd?: string; - debugOptions?: string[]; + debugOptions?: DebugOptions[]; env?: Object; envFile: string; exceptionHandling?: ExceptionHandling; diff --git a/src/client/debugger/DebugClients/LocalDebugClient.ts b/src/client/debugger/DebugClients/LocalDebugClient.ts index e1824936ab2d..4101b513786d 100644 --- a/src/client/debugger/DebugClients/LocalDebugClient.ts +++ b/src/client/debugger/DebugClients/LocalDebugClient.ts @@ -8,7 +8,7 @@ import { PathUtils } from '../../common/platform/pathUtils'; import { CurrentProcess } from '../../common/process/currentProcess'; import { EnvironmentVariablesService } from '../../common/variables/environment'; import { IServiceContainer } from '../../ioc/types'; -import { IDebugServer, IPythonProcess, LaunchRequestArguments, VALID_DEBUG_OPTIONS } from '../Common/Contracts'; +import { DebugOptions, IDebugServer, IPythonProcess, LaunchRequestArguments, VALID_DEBUG_OPTIONS } from '../Common/Contracts'; import { IS_WINDOWS } from '../Common/Utils'; import { BaseDebugServer } from '../DebugServers/BaseDebugServer'; import { LocalDebugServer } from '../DebugServers/LocalDebugServer'; @@ -157,7 +157,7 @@ export class LocalDebugClient extends DebugClient { } // tslint:disable-next-line:member-ordering protected buildLauncherArguments(): string[] { - const vsDebugOptions = ['RedirectOutput']; + const vsDebugOptions = [DebugOptions.RedirectOutput]; if (Array.isArray(this.args.debugOptions)) { this.args.debugOptions.filter(opt => VALID_DEBUG_OPTIONS.indexOf(opt) >= 0) .forEach(item => vsDebugOptions.push(item)); diff --git a/src/client/debugger/configProviders/baseProvider.ts b/src/client/debugger/configProviders/baseProvider.ts index fef1a3c377da..f7786eca9b70 100644 --- a/src/client/debugger/configProviders/baseProvider.ts +++ b/src/client/debugger/configProviders/baseProvider.ts @@ -11,12 +11,11 @@ import { PythonLanguage } from '../../common/constants'; import { IFileSystem, IPlatformService } from '../../common/platform/types'; import { IConfigurationService } from '../../common/types'; import { IServiceContainer } from '../../ioc/types'; -import { DebuggerType, LaunchRequestArguments } from '../Common/Contracts'; +import { DebuggerType, DebugOptions, LaunchRequestArguments } from '../Common/Contracts'; // tslint:disable:no-invalid-template-strings export type PythonDebugConfiguration = DebugConfiguration & LaunchRequestArguments; -export type PTVSDDebugConfiguration = PythonDebugConfiguration & { redirectOutput: boolean; fixFilePathCase: boolean }; @injectable() export abstract class BaseConfigurationProvider implements DebugConfigurationProvider { @@ -62,10 +61,10 @@ export abstract class BaseConfigurationProvider implements DebugConfigurationPro debugConfiguration.debugOptions = []; } // Always redirect output. - if (debugConfiguration.debugOptions.indexOf('RedirectOutput') === -1) { - debugConfiguration.debugOptions.push('RedirectOutput'); + if (debugConfiguration.debugOptions.indexOf(DebugOptions.RedirectOutput) === -1) { + debugConfiguration.debugOptions.push(DebugOptions.RedirectOutput); } - if (debugConfiguration.debugOptions.indexOf('Pyramid') >= 0) { + if (debugConfiguration.debugOptions.indexOf(DebugOptions.Pyramid) >= 0) { const platformService = this.serviceContainer.get(IPlatformService); const fs = this.serviceContainer.get(IFileSystem); const pserve = platformService.isWindows ? 'pserve.exe' : 'pserve'; diff --git a/src/client/debugger/configProviders/pythonV2Provider.ts b/src/client/debugger/configProviders/pythonV2Provider.ts index 78ad13af10a0..f9d124d95e0e 100644 --- a/src/client/debugger/configProviders/pythonV2Provider.ts +++ b/src/client/debugger/configProviders/pythonV2Provider.ts @@ -7,7 +7,8 @@ import { inject, injectable } from 'inversify'; import { Uri } from 'vscode'; import { IPlatformService } from '../../common/platform/types'; import { IServiceContainer } from '../../ioc/types'; -import { BaseConfigurationProvider, PTVSDDebugConfiguration, PythonDebugConfiguration } from './baseProvider'; +import { DebugOptions } from '../Common/Contracts'; +import { BaseConfigurationProvider, PythonDebugConfiguration } from './baseProvider'; @injectable() export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvider { @@ -20,8 +21,9 @@ export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvide debugConfiguration.stopOnEntry = false; // Add PTVSD specific flags. - const ptvsdDebugConfigurationFlags = debugConfiguration as PTVSDDebugConfiguration; - ptvsdDebugConfigurationFlags.redirectOutput = Array.isArray(debugConfiguration.debugOptions) && debugConfiguration.debugOptions.indexOf('RedirectOutput') >= 0; - ptvsdDebugConfigurationFlags.fixFilePathCase = this.serviceContainer.get(IPlatformService).isWindows; + if (this.serviceContainer.get(IPlatformService).isWindows) { + debugConfiguration.debugOptions = Array.isArray(debugConfiguration.debugOptions) ? debugConfiguration.debugOptions : []; + debugConfiguration.debugOptions.push(DebugOptions.FixFilePathCase); + } } } diff --git a/src/client/unittests/common/debugLauncher.ts b/src/client/unittests/common/debugLauncher.ts index d27dd8af96d7..12cc2f59cb83 100644 --- a/src/client/unittests/common/debugLauncher.ts +++ b/src/client/unittests/common/debugLauncher.ts @@ -4,6 +4,7 @@ import { Uri } from 'vscode'; import { IDebugService, IWorkspaceService } from '../../common/application/types'; import { EXTENSION_ROOT_DIR } from '../../common/constants'; import { IConfigurationService } from '../../common/types'; +import { DebugOptions } from '../../debugger/Common/Contracts'; import { IServiceContainer } from '../../ioc/types'; import { ITestDebugLauncher, LaunchOptions, TestProvider } from './types'; @@ -40,7 +41,7 @@ export class DebugLauncher implements ITestDebugLauncher { cwd, args: debugArgs, console: 'none', - debugOptions: ['RedirectOutput'] + debugOptions: [DebugOptions.RedirectOutput] }).then(() => void (0)); } private fixArgs(args: string[], testProvider: TestProvider, useExperimentalDebugger: boolean): string[] { diff --git a/src/test/debugger/configProvider/provider.test.ts b/src/test/debugger/configProvider/provider.test.ts index 050631d18d01..ebe4a0ffbdfe 100644 --- a/src/test/debugger/configProvider/provider.test.ts +++ b/src/test/debugger/configProvider/provider.test.ts @@ -14,6 +14,7 @@ import { PythonLanguage } from '../../../client/common/constants'; import { IFileSystem, IPlatformService } from '../../../client/common/platform/types'; import { IConfigurationService, IPythonSettings } from '../../../client/common/types'; import { PythonDebugConfigurationProvider, PythonV2DebugConfigurationProvider } from '../../../client/debugger'; +import { DebugOptions } from '../../../client/debugger/Common/Contracts'; import { IServiceContainer } from '../../../client/ioc/types'; [ @@ -260,24 +261,8 @@ import { IServiceContainer } from '../../../client/ioc/types'; expect(debugConfig).to.have.property('stopOnEntry', false); expect(debugConfig).to.have.property('debugOptions'); - expect((debugConfig as any).debugOptions).to.be.deep.equal(['RedirectOutput']); - }); - test('Test redirection of output', async () => { - if (provider.debugType === 'python') { - return; - } - const pythonPath = `PythonPath_${new Date().toString()}`; - const workspaceFolder = createMoqWorkspaceFolder(__dirname); - const pythonFile = 'xyz.py'; - setupIoc(pythonPath); - setupActiveEditor(pythonFile, PythonLanguage.language); - - const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { debugOptions: ['RedirectOutput'] } as any); - - expect(debugConfig).to.have.property('redirectOutput'); - expect((debugConfig as any).redirectOutput).to.be.equal(true, 'invalid value'); + expect((debugConfig as any).debugOptions).to.be.deep.equal([DebugOptions.RedirectOutput]); }); - async function testFixFilePathCase(isWindows: boolean, isMac: boolean, isLinux: boolean) { const pythonPath = `PythonPath_${new Date().toString()}`; const workspaceFolder = createMoqWorkspaceFolder(__dirname); @@ -286,9 +271,11 @@ import { IServiceContainer } from '../../../client/ioc/types'; setupActiveEditor(pythonFile, PythonLanguage.language); const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, {} as DebugConfiguration); - - expect(debugConfig).to.have.property('fixFilePathCase'); - expect((debugConfig as any).fixFilePathCase).to.be.equal(isWindows, 'invalid value (true only for windows)'); + if (isWindows) { + expect(debugConfig).to.have.property('debugOptions').contains(DebugOptions.FixFilePathCase); + } else { + expect(debugConfig).to.have.property('debugOptions').not.contains(DebugOptions.FixFilePathCase); + } } test('Test fixFilePathCase for Windows', async () => { if (provider.debugType === 'python') { @@ -318,7 +305,7 @@ import { IServiceContainer } from '../../../client/ioc/types'; setupIoc(pythonPath, isWindows, isMac, isLinux); setupActiveEditor(pythonFile, PythonLanguage.language); - const options = addPyramidDebugOption ? { debugOptions: ['Pyramid'] } : {}; + const options = addPyramidDebugOption ? { debugOptions: [DebugOptions.Pyramid] } : {}; fileSystem.setup(fs => fs.fileExistsSync(TypeMoq.It.isValue(pythonPath))).returns(() => pythonPathExists); const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, options as any as DebugConfiguration); diff --git a/src/test/debugger/misc.test.ts b/src/test/debugger/misc.test.ts index aa26b79acba4..90fc5669447f 100644 --- a/src/test/debugger/misc.test.ts +++ b/src/test/debugger/misc.test.ts @@ -14,7 +14,7 @@ import { noop } from '../../client/common/core.utils'; import { IS_WINDOWS } from '../../client/common/platform/constants'; import { FileSystem } from '../../client/common/platform/fileSystem'; import { PlatformService } from '../../client/common/platform/platformService'; -import { LaunchRequestArguments } from '../../client/debugger/Common/Contracts'; +import { DebugOptions, LaunchRequestArguments } from '../../client/debugger/Common/Contracts'; import { sleep } from '../common'; import { IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; import { DEBUGGER_TIMEOUT } from './common/constants'; @@ -78,7 +78,7 @@ let testCounter = 0; program: path.join(debugFilesPath, pythonFile), cwd: debugFilesPath, stopOnEntry, - debugOptions: ['RedirectOutput'], + debugOptions: [DebugOptions.RedirectOutput], pythonPath: 'python', args: [], env, diff --git a/src/test/debugger/portAndHost.test.ts b/src/test/debugger/portAndHost.test.ts index edfd2a2a158e..6d2d79491bbf 100644 --- a/src/test/debugger/portAndHost.test.ts +++ b/src/test/debugger/portAndHost.test.ts @@ -8,7 +8,7 @@ import * as net from 'net'; import * as path from 'path'; import { DebugClient } from 'vscode-debugadapter-testsupport'; import { noop } from '../../client/common/core.utils'; -import { LaunchRequestArguments } from '../../client/debugger/Common/Contracts'; +import { DebugOptions, LaunchRequestArguments } from '../../client/debugger/Common/Contracts'; import { IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; import { DEBUGGER_TIMEOUT } from './common/constants'; @@ -49,7 +49,7 @@ const EXPERIMENTAL_DEBUG_ADAPTER = path.join(__dirname, '..', '..', 'client', 'd program: path.join(debugFilesPath, pythonFile), cwd: debugFilesPath, stopOnEntry, - debugOptions: ['RedirectOutput'], + debugOptions: [DebugOptions.RedirectOutput], pythonPath: 'python', args: [], envFile: '', diff --git a/src/test/unittests/common/debugLauncher.test.ts b/src/test/unittests/common/debugLauncher.test.ts index 25e76223049b..82a6299e4b83 100644 --- a/src/test/unittests/common/debugLauncher.test.ts +++ b/src/test/unittests/common/debugLauncher.test.ts @@ -14,6 +14,7 @@ import { IDebugService, IWorkspaceService } from '../../../client/common/applica import { EXTENSION_ROOT_DIR } from '../../../client/common/constants'; import '../../../client/common/extensions'; import { IConfigurationService, IPythonSettings, IUnitTestSettings } from '../../../client/common/types'; +import { DebugOptions } from '../../../client/debugger/Common/Contracts'; import { IServiceContainer } from '../../../client/ioc/types'; import { DebugLauncher } from '../../../client/unittests/common/debugLauncher'; import { TestProvider } from '../../../client/unittests/common/types'; @@ -47,7 +48,7 @@ suite('Unit Tests - Debug Launcher', () => { }); function setupDebugManager(workspaceFolder: WorkspaceFolder, name: string, type: string, request: string, program: string, cwd: string, - args: string[], console, debugOptions: string[], + args: string[], console, debugOptions: DebugOptions[], testProvider: TestProvider, useExperimentalDebugger: boolean) { const debugArgs = testProvider === 'unittest' && useExperimentalDebugger ? args.filter(item => item !== '--debug') : args; @@ -96,7 +97,7 @@ suite('Unit Tests - Debug Launcher', () => { const args = ['/one/two/three/testfile.py']; const cwd = workspaceFolders[0].uri.fsPath; const program = testLaunchScript; - setupDebugManager(workspaceFolders[0], 'Debug Unit Test', debuggerType, 'launch', program, cwd, args, 'none', ['RedirectOutput'], testProvider, useExperimentalDebugger); + setupDebugManager(workspaceFolders[0], 'Debug Unit Test', debuggerType, 'launch', program, cwd, args, 'none', [DebugOptions.RedirectOutput], testProvider, useExperimentalDebugger); debugLauncher.launchDebugger({ cwd, args, testProvider }).ignoreErrors(); debugService.verifyAll(); @@ -111,7 +112,7 @@ suite('Unit Tests - Debug Launcher', () => { const args = ['/one/two/three/testfile.py', '--debug', '1']; const cwd = workspaceFolders[0].uri.fsPath; const program = testLaunchScript; - setupDebugManager(workspaceFolders[0], 'Debug Unit Test', debuggerType, 'launch', program, cwd, args, 'none', ['RedirectOutput'], testProvider, useExperimentalDebugger); + setupDebugManager(workspaceFolders[0], 'Debug Unit Test', debuggerType, 'launch', program, cwd, args, 'none', [DebugOptions.RedirectOutput], testProvider, useExperimentalDebugger); debugLauncher.launchDebugger({ cwd, args, testProvider }).ignoreErrors(); debugService.verifyAll(); From 9042df6d18d73ad2d0a3f563f64cda45cc746bac Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 26 Mar 2018 13:15:10 -0700 Subject: [PATCH 067/433] Ensure file paths are not sent in telemetry when running unit tests (#1197) * :bug: 1st arg of command handlers is {} or Uri based on how invoked * :memo: change log * Fixes #1180 --- news/3 Code Health/1180.md | 1 + src/client/unittests/codeLenses/testFiles.ts | 21 +++++++++-------- src/client/unittests/common/testUtils.ts | 18 +++++++-------- src/client/unittests/display/picker.ts | 13 +++++------ src/client/unittests/main.ts | 24 ++++++++++---------- 5 files changed, 38 insertions(+), 39 deletions(-) create mode 100644 news/3 Code Health/1180.md diff --git a/news/3 Code Health/1180.md b/news/3 Code Health/1180.md new file mode 100644 index 000000000000..9d27520795b0 --- /dev/null +++ b/news/3 Code Health/1180.md @@ -0,0 +1 @@ +Ensure file paths are not sent in telemetry when running unit tests. diff --git a/src/client/unittests/codeLenses/testFiles.ts b/src/client/unittests/codeLenses/testFiles.ts index 10f115148594..90b7367140f1 100644 --- a/src/client/unittests/codeLenses/testFiles.ts +++ b/src/client/unittests/codeLenses/testFiles.ts @@ -1,7 +1,8 @@ 'use strict'; -import { CancellationToken, CancellationTokenSource, CodeLens, CodeLensProvider, Event, EventEmitter, Position, Range, SymbolInformation, SymbolKind, TextDocument, workspace } from 'vscode'; -import { Uri } from 'vscode'; +// tslint:disable:no-object-literal-type-assertion + +import { CancellationToken, CancellationTokenSource, CodeLens, CodeLensProvider, Event, EventEmitter, Position, Range, SymbolInformation, SymbolKind, TextDocument, Uri, workspace } from 'vscode'; import * as constants from '../../common/constants'; import { PythonSymbolProvider } from '../../providers/symbolProvider'; import { CommandSource } from '../common/constants'; @@ -108,12 +109,12 @@ export class TestFileCodeLensProvider implements CodeLensProvider { new CodeLens(range, { title: getTestStatusIcon(cls.status) + constants.Text.CodeLensRunUnitTest, command: constants.Commands.Tests_Run, - arguments: [CommandSource.codelens, file, { testSuite: [cls] }] + arguments: [undefined, CommandSource.codelens, file, { testSuite: [cls] }] }), new CodeLens(range, { title: getTestStatusIcon(cls.status) + constants.Text.CodeLensDebugUnitTest, command: constants.Commands.Tests_Debug, - arguments: [CommandSource.codelens, file, { testSuite: [cls] }] + arguments: [undefined, CommandSource.codelens, file, { testSuite: [cls] }] }) ]; } @@ -182,12 +183,12 @@ function getFunctionCodeLens(file: Uri, functionsAndSuites: FunctionsAndSuites, new CodeLens(range, { title: getTestStatusIcon(fn.status) + constants.Text.CodeLensRunUnitTest, command: constants.Commands.Tests_Run, - arguments: [CommandSource.codelens, file, { testFunction: [fn] }] + arguments: [undefined, CommandSource.codelens, file, { testFunction: [fn] }] }), new CodeLens(range, { title: getTestStatusIcon(fn.status) + constants.Text.CodeLensDebugUnitTest, command: constants.Commands.Tests_Debug, - arguments: [CommandSource.codelens, file, { testFunction: [fn] }] + arguments: [undefined, CommandSource.codelens, file, { testFunction: [fn] }] }) ]; } @@ -203,12 +204,12 @@ function getFunctionCodeLens(file: Uri, functionsAndSuites: FunctionsAndSuites, new CodeLens(range, { title: constants.Text.CodeLensRunUnitTest, command: constants.Commands.Tests_Run, - arguments: [CommandSource.codelens, file, { testFunction: functions }] + arguments: [undefined, CommandSource.codelens, file, { testFunction: functions }] }), new CodeLens(range, { title: constants.Text.CodeLensDebugUnitTest, command: constants.Commands.Tests_Debug, - arguments: [CommandSource.codelens, file, { testFunction: functions }] + arguments: [undefined, CommandSource.codelens, file, { testFunction: functions }] }) ]; } @@ -218,12 +219,12 @@ function getFunctionCodeLens(file: Uri, functionsAndSuites: FunctionsAndSuites, new CodeLens(range, { title: `${getTestStatusIcons(functions)}${constants.Text.CodeLensRunUnitTest} (Multiple)`, command: constants.Commands.Tests_Picker_UI, - arguments: [CommandSource.codelens, file, functions] + arguments: [undefined, CommandSource.codelens, file, functions] }), new CodeLens(range, { title: `${getTestStatusIcons(functions)}${constants.Text.CodeLensDebugUnitTest} (Multiple)`, command: constants.Commands.Tests_Picker_UI_Debug, - arguments: [CommandSource.codelens, file, functions] + arguments: [undefined, CommandSource.codelens, file, functions] }) ]; } diff --git a/src/client/unittests/common/testUtils.ts b/src/client/unittests/common/testUtils.ts index 385185d5555c..c2eb115c924b 100644 --- a/src/client/unittests/common/testUtils.ts +++ b/src/client/unittests/common/testUtils.ts @@ -1,15 +1,11 @@ import { inject, injectable, named } from 'inversify'; import * as path from 'path'; -import * as vscode from 'vscode'; -import { Uri, workspace } from 'vscode'; -import { window } from 'vscode'; +import { commands, Uri, window, workspace } from 'vscode'; import * as constants from '../../common/constants'; -import { IUnitTestSettings } from '../../common/types'; -import { Product } from '../../common/types'; +import { IUnitTestSettings, Product } from '../../common/types'; import { CommandSource } from './constants'; import { TestFlatteningVisitor } from './testVisitors/flatteningVisitor'; -import { ITestVisitor, TestFile, TestFolder, TestProvider, Tests, TestSettingsPropertyNames, TestsToRun, UnitTestProduct } from './types'; -import { ITestsHelper } from './types'; +import { ITestsHelper, ITestVisitor, TestFile, TestFolder, TestProvider, Tests, TestSettingsPropertyNames, TestsToRun, UnitTestProduct } from './types'; export async function selectTestWorkspace(): Promise { if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0) { @@ -24,9 +20,9 @@ export async function selectTestWorkspace(): Promise { } export function displayTestErrorMessage(message: string) { - vscode.window.showErrorMessage(message, constants.Button_Text_Tests_View_Output).then(action => { + window.showErrorMessage(message, constants.Button_Text_Tests_View_Output).then(action => { if (action === constants.Button_Text_Tests_View_Output) { - vscode.commands.executeCommand(constants.Commands.Tests_ViewOutput, CommandSource.ui); + commands.executeCommand(constants.Commands.Tests_ViewOutput, undefined, CommandSource.ui); } }); @@ -44,7 +40,7 @@ export function convertFileToPackage(filePath: string): string { @injectable() export class TestsHelper implements ITestsHelper { - constructor( @inject(ITestVisitor) @named('TestFlatteningVisitor') private flatteningVisitor: TestFlatteningVisitor) { } + constructor(@inject(ITestVisitor) @named('TestFlatteningVisitor') private flatteningVisitor: TestFlatteningVisitor) { } public parseProviderName(product: UnitTestProduct): TestProvider { switch (product) { case Product.nosetest: return 'nosetest'; @@ -96,6 +92,7 @@ export class TestsHelper implements ITestsHelper { public flattenTestFiles(testFiles: TestFile[]): Tests { testFiles.forEach(testFile => this.flatteningVisitor.visitTestFile(testFile)); + // tslint:disable-next-line:no-object-literal-type-assertion const tests = { testFiles: testFiles, testFunctions: this.flatteningVisitor.flattenedTestFunctions, @@ -165,6 +162,7 @@ export class TestsHelper implements ITestsHelper { if (testFns.length > 0) { return { testFunction: testFns }; } // Just return this as a test file. + // tslint:disable-next-line:no-object-literal-type-assertion return { testFile: [{ name: name, nameToRun: name, functions: [], suites: [], xmlName: name, fullPath: '', time: 0 }] }; } } diff --git a/src/client/unittests/display/picker.ts b/src/client/unittests/display/picker.ts index 2b3d47652abf..7c054b88a06e 100644 --- a/src/client/unittests/display/picker.ts +++ b/src/client/unittests/display/picker.ts @@ -1,6 +1,5 @@ import * as path from 'path'; -import { QuickPickItem, Uri, window } from 'vscode'; -import * as vscode from 'vscode'; +import { commands, QuickPickItem, Uri, window } from 'vscode'; import * as constants from '../../common/constants'; import { CommandSource } from '../common/constants'; import { FlattenedTestFunction, ITestCollectionStorageService, TestFile, TestFunction, Tests, TestStatus, TestsToRun } from '../common/types'; @@ -10,7 +9,7 @@ export class TestDisplay { public displayStopTestUI(workspace: Uri, message: string) { window.showQuickPick([message]).then(item => { if (item === message) { - vscode.commands.executeCommand(constants.Commands.Tests_Stop, workspace); + commands.executeCommand(constants.Commands.Tests_Stop, undefined, workspace); } }); } @@ -194,7 +193,7 @@ function onItemSelected(cmdSource: CommandSource, wkspace: Uri, selection: TestI } let cmd = ''; // tslint:disable-next-line:no-any - const args: any[] = [cmdSource, wkspace]; + const args: any[] = [undefined, cmdSource, wkspace]; switch (selection.type) { case Type.Null: { return; @@ -221,13 +220,13 @@ function onItemSelected(cmdSource: CommandSource, wkspace: Uri, selection: TestI } case Type.RunMethod: { cmd = constants.Commands.Tests_Run; - // tslint:disable-next-line:prefer-type-cast + // tslint:disable-next-line:prefer-type-cast no-object-literal-type-assertion args.push({ testFunction: [selection.fn.testFunction] } as TestsToRun); break; } case Type.DebugMethod: { cmd = constants.Commands.Tests_Debug; - // tslint:disable-next-line:prefer-type-cast + // tslint:disable-next-line:prefer-type-cast no-object-literal-type-assertion args.push({ testFunction: [selection.fn.testFunction] } as TestsToRun); args.push(true); break; @@ -237,5 +236,5 @@ function onItemSelected(cmdSource: CommandSource, wkspace: Uri, selection: TestI } } - vscode.commands.executeCommand(cmd, ...args); + commands.executeCommand(cmd, ...args); } diff --git a/src/client/unittests/main.ts b/src/client/unittests/main.ts index 71bb3c2339b2..81b8ad6a9946 100644 --- a/src/client/unittests/main.ts +++ b/src/client/unittests/main.ts @@ -87,34 +87,34 @@ function dispose() { } function registerCommands(): vscode.Disposable[] { const disposables: Disposable[] = []; - disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Discover, (cmdSource: CommandSource = CommandSource.commandPalette, resource?: Uri) => { + disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Discover, (_, cmdSource: CommandSource = CommandSource.commandPalette, resource?: Uri) => { // Ignore the exceptions returned. // This command will be invoked else where in the extension. // tslint:disable-next-line:no-empty discoverTests(cmdSource, resource, true, true).catch(() => { }); })); - disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Run_Failed, (cmdSource: CommandSource = CommandSource.commandPalette, resource: Uri) => runTestsImpl(cmdSource, resource, undefined, true))); + disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Run_Failed, (_, cmdSource: CommandSource = CommandSource.commandPalette, resource: Uri) => runTestsImpl(cmdSource, resource, undefined, true))); // tslint:disable-next-line:no-unnecessary-callback-wrapper - disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Run, (cmdSource: CommandSource = CommandSource.commandPalette, file: Uri, testToRun?: TestsToRun) => runTestsImpl(cmdSource, file, testToRun))); - disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Debug, (cmdSource: CommandSource = CommandSource.commandPalette, file: Uri, testToRun: TestsToRun) => runTestsImpl(cmdSource, file, testToRun, false, true))); + disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Run, (_, cmdSource: CommandSource = CommandSource.commandPalette, file: Uri, testToRun?: TestsToRun) => runTestsImpl(cmdSource, file, testToRun))); + disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Debug, (_, cmdSource: CommandSource = CommandSource.commandPalette, file: Uri, testToRun: TestsToRun) => runTestsImpl(cmdSource, file, testToRun, false, true))); // tslint:disable-next-line:no-unnecessary-callback-wrapper disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_View_UI, () => displayUI(CommandSource.commandPalette))); // tslint:disable-next-line:no-unnecessary-callback-wrapper - disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Picker_UI, (cmdSource: CommandSource = CommandSource.commandPalette, file: Uri, testFunctions: TestFunction[]) => displayPickerUI(cmdSource, file, testFunctions))); - disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Picker_UI_Debug, (cmdSource: CommandSource = CommandSource.commandPalette, file: Uri, testFunctions: TestFunction[]) => displayPickerUI(cmdSource, file, testFunctions, true))); + disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Picker_UI, (_, cmdSource: CommandSource = CommandSource.commandPalette, file: Uri, testFunctions: TestFunction[]) => displayPickerUI(cmdSource, file, testFunctions))); + disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Picker_UI_Debug, (_, cmdSource: CommandSource = CommandSource.commandPalette, file: Uri, testFunctions: TestFunction[]) => displayPickerUI(cmdSource, file, testFunctions, true))); // tslint:disable-next-line:no-unnecessary-callback-wrapper - disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Stop, (resource: Uri) => stopTests(resource))); + disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Stop, (_, resource: Uri) => stopTests(resource))); // tslint:disable-next-line:no-unnecessary-callback-wrapper - disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_ViewOutput, (cmdSource: CommandSource = CommandSource.commandPalette) => viewOutput(cmdSource))); + disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_ViewOutput, (_, cmdSource: CommandSource = CommandSource.commandPalette) => viewOutput(cmdSource))); disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Ask_To_Stop_Discovery, () => displayStopUI('Stop discovering tests'))); disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Ask_To_Stop_Test, () => displayStopUI('Stop running tests'))); // tslint:disable-next-line:no-unnecessary-callback-wrapper - disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Select_And_Run_Method, (cmdSource: CommandSource = CommandSource.commandPalette, resource: Uri) => selectAndRunTestMethod(cmdSource, resource))); - disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Select_And_Debug_Method, (cmdSource: CommandSource = CommandSource.commandPalette, resource: Uri) => selectAndRunTestMethod(cmdSource, resource, true))); + disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Select_And_Run_Method, (_, cmdSource: CommandSource = CommandSource.commandPalette, resource: Uri) => selectAndRunTestMethod(cmdSource, resource))); + disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Select_And_Debug_Method, (_, cmdSource: CommandSource = CommandSource.commandPalette, resource: Uri) => selectAndRunTestMethod(cmdSource, resource, true))); // tslint:disable-next-line:no-unnecessary-callback-wrapper - disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Select_And_Run_File, (cmdSource: CommandSource = CommandSource.commandPalette) => selectAndRunTestFile(cmdSource))); + disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Select_And_Run_File, (_, cmdSource: CommandSource = CommandSource.commandPalette) => selectAndRunTestFile(cmdSource))); // tslint:disable-next-line:no-unnecessary-callback-wrapper - disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Run_Current_File, (cmdSource: CommandSource = CommandSource.commandPalette) => runCurrentTestFile(cmdSource))); + disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Run_Current_File, (_, cmdSource: CommandSource = CommandSource.commandPalette) => runCurrentTestFile(cmdSource))); return disposables; } From 4b8925958ff9ed1ef2cdf13665cf1acca73ca9cb Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 26 Mar 2018 14:29:45 -0700 Subject: [PATCH 068/433] Add support for django and flask template debugging in experimental debugger (#1200) * :sparkles: django and flask debugging * :memo: change log * Update to contain flask as default debug config [skip ci] * Fixes #1189 * Fixes #1190 * Fixes #1198 --- news/1 Enhancements/1189.md | 1 + news/1 Enhancements/1190.md | 1 + news/3 Code Health/1198.md | 1 + package.json | 58 +++++++++++++++++-- src/client/debugger/Common/Contracts.ts | 4 +- .../debugger/DebugClients/LocalDebugClient.ts | 8 ++- src/client/debugger/Main.ts | 2 +- .../configProviders/pythonV2Provider.ts | 6 +- .../debugger/configProvider/provider.test.ts | 16 +++++ 9 files changed, 86 insertions(+), 11 deletions(-) create mode 100644 news/1 Enhancements/1189.md create mode 100644 news/1 Enhancements/1190.md create mode 100644 news/3 Code Health/1198.md diff --git a/news/1 Enhancements/1189.md b/news/1 Enhancements/1189.md new file mode 100644 index 000000000000..6d306e55189c --- /dev/null +++ b/news/1 Enhancements/1189.md @@ -0,0 +1 @@ +Add support for Django Template debugging in experimental debugger. diff --git a/news/1 Enhancements/1190.md b/news/1 Enhancements/1190.md new file mode 100644 index 000000000000..a1b144662fa5 --- /dev/null +++ b/news/1 Enhancements/1190.md @@ -0,0 +1 @@ +Add support for Flask Template debugging in experimental debugger. diff --git a/news/3 Code Health/1198.md b/news/3 Code Health/1198.md new file mode 100644 index 000000000000..b14083165f25 --- /dev/null +++ b/news/3 Code Health/1198.md @@ -0,0 +1 @@ +Change `DjangoDebugging` to `Django` in `debugOptions` of launch.json. diff --git a/package.json b/package.json index 62ca582bcf96..c59ad5dfcea9 100644 --- a/package.json +++ b/package.json @@ -363,7 +363,7 @@ ], "debugOptions": [ "RedirectOutput", - "DjangoDebugging" + "Django" ] } }, @@ -515,7 +515,7 @@ "RedirectOutput", "DebugStdLib", "BreakOnSystemExitZero", - "DjangoDebugging", + "Django", "Sudo", "IgnoreDjangoTemplateWarnings", "Pyramid" @@ -663,7 +663,7 @@ ], "debugOptions": [ "RedirectOutput", - "DjangoDebugging" + "Django" ] }, { @@ -781,6 +781,32 @@ "runserver", "--noreload", "--nothreading" + ], + "debugOptions": [ + "RedirectOutput", + "Django" + ] + } + }, + { + "label": "Python Experimental: Flask", + "description": "%python.snippet.launch.flask.description%", + "body": { + "name": "Flask", + "type": "pythonExperimental", + "request": "launch", + "module": "flask", + "env": { + "FLASK_APP": "^\"\\${workspaceFolder}/app.py\"" + }, + "args": [ + "run", + "--no-debugger", + "--no-reload" + ], + "debugOptions": [ + "RedirectOutput", + "Flask" ] } }, @@ -903,8 +929,8 @@ "enum": [ "RedirectOutput", "DebugStdLib", - "DjangoDebugging", - "FlaskDebugging", + "Django", + "Flask", "Sudo", "Pyramid" ] @@ -957,6 +983,28 @@ "runserver", "--noreload", "--nothreading" + ], + "debugOptions": [ + "RedirectOutput", + "Django" + ] + }, + { + "label": "Python Experimental: Flask", + "type": "pythonExperimental", + "request": "launch", + "module": "flask", + "env": { + "FLASK_APP": "${workspaceFolder}/app.py" + }, + "args": [ + "run", + "--no-debugger", + "--no-reload" + ], + "debugOptions": [ + "RedirectOutput", + "Flask" ] }, { diff --git a/src/client/debugger/Common/Contracts.ts b/src/client/debugger/Common/Contracts.ts index bc1820f4725f..1e27d9ba5ca3 100644 --- a/src/client/debugger/Common/Contracts.ts +++ b/src/client/debugger/Common/Contracts.ts @@ -40,8 +40,8 @@ export enum DebugOptions { WaitOnAbnormalExit = 'WaitOnAbnormalExit', WaitOnNormalExit = 'WaitOnNormalExit', RedirectOutput = 'RedirectOutput', - DjangoDebugging = 'DjangoDebugging', - FlaskDebugging = 'FlaskDebugging', + Django = 'Django', + Flask = 'Flask', DebugStdLib = 'DebugStdLib', BreakOnSystemExitZero = 'BreakOnSystemExitZero', Sudo = 'Sudo', diff --git a/src/client/debugger/DebugClients/LocalDebugClient.ts b/src/client/debugger/DebugClients/LocalDebugClient.ts index 4101b513786d..e74ce78a63f5 100644 --- a/src/client/debugger/DebugClients/LocalDebugClient.ts +++ b/src/client/debugger/DebugClients/LocalDebugClient.ts @@ -157,12 +157,16 @@ export class LocalDebugClient extends DebugClient { } // tslint:disable-next-line:member-ordering protected buildLauncherArguments(): string[] { - const vsDebugOptions = [DebugOptions.RedirectOutput]; + const vsDebugOptions: string[] = [DebugOptions.RedirectOutput]; if (Array.isArray(this.args.debugOptions)) { this.args.debugOptions.filter(opt => VALID_DEBUG_OPTIONS.indexOf(opt) >= 0) .forEach(item => vsDebugOptions.push(item)); } - + const djangoIndex = vsDebugOptions.indexOf(DebugOptions.Django); + // PTVSD expects the string `DjangoDebugging` + if (djangoIndex >= 0) { + vsDebugOptions[djangoIndex] = 'DjangoDebugging'; + } const programArgs = Array.isArray(this.args.args) && this.args.args.length > 0 ? this.args.args : []; if (typeof this.args.module === 'string' && this.args.module.length > 0) { return [vsDebugOptions.join(','), '-m', this.args.module].concat(programArgs); diff --git a/src/client/debugger/Main.ts b/src/client/debugger/Main.ts index f20b7397c0e3..eaadb8cc028e 100644 --- a/src/client/debugger/Main.ts +++ b/src/client/debugger/Main.ts @@ -332,7 +332,7 @@ export class PythonDebugger extends LoggingDebugSession { let isDjangoFile = false; if (this.launchArgs && Array.isArray(this.launchArgs.debugOptions) && - this.launchArgs.debugOptions.indexOf(DebugOptions.DjangoDebugging) >= 0) { + this.launchArgs.debugOptions.indexOf(DebugOptions.Django) >= 0) { isDjangoFile = filePath.toUpperCase().endsWith(".HTML"); } if (this.attachArgs != null && diff --git a/src/client/debugger/configProviders/pythonV2Provider.ts b/src/client/debugger/configProviders/pythonV2Provider.ts index f9d124d95e0e..36d91ba73a01 100644 --- a/src/client/debugger/configProviders/pythonV2Provider.ts +++ b/src/client/debugger/configProviders/pythonV2Provider.ts @@ -19,11 +19,15 @@ export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvide super.provideDefaults(workspaceFolder, debugConfiguration); debugConfiguration.stopOnEntry = false; + debugConfiguration.debugOptions = Array.isArray(debugConfiguration.debugOptions) ? debugConfiguration.debugOptions : []; // Add PTVSD specific flags. if (this.serviceContainer.get(IPlatformService).isWindows) { - debugConfiguration.debugOptions = Array.isArray(debugConfiguration.debugOptions) ? debugConfiguration.debugOptions : []; debugConfiguration.debugOptions.push(DebugOptions.FixFilePathCase); } + if (debugConfiguration.module && debugConfiguration.module.toUpperCase() === 'FLASK' + && debugConfiguration.debugOptions.indexOf(DebugOptions.Flask) === -1) { + debugConfiguration.debugOptions.push(DebugOptions.Flask); + } } } diff --git a/src/test/debugger/configProvider/provider.test.ts b/src/test/debugger/configProvider/provider.test.ts index ebe4a0ffbdfe..628eba9676da 100644 --- a/src/test/debugger/configProvider/provider.test.ts +++ b/src/test/debugger/configProvider/provider.test.ts @@ -342,5 +342,21 @@ import { IServiceContainer } from '../../../client/ioc/types'; test('Program is set to executable name for Pyramid when python exec does not exist (Mac)', async () => { await testPyramidConfiguration(false, false, true, true, false, true); }); + test('Auto detect flask debugging', async () => { + if (provider.debugType === 'python') { + return; + } + const pythonPath = `PythonPath_${new Date().toString()}`; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + const pythonFile = 'xyz.py'; + setupIoc(pythonPath); + setupActiveEditor(pythonFile, PythonLanguage.language); + + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { module: 'flask' } as any as DebugConfiguration); + + expect(debugConfig).to.have.property('debugOptions'); + expect((debugConfig as any).debugOptions).contains(DebugOptions.RedirectOutput); + expect((debugConfig as any).debugOptions).contains(DebugOptions.Flask); + }); }); }); From 98b445c2e8ee2bc0d619f674304d5c0873ef23b1 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 26 Mar 2018 16:39:37 -0700 Subject: [PATCH 069/433] Update links to linters --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4e66c84e5a8f..862e9bebf45e 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ contributors (if you would like to contribute a translation, see the + Auto formatting of code upon saving changes (default to 'Off') + Use either [yapf](https://pypi.io/project/yapf/) or [autopep8](https://pypi.io/project/autopep8/) for code formatting (defaults to autopep8) * Linting - + Support for multiple linters with custom settings (default is [Pylint](https://pypi.io/project/pylint/), but [Prospector](https://pypi.io/project/prospector/), [pycodestyle](https://pypi.io/project/pycodestyle/), [Flake8](https://pypi.io/project/flake8/), [pylama](https://github.com/klen/pylama), [pydocstyle](https://pypi.io/project/pydocstyle/), and [mypy](http://mypy-lang.org/) are also supported) + + Support for multiple linters with custom settings (default is [Pylint](https://pypi.org/project/pylint/), but [Prospector](https://pypi.org/project/prospector/), [Flake8](https://pypi.io/project/flake8/), [pylama](https://github.com/klen/pylama), [pydocstyle](https://pypi.org/project/pydocstyle/), and [mypy](https://pypi.org/project/mypy/) are also supported) * Debugging + Watch window + Evaluate expressions From 9a35c97ab76f4365d7379324e3ec279ebfdaf6dd Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 27 Mar 2018 11:13:46 -0700 Subject: [PATCH 070/433] Have announce be more tolerant of other directories (#1212) --- news/announce.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/news/announce.py b/news/announce.py index a1081eaa52e0..7d1d12e22770 100644 --- a/news/announce.py +++ b/news/announce.py @@ -5,6 +5,7 @@ import pathlib import re import subprocess +import sys import types import click @@ -50,7 +51,9 @@ def sections(directory): continue position, sep, title = path.name.partition(' ') if not sep: - raise ValueError(f'directory is missing position part: {path.name!r}') + print(f'directory name {path.name!r} is missing ranking; skipping', + file=sys.stderr) + continue found.append(SectionTitle(int(position), title, path)) return sorted(found, key=operator.attrgetter('index')) From 70a2a55137dade6c6195ae4f6e4440f33ec2c75d Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 27 Mar 2018 12:51:56 -0700 Subject: [PATCH 071/433] Add support for Jinja template debugging (#1211) Fixes #1210 --- news/1 Enhancements/1210.md | 1 + package.json | 6 +++--- src/client/debugger/Common/Contracts.ts | 2 +- src/client/debugger/configProviders/pythonV2Provider.ts | 4 ++-- src/test/debugger/configProvider/provider.test.ts | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) create mode 100644 news/1 Enhancements/1210.md diff --git a/news/1 Enhancements/1210.md b/news/1 Enhancements/1210.md new file mode 100644 index 000000000000..68b06658e94b --- /dev/null +++ b/news/1 Enhancements/1210.md @@ -0,0 +1 @@ +Add support for Jinja template debugging. \ No newline at end of file diff --git a/package.json b/package.json index c59ad5dfcea9..86cc43cbf4b0 100644 --- a/package.json +++ b/package.json @@ -806,7 +806,7 @@ ], "debugOptions": [ "RedirectOutput", - "Flask" + "Jinja" ] } }, @@ -930,7 +930,7 @@ "RedirectOutput", "DebugStdLib", "Django", - "Flask", + "Jinja", "Sudo", "Pyramid" ] @@ -1004,7 +1004,7 @@ ], "debugOptions": [ "RedirectOutput", - "Flask" + "Jinja" ] }, { diff --git a/src/client/debugger/Common/Contracts.ts b/src/client/debugger/Common/Contracts.ts index 1e27d9ba5ca3..d2b0774845d8 100644 --- a/src/client/debugger/Common/Contracts.ts +++ b/src/client/debugger/Common/Contracts.ts @@ -41,7 +41,7 @@ export enum DebugOptions { WaitOnNormalExit = 'WaitOnNormalExit', RedirectOutput = 'RedirectOutput', Django = 'Django', - Flask = 'Flask', + Jinja = 'Jinja', DebugStdLib = 'DebugStdLib', BreakOnSystemExitZero = 'BreakOnSystemExitZero', Sudo = 'Sudo', diff --git a/src/client/debugger/configProviders/pythonV2Provider.ts b/src/client/debugger/configProviders/pythonV2Provider.ts index 36d91ba73a01..0d95f539e80b 100644 --- a/src/client/debugger/configProviders/pythonV2Provider.ts +++ b/src/client/debugger/configProviders/pythonV2Provider.ts @@ -26,8 +26,8 @@ export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvide debugConfiguration.debugOptions.push(DebugOptions.FixFilePathCase); } if (debugConfiguration.module && debugConfiguration.module.toUpperCase() === 'FLASK' - && debugConfiguration.debugOptions.indexOf(DebugOptions.Flask) === -1) { - debugConfiguration.debugOptions.push(DebugOptions.Flask); + && debugConfiguration.debugOptions.indexOf(DebugOptions.Jinja) === -1) { + debugConfiguration.debugOptions.push(DebugOptions.Jinja); } } } diff --git a/src/test/debugger/configProvider/provider.test.ts b/src/test/debugger/configProvider/provider.test.ts index 628eba9676da..c8670b6f0e68 100644 --- a/src/test/debugger/configProvider/provider.test.ts +++ b/src/test/debugger/configProvider/provider.test.ts @@ -356,7 +356,7 @@ import { IServiceContainer } from '../../../client/ioc/types'; expect(debugConfig).to.have.property('debugOptions'); expect((debugConfig as any).debugOptions).contains(DebugOptions.RedirectOutput); - expect((debugConfig as any).debugOptions).contains(DebugOptions.Flask); + expect((debugConfig as any).debugOptions).contains(DebugOptions.Jinja); }); }); }); From 28393cd484606d2e735f7401ae6ca35a75bcaa2f Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 27 Mar 2018 12:52:15 -0700 Subject: [PATCH 072/433] Changed property name used to capture the trigger source of Unit Tests Fixes #1213 --- news/3 Code Health/1213.md | 1 + src/client/telemetry/types.ts | 2 +- src/client/unittests/common/managers/baseTestManager.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 news/3 Code Health/1213.md diff --git a/news/3 Code Health/1213.md b/news/3 Code Health/1213.md new file mode 100644 index 000000000000..85e1b6302812 --- /dev/null +++ b/news/3 Code Health/1213.md @@ -0,0 +1 @@ +Changed property name used to capture the trigger source of Unit Tests. \ No newline at end of file diff --git a/src/client/telemetry/types.ts b/src/client/telemetry/types.ts index f8964590a324..dcb355155f45 100644 --- a/src/client/telemetry/types.ts +++ b/src/client/telemetry/types.ts @@ -44,7 +44,7 @@ export type TestRunTelemetry = { tool: 'nosetest' | 'pytest' | 'unittest'; scope: 'currentFile' | 'all' | 'file' | 'class' | 'function' | 'failed'; debugging: boolean; - trigger: 'ui' | 'codelens' | 'commandpalette' | 'auto'; + triggeredBy: 'ui' | 'codelens' | 'commandpalette' | 'auto'; failed: boolean; }; export type TestDiscoverytTelemetry = { diff --git a/src/client/unittests/common/managers/baseTestManager.ts b/src/client/unittests/common/managers/baseTestManager.ts index 7ee714e718ad..266d269148c9 100644 --- a/src/client/unittests/common/managers/baseTestManager.ts +++ b/src/client/unittests/common/managers/baseTestManager.ts @@ -177,7 +177,7 @@ export abstract class BaseTestManager implements ITestManager { tool: this.testProvider, scope: 'all', debugging: debug === true, - trigger: cmdSource, + triggeredBy: cmdSource, failed: false }; if (runFailedTests === true) { From 9013e3be223e9a091b135a8bc3bfab75d513112f Mon Sep 17 00:00:00 2001 From: Julien Russo Date: Tue, 27 Mar 2018 22:54:35 +0200 Subject: [PATCH 073/433] Added command translations for italian (#1152) --- README.md | 1 + news/1 Enhancements/1152.md | 2 ++ package.nls.it.json | 49 +++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+) create mode 100644 news/1 Enhancements/1152.md create mode 100644 package.nls.it.json diff --git a/README.md b/README.md index 862e9bebf45e..5687fda5a28c 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ contributors (if you would like to contribute a translation, see the [pull request which added simplified Chinese](https://github.com/Microsoft/vscode-python/pull/240)): * `en` +* `it` * `ja` * `ko-kr` * `ru` diff --git a/news/1 Enhancements/1152.md b/news/1 Enhancements/1152.md new file mode 100644 index 000000000000..25c2dcea67cb --- /dev/null +++ b/news/1 Enhancements/1152.md @@ -0,0 +1,2 @@ +Added commands translation for italian locale. +(thanks [Dotpys](https://github.com/Dotpys/)) \ No newline at end of file diff --git a/package.nls.it.json b/package.nls.it.json new file mode 100644 index 000000000000..6fabf1d664c8 --- /dev/null +++ b/package.nls.it.json @@ -0,0 +1,49 @@ +{ + "python.command.python.sortImports.title": "Ordina gli import", + "python.command.python.startREPL.title": "Apri nuova REPL", + "python.command.python.createTerminal.title": "Apri nuovo terminale", + "python.command.python.buildWorkspaceSymbols.title": "Compila simboli dello spazio di lavoro", + "python.command.python.runtests.title": "Esegui tutti i test", + "python.command.python.debugtests.title": "Esegui debug di tutti i test", + "python.command.python.execInTerminal.title": "Esegui file Python nel terminale", + "python.command.python.setInterpreter.title": "Seleziona interprete", + "python.command.python.updateSparkLibrary.title": "Aggiorna librerie PySpark dello spazio di lavoro", + "python.command.python.refactorExtractVariable.title": "Estrai variable", + "python.command.python.refactorExtractMethod.title": "Estrai metodo", + "python.command.python.viewTestOutput.title": "Mostra output dei test", + "python.command.python.selectAndRunTestMethod.title": "Esegui metodo di test ...", + "python.command.python.selectAndDebugTestMethod.title": "Esegui debug del metodo di test ...", + "python.command.python.selectAndRunTestFile.title": "Esegui file di test ...", + "python.command.python.runCurrentTestFile.title": "Esegui file di test attuale", + "python.command.python.runFailedTests.title": "Esegui test falliti", + "python.command.python.execSelectionInTerminal.title": "Esegui selezione/linea nel terminale di Python", + "python.command.python.execSelectionInDjangoShell.title": "Esegui selezione/linea nella shell Django", + "python.command.python.goToPythonObject.title": "Vai a oggetto Python", + "python.command.python.setLinter.title": "Selezione Linter", + "python.command.python.enableLinting.title": "Attiva Linting", + "python.command.python.runLinting.title": "Esegui Linting", + "python.snippet.launch.standard.label": "Python: File corrente", + "python.snippet.launch.standard.description": "Esegui debug di un programma Python su output predefinito", + "python.snippet.launch.pyspark.label": "Python: PySpark", + "python.snippet.launch.pyspark.description": "Esegui debug PySpark", + "python.snippet.launch.module.label": "Python: Modulo", + "python.snippet.launch.module.description": "Esegui debug modulo Python", + "python.snippet.launch.terminal.label": "Python: Terminale (integrato)", + "python.snippet.launch.terminal.description": "Esegui debug di un programma Python nel terminale integrato", + "python.snippet.launch.externalTerminal.label": "Python: Terminale (esterno)", + "python.snippet.launch.externalTerminal.description": "Esegui debug di un programma Python nel terminale esterno", + "python.snippet.launch.django.label": "Python: Django", + "python.snippet.launch.django.description": "Esegui debug applicazione Django", + "python.snippet.launch.flask.label": "Python: Flask (0.11.x o successiva)", + "python.snippet.launch.flask.description": "Esegui debug applicazione Flask", + "python.snippet.launch.flaskOld.label": "Python: Flask (0.10.x o precedente)", + "python.snippet.launch.flaskOld.description": "Esegui debug applicazione Flask in vecchio stile", + "python.snippet.launch.pyramid.label": "Python: Applicazione Pyramid", + "python.snippet.launch.pyramid.description": "Esegui debug applicazione Pyramid", + "python.snippet.launch.watson.label": "Python: Applicazione Watson", + "python.snippet.launch.watson.description": "Esegui debug applicazione Watson", + "python.snippet.launch.attach.label": "Python: Allega", + "python.snippet.launch.attach.description": "Allega debugger per debug remoto", + "python.snippet.launch.scrapy.label": "Python: Scrapy", + "python.snippet.launch.scrapy.description": "Scrapy con terminale integrato" +} From d157e93ef65005143bb16c2f8805427202b78f1e Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 27 Mar 2018 14:03:09 -0700 Subject: [PATCH 074/433] Add appropriate thanks --- news/1 Enhancements/961.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/news/1 Enhancements/961.md b/news/1 Enhancements/961.md index 431028ffca82..d2bf1576d4ab 100644 --- a/news/1 Enhancements/961.md +++ b/news/1 Enhancements/961.md @@ -1,2 +1,3 @@ Enable syntax highlighting for `requirements.in` files as used by -e.g. [pip-tools](https://github.com/jazzband/pip-tools). +e.g. [pip-tools](https://github.com/jazzband/pip-tools) +(thanks [Lorenzo Villani](https://github.com/lvillani)) From 40855c8bb075225263834266a55ca9b927b5557e Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 27 Mar 2018 14:04:08 -0700 Subject: [PATCH 075/433] Add appropriate thanks --- news/3 Code Health/1066.md | 1 + 1 file changed, 1 insertion(+) diff --git a/news/3 Code Health/1066.md b/news/3 Code Health/1066.md index 2f9e652cca4b..8279878538bb 100644 --- a/news/3 Code Health/1066.md +++ b/news/3 Code Health/1066.md @@ -1 +1,2 @@ Update npm package `vscode-extension-telemetry` to fix the warning 'os.tmpDir() deprecation'. +(thanks [osya](https://github.com/osya)) From 5043cc150426d2338256d8f3ec1b68b864cf0fc9 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 27 Mar 2018 14:05:36 -0700 Subject: [PATCH 076/433] Add appropriate thanks --- news/3 Code Health/1034.md | 1 + 1 file changed, 1 insertion(+) diff --git a/news/3 Code Health/1034.md b/news/3 Code Health/1034.md index 93047bb85ff6..ca4bdd6fbe57 100644 --- a/news/3 Code Health/1034.md +++ b/news/3 Code Health/1034.md @@ -1 +1,2 @@ Remove Jupyter commands. +(thanks [Yu Zhang](https://github.com/neilsustc)) From e570195045a034fb60c29b0f7ff2eac19a6a1b40 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 27 Mar 2018 14:08:45 -0700 Subject: [PATCH 077/433] Clean up entry --- news/2 Fixes/1036.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/news/2 Fixes/1036.md b/news/2 Fixes/1036.md index 3992f412b521..55800cca1e6a 100644 --- a/news/2 Fixes/1036.md +++ b/news/2 Fixes/1036.md @@ -1,5 +1,2 @@ Add ability to disable the check on memory usage of language server (Jedi) process. -To turn off this check, add the following setting into your user or workspace settings (`settings.json`) file: -```json -"python.jediMemoryLimit": -1 -``` +To turn off this check, add `"python.jediMemoryLimit": -1` to your user or workspace settings (`settings.json`) file. From b36b8d6432bcc91ad190cc5bed6981b622f66524 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 27 Mar 2018 14:29:49 -0700 Subject: [PATCH 078/433] Clarify a news entry --- news/3 Code Health/1090.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/3 Code Health/1090.md b/news/3 Code Health/1090.md index 49921ad6f4ca..222251dbfcf7 100644 --- a/news/3 Code Health/1090.md +++ b/news/3 Code Health/1090.md @@ -1 +1 @@ -Prevent debugger stepping into js code, when debugging async TypeScript code. +Prevent the debugger stepping into JS code while developing the extension when debugging async TypeScript code. From a6ffd21ea9ef51d4241436eff8d0d69acc11fe3e Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 27 Mar 2018 15:15:27 -0700 Subject: [PATCH 079/433] Clarify a news entry --- news/3 Code Health/1146.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/3 Code Health/1146.md b/news/3 Code Health/1146.md index d6e8ac464a69..26572fed17ea 100644 --- a/news/3 Code Health/1146.md +++ b/news/3 Code Health/1146.md @@ -1 +1 @@ -Improve compilation speed of TypeScript code. \ No newline at end of file +Improve compilation speed of the extension's TypeScript code. From c3ab24f1eaefc3f51fd633a92514217a721800a7 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 27 Mar 2018 15:16:58 -0700 Subject: [PATCH 080/433] Clarify news entry --- news/3 Code Health/983.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/3 Code Health/983.md b/news/3 Code Health/983.md index bd0250359c96..fe4e2a3e280a 100644 --- a/news/3 Code Health/983.md +++ b/news/3 Code Health/983.md @@ -1 +1 @@ -Launch the unit tests in debug mode as opposed to running and attaching the debugger. +Launch unit tests in debug mode as opposed to running and attaching the debugger to the already-running interpreter. From 1ccea94519b62a163cc033c7127f7557f965882f Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 27 Mar 2018 15:55:07 -0700 Subject: [PATCH 081/433] Renamed invalid property named `label` to `name` in `launch.json` file. (#1221) * :bug: rename property * :fire: delete unwanted file [skip cli] * Fixes #1219 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 86cc43cbf4b0..dfb0680cfcc7 100644 --- a/package.json +++ b/package.json @@ -990,7 +990,7 @@ ] }, { - "label": "Python Experimental: Flask", + "name": "Python Experimental: Flask", "type": "pythonExperimental", "request": "launch", "module": "flask", From d037583e3d4eb5fe07995420057ea7705423926e Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Wed, 28 Mar 2018 10:11:55 -0700 Subject: [PATCH 082/433] Fix spacing of general (non-specific) tokens + tests (#1222) Fixes #1096 --- src/client/formatters/lineFormatter.ts | 98 +++++++++++++++---- .../format/extension.lineFormatter.test.ts | 16 +++ .../format/extension.onEnterFormat.test.ts | 22 ++++- .../formatting/fileToFormatOnEnter.py | 4 + 4 files changed, 118 insertions(+), 22 deletions(-) diff --git a/src/client/formatters/lineFormatter.ts b/src/client/formatters/lineFormatter.ts index 4b3bff70aa8d..fc347235a525 100644 --- a/src/client/formatters/lineFormatter.ts +++ b/src/client/formatters/lineFormatter.ts @@ -5,14 +5,15 @@ import Char from 'typescript-char'; import { BraceCounter } from '../language/braceCounter'; import { TextBuilder } from '../language/textBuilder'; +import { TextRangeCollection } from '../language/textRangeCollection'; import { Tokenizer } from '../language/tokenizer'; import { ITextRangeCollection, IToken, TokenType } from '../language/types'; export class LineFormatter { - private builder: TextBuilder; - private tokens: ITextRangeCollection; - private braceCounter: BraceCounter; - private text: string; + private builder = new TextBuilder(); + private tokens: ITextRangeCollection = new TextRangeCollection([]); + private braceCounter = new BraceCounter(); + private text = ''; // tslint:disable-next-line:cyclomatic-complexity public formatLine(text: string): string { @@ -27,7 +28,7 @@ export class LineFormatter { const ws = this.text.substr(0, this.tokens.getItemAt(0).start); if (ws.length > 0) { - this.builder.append(ws); // Preserve leading indentation + this.builder.append(ws); // Preserve leading indentation. } for (let i = 0; i < this.tokens.count; i += 1) { @@ -55,24 +56,28 @@ export class LineFormatter { break; case TokenType.Colon: - // x: 1 if not in slice, x[1:y] if inside the slice + // x: 1 if not in slice, x[1:y] if inside the slice. this.builder.append(':'); if (!this.braceCounter.isOpened(TokenType.OpenBracket) && (next && next.type !== TokenType.Colon)) { - // Not inside opened [[ ... ] sequence + // Not inside opened [[ ... ] sequence. this.builder.softAppendSpace(); } break; case TokenType.Comment: - // add space before in-line comment + // Add space before in-line comment. if (prev) { this.builder.softAppendSpace(); } this.builder.append(this.text.substring(t.start, t.end)); break; + case TokenType.Semicolon: + this.builder.append(';'); + break; + default: - this.handleOther(t); + this.handleOther(t, i); break; } } @@ -85,7 +90,7 @@ export class LineFormatter { const opCode = this.text.charCodeAt(t.start); switch (opCode) { case Char.Equal: - if (index >= 2 && this.handleEqual(t, index)) { + if (this.handleEqual(t, index)) { return; } break; @@ -105,27 +110,66 @@ export class LineFormatter { } private handleEqual(t: IToken, index: number): boolean { - if (this.braceCounter.isOpened(TokenType.OpenBrace)) { - // Check if this is = in function arguments. If so, do not - // add spaces around it. - const prev = this.tokens.getItemAt(index - 1); - const prevPrev = this.tokens.getItemAt(index - 2); - if (prev.type === TokenType.Identifier && - (prevPrev.type === TokenType.Comma || prevPrev.type === TokenType.OpenBrace)) { - this.builder.append('='); - return true; - } + if (this.isMultipleStatements(index) && !this.braceCounter.isOpened(TokenType.OpenBrace)) { + return false; // x = 1; x, y = y, x + } + // Check if this is = in function arguments. If so, do not add spaces around it. + if (this.isEqualsInsideArguments(index)) { + this.builder.append('='); + return true; } return false; } - private handleOther(t: IToken): void { + private handleOther(t: IToken, index: number): void { if (this.isBraceType(t.type)) { this.braceCounter.countBrace(t); + this.builder.append(this.text.substring(t.start, t.end)); + return; } + + if (this.isEqualsInsideArguments(index - 1)) { + // Don't add space around = inside function arguments. + this.builder.append(this.text.substring(t.start, t.end)); + return; + } + + if (index > 0) { + const prev = this.tokens.getItemAt(index - 1); + if (this.isOpenBraceType(prev.type) || prev.type === TokenType.Colon) { + // Don't insert space after (, [ or { . + this.builder.append(this.text.substring(t.start, t.end)); + return; + } + } + + // In general, keep tokens separated. + this.builder.softAppendSpace(); this.builder.append(this.text.substring(t.start, t.end)); } + private isEqualsInsideArguments(index: number): boolean { + if (index < 1) { + return false; + } + const prev = this.tokens.getItemAt(index - 1); + if (prev.type === TokenType.Identifier) { + if (index >= 2) { + // (x=1 or ,x=1 + const prevPrev = this.tokens.getItemAt(index - 2); + return prevPrev.type === TokenType.Comma || prevPrev.type === TokenType.OpenBrace; + } else if (index < this.tokens.count - 2) { + const next = this.tokens.getItemAt(index + 1); + const nextNext = this.tokens.getItemAt(index + 2); + // x=1, or x=1) + if (this.isValueType(next.type)) { + return nextNext.type === TokenType.Comma || nextNext.type === TokenType.CloseBrace; + } + } + } + return false; + } + private isOpenBraceType(type: TokenType): boolean { return type === TokenType.OpenBrace || type === TokenType.OpenBracket || type === TokenType.OpenCurly; } @@ -135,4 +179,16 @@ export class LineFormatter { private isBraceType(type: TokenType): boolean { return this.isOpenBraceType(type) || this.isCloseBraceType(type); } + private isValueType(type: TokenType): boolean { + return type === TokenType.Identifier || type === TokenType.Unknown || + type === TokenType.Number || type === TokenType.String; + } + private isMultipleStatements(index: number): boolean { + for (let i = index; i >= 0; i -= 1) { + if (this.tokens.getItemAt(i).type === TokenType.Semicolon) { + return true; + } + } + return false; + } } diff --git a/src/test/format/extension.lineFormatter.test.ts b/src/test/format/extension.lineFormatter.test.ts index 842cb02d735d..79de72c5774a 100644 --- a/src/test/format/extension.lineFormatter.test.ts +++ b/src/test/format/extension.lineFormatter.test.ts @@ -65,4 +65,20 @@ suite('Formatting - line formatter', () => { const actual = formatter.formatLine(' # comment'); assert.equal(actual, ' # comment'); }); + test('Equals in first argument', () => { + const actual = formatter.formatLine('foo(x =0)'); + assert.equal(actual, 'foo(x=0)'); + }); + test('Equals in second argument', () => { + const actual = formatter.formatLine('foo(x,y= \"a\",'); + assert.equal(actual, 'foo(x, y=\"a\",'); + }); + test('Equals in multiline arguments', () => { + const actual = formatter.formatLine('x = 1,y =-2)'); + assert.equal(actual, 'x=1, y=-2)'); + }); + test('Equals in multiline arguments starting comma', () => { + const actual = formatter.formatLine(',x = 1,y =m)'); + assert.equal(actual, ', x=1, y=m)'); + }); }); diff --git a/src/test/format/extension.onEnterFormat.test.ts b/src/test/format/extension.onEnterFormat.test.ts index 74597ce19be7..8f594d5e2559 100644 --- a/src/test/format/extension.onEnterFormat.test.ts +++ b/src/test/format/extension.onEnterFormat.test.ts @@ -59,8 +59,28 @@ suite('Formatting - OnEnter provider', () => { assert.equal(text, 'x.y', 'Line ending with period was reformatted'); }); - test('Formatting line ending in string', async () => { + test('Formatting line with unknown neighboring tokens', async () => { const text = await formatAtPosition(9, 0); + assert.equal(text, 'if x <= 1:', 'Line with unknown neighboring tokens was not formatted'); + }); + + test('Formatting line with unknown neighboring tokens', async () => { + const text = await formatAtPosition(10, 0); + assert.equal(text, 'if 1 <= x:', 'Line with unknown neighboring tokens was not formatted'); + }); + + test('Formatting method definition with arguments', async () => { + const text = await formatAtPosition(11, 0); + assert.equal(text, 'def __init__(self, age=23)', 'Method definition with arguments was not formatted'); + }); + + test('Formatting space after open brace', async () => { + const text = await formatAtPosition(12, 0); + assert.equal(text, 'while(1)', 'Space after open brace was not formatted'); + }); + + test('Formatting line ending in string', async () => { + const text = await formatAtPosition(13, 0); assert.equal(text, 'x + """', 'Line ending in multiline string was not formatted'); }); diff --git a/src/test/pythonFiles/formatting/fileToFormatOnEnter.py b/src/test/pythonFiles/formatting/fileToFormatOnEnter.py index bbd025363098..8adfd1fa1233 100644 --- a/src/test/pythonFiles/formatting/fileToFormatOnEnter.py +++ b/src/test/pythonFiles/formatting/fileToFormatOnEnter.py @@ -6,4 +6,8 @@ x+1 # @x x.y +if x<=1: +if 1<=x: +def __init__(self, age = 23) +while(1) x+""" From dd792b0e2811ab46c8d97b5cca3c7661e3c27879 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Wed, 28 Mar 2018 10:17:03 -0700 Subject: [PATCH 083/433] RC update (#1220) --- CHANGELOG.md | 46 ++++++++++++++++++++++++++++++++++++++++++---- package.json | 2 +- 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40bb6b4b5f3c..0e27347650ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2018.3.0-beta (19 Mar 2018) +## 2018.3.0-rc (27 Mar 2018) ### Enhancements @@ -12,8 +12,19 @@ ([#1031](https://github.com/Microsoft/vscode-python/issues/1031)) 1. Add a Scrapy debug configuration for the experimental debugger. ([#1032](https://github.com/Microsoft/vscode-python/issues/1032)) +1. When using pipenv, install packages (such as linters, test frameworks) in dev-packages. + ([#1110](https://github.com/Microsoft/vscode-python/issues/1110)) +1. Added commands translation for italian locale. +(thanks [Dotpys](https://github.com/Dotpys/)) ([#1152](https://github.com/Microsoft/vscode-python/issues/1152)) +1. Add support for Django Template debugging in experimental debugger. + ([#1189](https://github.com/Microsoft/vscode-python/issues/1189)) +1. Add support for Flask Template debugging in experimental debugger. + ([#1190](https://github.com/Microsoft/vscode-python/issues/1190)) +1. Add support for Jinja template debugging. ([#1210](https://github.com/Microsoft/vscode-python/issues/1210)) 1. When debugging, use `Integrated Terminal` as the default console. ([#526](https://github.com/Microsoft/vscode-python/issues/526)) +1. Disable the display of errors messages when rediscovering of tests fail in response to changes to files, e.g. don't show a message if there's a syntax error in the test code. + ([#704](https://github.com/Microsoft/vscode-python/issues/704)) 1. Bundle python depedencies (PTVSD package) in the extension for the experimental debugger. ([#741](https://github.com/Microsoft/vscode-python/issues/741)) 1. Add support for expermental debugger when debugging Python Unit Tests. @@ -21,17 +32,28 @@ 1. Support `Debug Console` as a `console` option for the Experimental Debugger. ([#950](https://github.com/Microsoft/vscode-python/issues/950)) 1. Enable syntax highlighting for `requirements.in` files as used by -e.g. [pip-tools](https://github.com/jazzband/pip-tools). +e.g. [pip-tools](https://github.com/jazzband/pip-tools) +(thanks [Lorenzo Villani](https://github.com/lvillani)) ([#961](https://github.com/Microsoft/vscode-python/issues/961)) 1. Add support to read name of Pipfile from environment variable. ([#999](https://github.com/Microsoft/vscode-python/issues/999)) ### Fixes +1. Fixes issue that causes debugging of unit tests to hang indefinitely. ([#1009](https://github.com/Microsoft/vscode-python/issues/1009)) +1. Add ability to disable the check on memory usage of language server (Jedi) process. +To turn off this check, add `"python.jediMemoryLimit": -1` to your user or workspace settings (`settings.json`) file. + ([#1036](https://github.com/Microsoft/vscode-python/issues/1036)) 1. Ignore test results when debugging unit tests. ([#1043](https://github.com/Microsoft/vscode-python/issues/1043)) +1. Fixes auto formatting of conditional statements containing expressions with `<=` symbols. + ([#1096](https://github.com/Microsoft/vscode-python/issues/1096)) 1. Resolve debug configuration information in `launch.json` when debugging without opening a python file. ([#1098](https://github.com/Microsoft/vscode-python/issues/1098)) +1. Disables auto completion when editing text at the end of a comment string. + ([#1123](https://github.com/Microsoft/vscode-python/issues/1123)) +1. Ensures file paths are properly encoded when passing them as arguments to linters. + ([#199](https://github.com/Microsoft/vscode-python/issues/199)) 1. Fix occasionally having unverified breakpoints ([#87](https://github.com/Microsoft/vscode-python/issues/87)) 1. Ensure conda installer is not used for non-conda environments. @@ -44,6 +66,7 @@ e.g. [pip-tools](https://github.com/jazzband/pip-tools). 1. Exclude 'news' folder from getting packaged into the extension. ([#1020](https://github.com/Microsoft/vscode-python/issues/1020)) 1. Remove Jupyter commands. +(thanks [Yu Zhang](https://github.com/neilsustc)) ([#1034](https://github.com/Microsoft/vscode-python/issues/1034)) 1. Trigger incremental build compilation only when typescript files are modified. ([#1040](https://github.com/Microsoft/vscode-python/issues/1040)) @@ -52,22 +75,37 @@ e.g. [pip-tools](https://github.com/jazzband/pip-tools). 1. Enable unit testing of stdout and stderr redirection for the experimental debugger. ([#1048](https://github.com/Microsoft/vscode-python/issues/1048)) 1. Update npm package `vscode-extension-telemetry` to fix the warning 'os.tmpDir() deprecation'. +(thanks [osya](https://github.com/osya)) ([#1066](https://github.com/Microsoft/vscode-python/issues/1066)) -1. Prevent debugger stepping into js code, when debugging async TypeScript code. +1. Prevent the debugger stepping into JS code while developing the extension when debugging async TypeScript code. ([#1090](https://github.com/Microsoft/vscode-python/issues/1090)) 1. Increase timeouts for the debugger unit tests. ([#1094](https://github.com/Microsoft/vscode-python/issues/1094)) 1. Change the command used to install pip on AppVeyor to avoid installation errors. ([#1107](https://github.com/Microsoft/vscode-python/issues/1107)) +1. Check whether a document is active when detecthing changes in the active document. + ([#1114](https://github.com/Microsoft/vscode-python/issues/1114)) +1. Remove SIGINT handler in debugger adapter, thereby preventing it from shutting down the debugger. + ([#1122](https://github.com/Microsoft/vscode-python/issues/1122)) +1. Improve compilation speed of the extension's TypeScript code. + ([#1146](https://github.com/Microsoft/vscode-python/issues/1146)) +1. Changes to how debug options are passed into the experimental version of PTVSD (debugger). + ([#1168](https://github.com/Microsoft/vscode-python/issues/1168)) +1. Ensure file paths are not sent in telemetry when running unit tests. + ([#1180](https://github.com/Microsoft/vscode-python/issues/1180)) +1. Change `DjangoDebugging` to `Django` in `debugOptions` of launch.json. + ([#1198](https://github.com/Microsoft/vscode-python/issues/1198)) +1. Changed property name used to capture the trigger source of Unit Tests. ([#1213](https://github.com/Microsoft/vscode-python/issues/1213)) 1. Enable unit testing of the experimental debugger on CI servers ([#742](https://github.com/Microsoft/vscode-python/issues/742)) 1. Generate code coverage for debug adapter unit tests. ([#778](https://github.com/Microsoft/vscode-python/issues/778)) 1. Execute prospector as a module (using -m). ([#982](https://github.com/Microsoft/vscode-python/issues/982)) -1. Launch the unit tests in debug mode as opposed to running and attaching the debugger. +1. Launch unit tests in debug mode as opposed to running and attaching the debugger to the already-running interpreter. ([#983](https://github.com/Microsoft/vscode-python/issues/983)) + ## 2018.2.1 (09 Mar 2018) ### Fixes diff --git a/package.json b/package.json index dfb0680cfcc7..0422ea209096 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2018.3.0-beta", + "version": "2018.3.0-rc", "publisher": "ms-python", "author": { "name": "Microsoft Corporation" From db9e747bbe8766b3d5a1fe5b7341100ab8be3dc4 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 29 Mar 2018 10:18:58 -0700 Subject: [PATCH 084/433] 2018.3.0 release details (#1227) --- CHANGELOG.md | 3 +-- news/1 Enhancements/1029.md | 1 - news/1 Enhancements/1030.md | 1 - news/1 Enhancements/1031.md | 1 - news/1 Enhancements/1032.md | 1 - news/1 Enhancements/1110.md | 1 - news/1 Enhancements/1152.md | 2 -- news/1 Enhancements/1189.md | 1 - news/1 Enhancements/1190.md | 1 - news/1 Enhancements/1210.md | 1 - news/1 Enhancements/526.md | 1 - news/1 Enhancements/704.md | 1 - news/1 Enhancements/741.md | 1 - news/1 Enhancements/906.md | 1 - news/1 Enhancements/950.md | 1 - news/1 Enhancements/961.md | 3 --- news/1 Enhancements/999.md | 1 - news/2 Fixes/1009.md | 1 - news/2 Fixes/1036.md | 2 -- news/2 Fixes/1043.md | 1 - news/2 Fixes/1096.md | 1 - news/2 Fixes/1098.md | 1 - news/2 Fixes/1123.md | 1 - news/2 Fixes/199.md | 1 - news/2 Fixes/87.md | 1 - news/2 Fixes/969.md | 1 - news/2 Fixes/981.md | 1 - news/3 Code Health/1020.md | 1 - news/3 Code Health/1034.md | 2 -- news/3 Code Health/1040.md | 1 - news/3 Code Health/1042.md | 1 - news/3 Code Health/1048.md | 1 - news/3 Code Health/1066.md | 2 -- news/3 Code Health/1090.md | 1 - news/3 Code Health/1094.md | 1 - news/3 Code Health/1107.md | 1 - news/3 Code Health/1114.md | 1 - news/3 Code Health/1122.md | 1 - news/3 Code Health/1146.md | 1 - news/3 Code Health/1168.md | 1 - news/3 Code Health/1180.md | 1 - news/3 Code Health/1198.md | 1 - news/3 Code Health/1213.md | 1 - news/3 Code Health/742.md | 1 - news/3 Code Health/778.md | 1 - news/3 Code Health/982.md | 1 - news/3 Code Health/983.md | 1 - news/announce.py | 9 +++++++-- package.json | 2 +- 49 files changed, 9 insertions(+), 57 deletions(-) delete mode 100644 news/1 Enhancements/1029.md delete mode 100644 news/1 Enhancements/1030.md delete mode 100644 news/1 Enhancements/1031.md delete mode 100644 news/1 Enhancements/1032.md delete mode 100644 news/1 Enhancements/1110.md delete mode 100644 news/1 Enhancements/1152.md delete mode 100644 news/1 Enhancements/1189.md delete mode 100644 news/1 Enhancements/1190.md delete mode 100644 news/1 Enhancements/1210.md delete mode 100644 news/1 Enhancements/526.md delete mode 100644 news/1 Enhancements/704.md delete mode 100644 news/1 Enhancements/741.md delete mode 100644 news/1 Enhancements/906.md delete mode 100644 news/1 Enhancements/950.md delete mode 100644 news/1 Enhancements/961.md delete mode 100644 news/1 Enhancements/999.md delete mode 100644 news/2 Fixes/1009.md delete mode 100644 news/2 Fixes/1036.md delete mode 100644 news/2 Fixes/1043.md delete mode 100644 news/2 Fixes/1096.md delete mode 100644 news/2 Fixes/1098.md delete mode 100644 news/2 Fixes/1123.md delete mode 100644 news/2 Fixes/199.md delete mode 100644 news/2 Fixes/87.md delete mode 100644 news/2 Fixes/969.md delete mode 100644 news/2 Fixes/981.md delete mode 100644 news/3 Code Health/1020.md delete mode 100644 news/3 Code Health/1034.md delete mode 100644 news/3 Code Health/1040.md delete mode 100644 news/3 Code Health/1042.md delete mode 100644 news/3 Code Health/1048.md delete mode 100644 news/3 Code Health/1066.md delete mode 100644 news/3 Code Health/1090.md delete mode 100644 news/3 Code Health/1094.md delete mode 100644 news/3 Code Health/1107.md delete mode 100644 news/3 Code Health/1114.md delete mode 100644 news/3 Code Health/1122.md delete mode 100644 news/3 Code Health/1146.md delete mode 100644 news/3 Code Health/1168.md delete mode 100644 news/3 Code Health/1180.md delete mode 100644 news/3 Code Health/1198.md delete mode 100644 news/3 Code Health/1213.md delete mode 100644 news/3 Code Health/742.md delete mode 100644 news/3 Code Health/778.md delete mode 100644 news/3 Code Health/982.md delete mode 100644 news/3 Code Health/983.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e27347650ed..358776c756f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2018.3.0-rc (27 Mar 2018) +## 2018.3.0 (28 Mar 2018) ### Enhancements @@ -105,7 +105,6 @@ To turn off this check, add `"python.jediMemoryLimit": -1` to your user or works 1. Launch unit tests in debug mode as opposed to running and attaching the debugger to the already-running interpreter. ([#983](https://github.com/Microsoft/vscode-python/issues/983)) - ## 2018.2.1 (09 Mar 2018) ### Fixes diff --git a/news/1 Enhancements/1029.md b/news/1 Enhancements/1029.md deleted file mode 100644 index 9da8383fe46d..000000000000 --- a/news/1 Enhancements/1029.md +++ /dev/null @@ -1 +0,0 @@ -Add a PySpark debug configuration for the experimental debugger. diff --git a/news/1 Enhancements/1030.md b/news/1 Enhancements/1030.md deleted file mode 100644 index 5822cd9b67ee..000000000000 --- a/news/1 Enhancements/1030.md +++ /dev/null @@ -1 +0,0 @@ -Add a Pyramid debug configuration for the experimental debugger. diff --git a/news/1 Enhancements/1031.md b/news/1 Enhancements/1031.md deleted file mode 100644 index 299a112ecfcf..000000000000 --- a/news/1 Enhancements/1031.md +++ /dev/null @@ -1 +0,0 @@ -Add a Watson debug configuration for the experimental debugger. diff --git a/news/1 Enhancements/1032.md b/news/1 Enhancements/1032.md deleted file mode 100644 index d7d5cb280e36..000000000000 --- a/news/1 Enhancements/1032.md +++ /dev/null @@ -1 +0,0 @@ -Add a Scrapy debug configuration for the experimental debugger. diff --git a/news/1 Enhancements/1110.md b/news/1 Enhancements/1110.md deleted file mode 100644 index 95572f4644ec..000000000000 --- a/news/1 Enhancements/1110.md +++ /dev/null @@ -1 +0,0 @@ -When using pipenv, install packages (such as linters, test frameworks) in dev-packages. diff --git a/news/1 Enhancements/1152.md b/news/1 Enhancements/1152.md deleted file mode 100644 index 25c2dcea67cb..000000000000 --- a/news/1 Enhancements/1152.md +++ /dev/null @@ -1,2 +0,0 @@ -Added commands translation for italian locale. -(thanks [Dotpys](https://github.com/Dotpys/)) \ No newline at end of file diff --git a/news/1 Enhancements/1189.md b/news/1 Enhancements/1189.md deleted file mode 100644 index 6d306e55189c..000000000000 --- a/news/1 Enhancements/1189.md +++ /dev/null @@ -1 +0,0 @@ -Add support for Django Template debugging in experimental debugger. diff --git a/news/1 Enhancements/1190.md b/news/1 Enhancements/1190.md deleted file mode 100644 index a1b144662fa5..000000000000 --- a/news/1 Enhancements/1190.md +++ /dev/null @@ -1 +0,0 @@ -Add support for Flask Template debugging in experimental debugger. diff --git a/news/1 Enhancements/1210.md b/news/1 Enhancements/1210.md deleted file mode 100644 index 68b06658e94b..000000000000 --- a/news/1 Enhancements/1210.md +++ /dev/null @@ -1 +0,0 @@ -Add support for Jinja template debugging. \ No newline at end of file diff --git a/news/1 Enhancements/526.md b/news/1 Enhancements/526.md deleted file mode 100644 index 829c4ac990f7..000000000000 --- a/news/1 Enhancements/526.md +++ /dev/null @@ -1 +0,0 @@ -When debugging, use `Integrated Terminal` as the default console. diff --git a/news/1 Enhancements/704.md b/news/1 Enhancements/704.md deleted file mode 100644 index 49f1c4d9a3b8..000000000000 --- a/news/1 Enhancements/704.md +++ /dev/null @@ -1 +0,0 @@ -Disable the display of errors messages when rediscovering of tests fail in response to changes to files, e.g. don't show a message if there's a syntax error in the test code. diff --git a/news/1 Enhancements/741.md b/news/1 Enhancements/741.md deleted file mode 100644 index 329ff07728af..000000000000 --- a/news/1 Enhancements/741.md +++ /dev/null @@ -1 +0,0 @@ -Bundle python depedencies (PTVSD package) in the extension for the experimental debugger. diff --git a/news/1 Enhancements/906.md b/news/1 Enhancements/906.md deleted file mode 100644 index 95569b479d9e..000000000000 --- a/news/1 Enhancements/906.md +++ /dev/null @@ -1 +0,0 @@ -Add support for expermental debugger when debugging Python Unit Tests. diff --git a/news/1 Enhancements/950.md b/news/1 Enhancements/950.md deleted file mode 100644 index bba2053af7fa..000000000000 --- a/news/1 Enhancements/950.md +++ /dev/null @@ -1 +0,0 @@ -Support `Debug Console` as a `console` option for the Experimental Debugger. diff --git a/news/1 Enhancements/961.md b/news/1 Enhancements/961.md deleted file mode 100644 index d2bf1576d4ab..000000000000 --- a/news/1 Enhancements/961.md +++ /dev/null @@ -1,3 +0,0 @@ -Enable syntax highlighting for `requirements.in` files as used by -e.g. [pip-tools](https://github.com/jazzband/pip-tools) -(thanks [Lorenzo Villani](https://github.com/lvillani)) diff --git a/news/1 Enhancements/999.md b/news/1 Enhancements/999.md deleted file mode 100644 index c215b7aadd31..000000000000 --- a/news/1 Enhancements/999.md +++ /dev/null @@ -1 +0,0 @@ -Add support to read name of Pipfile from environment variable. diff --git a/news/2 Fixes/1009.md b/news/2 Fixes/1009.md deleted file mode 100644 index 665fdfc9e709..000000000000 --- a/news/2 Fixes/1009.md +++ /dev/null @@ -1 +0,0 @@ -Fixes issue that causes debugging of unit tests to hang indefinitely. \ No newline at end of file diff --git a/news/2 Fixes/1036.md b/news/2 Fixes/1036.md deleted file mode 100644 index 55800cca1e6a..000000000000 --- a/news/2 Fixes/1036.md +++ /dev/null @@ -1,2 +0,0 @@ -Add ability to disable the check on memory usage of language server (Jedi) process. -To turn off this check, add `"python.jediMemoryLimit": -1` to your user or workspace settings (`settings.json`) file. diff --git a/news/2 Fixes/1043.md b/news/2 Fixes/1043.md deleted file mode 100644 index cf0541628a52..000000000000 --- a/news/2 Fixes/1043.md +++ /dev/null @@ -1 +0,0 @@ -Ignore test results when debugging unit tests. diff --git a/news/2 Fixes/1096.md b/news/2 Fixes/1096.md deleted file mode 100644 index d4a4500b38bc..000000000000 --- a/news/2 Fixes/1096.md +++ /dev/null @@ -1 +0,0 @@ -Fixes auto formatting of conditional statements containing expressions with `<=` symbols. diff --git a/news/2 Fixes/1098.md b/news/2 Fixes/1098.md deleted file mode 100644 index d7b5b5e35ef1..000000000000 --- a/news/2 Fixes/1098.md +++ /dev/null @@ -1 +0,0 @@ -Resolve debug configuration information in `launch.json` when debugging without opening a python file. diff --git a/news/2 Fixes/1123.md b/news/2 Fixes/1123.md deleted file mode 100644 index 1a23a845690b..000000000000 --- a/news/2 Fixes/1123.md +++ /dev/null @@ -1 +0,0 @@ -Disables auto completion when editing text at the end of a comment string. diff --git a/news/2 Fixes/199.md b/news/2 Fixes/199.md deleted file mode 100644 index 94f155e516b8..000000000000 --- a/news/2 Fixes/199.md +++ /dev/null @@ -1 +0,0 @@ -Ensures file paths are properly encoded when passing them as arguments to linters. diff --git a/news/2 Fixes/87.md b/news/2 Fixes/87.md deleted file mode 100644 index c024086fd1d3..000000000000 --- a/news/2 Fixes/87.md +++ /dev/null @@ -1 +0,0 @@ -Fix occasionally having unverified breakpoints diff --git a/news/2 Fixes/969.md b/news/2 Fixes/969.md deleted file mode 100644 index a991de1a919d..000000000000 --- a/news/2 Fixes/969.md +++ /dev/null @@ -1 +0,0 @@ -Ensure conda installer is not used for non-conda environments. diff --git a/news/2 Fixes/981.md b/news/2 Fixes/981.md deleted file mode 100644 index 8621551fbbd4..000000000000 --- a/news/2 Fixes/981.md +++ /dev/null @@ -1 +0,0 @@ -Fixes issue that display incorrect interpreter briefly before updating it to the right value. diff --git a/news/3 Code Health/1020.md b/news/3 Code Health/1020.md deleted file mode 100644 index ddd9b41bfb7e..000000000000 --- a/news/3 Code Health/1020.md +++ /dev/null @@ -1 +0,0 @@ -Exclude 'news' folder from getting packaged into the extension. diff --git a/news/3 Code Health/1034.md b/news/3 Code Health/1034.md deleted file mode 100644 index ca4bdd6fbe57..000000000000 --- a/news/3 Code Health/1034.md +++ /dev/null @@ -1,2 +0,0 @@ -Remove Jupyter commands. -(thanks [Yu Zhang](https://github.com/neilsustc)) diff --git a/news/3 Code Health/1040.md b/news/3 Code Health/1040.md deleted file mode 100644 index 88e881779fbd..000000000000 --- a/news/3 Code Health/1040.md +++ /dev/null @@ -1 +0,0 @@ -Trigger incremental build compilation only when typescript files are modified. diff --git a/news/3 Code Health/1042.md b/news/3 Code Health/1042.md deleted file mode 100644 index 94b355687cda..000000000000 --- a/news/3 Code Health/1042.md +++ /dev/null @@ -1 +0,0 @@ -Updated npm dependencies in devDependencies and fix TypeScript compilation issues. diff --git a/news/3 Code Health/1048.md b/news/3 Code Health/1048.md deleted file mode 100644 index c49a6f27e1a1..000000000000 --- a/news/3 Code Health/1048.md +++ /dev/null @@ -1 +0,0 @@ -Enable unit testing of stdout and stderr redirection for the experimental debugger. diff --git a/news/3 Code Health/1066.md b/news/3 Code Health/1066.md deleted file mode 100644 index 8279878538bb..000000000000 --- a/news/3 Code Health/1066.md +++ /dev/null @@ -1,2 +0,0 @@ -Update npm package `vscode-extension-telemetry` to fix the warning 'os.tmpDir() deprecation'. -(thanks [osya](https://github.com/osya)) diff --git a/news/3 Code Health/1090.md b/news/3 Code Health/1090.md deleted file mode 100644 index 222251dbfcf7..000000000000 --- a/news/3 Code Health/1090.md +++ /dev/null @@ -1 +0,0 @@ -Prevent the debugger stepping into JS code while developing the extension when debugging async TypeScript code. diff --git a/news/3 Code Health/1094.md b/news/3 Code Health/1094.md deleted file mode 100644 index 9da7257da17b..000000000000 --- a/news/3 Code Health/1094.md +++ /dev/null @@ -1 +0,0 @@ -Increase timeouts for the debugger unit tests. diff --git a/news/3 Code Health/1107.md b/news/3 Code Health/1107.md deleted file mode 100644 index 21e00e5d7662..000000000000 --- a/news/3 Code Health/1107.md +++ /dev/null @@ -1 +0,0 @@ -Change the command used to install pip on AppVeyor to avoid installation errors. diff --git a/news/3 Code Health/1114.md b/news/3 Code Health/1114.md deleted file mode 100644 index 0c77070b860d..000000000000 --- a/news/3 Code Health/1114.md +++ /dev/null @@ -1 +0,0 @@ -Check whether a document is active when detecthing changes in the active document. diff --git a/news/3 Code Health/1122.md b/news/3 Code Health/1122.md deleted file mode 100644 index 79587594dda4..000000000000 --- a/news/3 Code Health/1122.md +++ /dev/null @@ -1 +0,0 @@ -Remove SIGINT handler in debugger adapter, thereby preventing it from shutting down the debugger. diff --git a/news/3 Code Health/1146.md b/news/3 Code Health/1146.md deleted file mode 100644 index 26572fed17ea..000000000000 --- a/news/3 Code Health/1146.md +++ /dev/null @@ -1 +0,0 @@ -Improve compilation speed of the extension's TypeScript code. diff --git a/news/3 Code Health/1168.md b/news/3 Code Health/1168.md deleted file mode 100644 index 191f91bdd741..000000000000 --- a/news/3 Code Health/1168.md +++ /dev/null @@ -1 +0,0 @@ -Changes to how debug options are passed into the experimental version of PTVSD (debugger). diff --git a/news/3 Code Health/1180.md b/news/3 Code Health/1180.md deleted file mode 100644 index 9d27520795b0..000000000000 --- a/news/3 Code Health/1180.md +++ /dev/null @@ -1 +0,0 @@ -Ensure file paths are not sent in telemetry when running unit tests. diff --git a/news/3 Code Health/1198.md b/news/3 Code Health/1198.md deleted file mode 100644 index b14083165f25..000000000000 --- a/news/3 Code Health/1198.md +++ /dev/null @@ -1 +0,0 @@ -Change `DjangoDebugging` to `Django` in `debugOptions` of launch.json. diff --git a/news/3 Code Health/1213.md b/news/3 Code Health/1213.md deleted file mode 100644 index 85e1b6302812..000000000000 --- a/news/3 Code Health/1213.md +++ /dev/null @@ -1 +0,0 @@ -Changed property name used to capture the trigger source of Unit Tests. \ No newline at end of file diff --git a/news/3 Code Health/742.md b/news/3 Code Health/742.md deleted file mode 100644 index d35d1ffd391e..000000000000 --- a/news/3 Code Health/742.md +++ /dev/null @@ -1 +0,0 @@ -Enable unit testing of the experimental debugger on CI servers diff --git a/news/3 Code Health/778.md b/news/3 Code Health/778.md deleted file mode 100644 index 91eb90689389..000000000000 --- a/news/3 Code Health/778.md +++ /dev/null @@ -1 +0,0 @@ -Generate code coverage for debug adapter unit tests. diff --git a/news/3 Code Health/982.md b/news/3 Code Health/982.md deleted file mode 100644 index 00553e1194e9..000000000000 --- a/news/3 Code Health/982.md +++ /dev/null @@ -1 +0,0 @@ -Execute prospector as a module (using -m). diff --git a/news/3 Code Health/983.md b/news/3 Code Health/983.md deleted file mode 100644 index fe4e2a3e280a..000000000000 --- a/news/3 Code Health/983.md +++ /dev/null @@ -1 +0,0 @@ -Launch unit tests in debug mode as opposed to running and attaching the debugger to the already-running interpreter. diff --git a/news/announce.py b/news/announce.py index 7d1d12e22770..ce929b9900b2 100644 --- a/news/announce.py +++ b/news/announce.py @@ -88,8 +88,13 @@ def changelog_markdown(data): def git_rm(path): """Run git-rm on the path.""" status = subprocess.run(['git', 'rm', os.fspath(path.resolve())], - shell=True) - status.check_returncode() + shell=True, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + try: + status.check_returncode() + except Exception: + print(status.stdout, file=sys.stderr) + raise def cleanup(data): diff --git a/package.json b/package.json index 0422ea209096..4995a9325801 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2018.3.0-rc", + "version": "2018.3.0", "publisher": "ms-python", "author": { "name": "Microsoft Corporation" From f9024dc91891f200956684c4b0b52860039d674e Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 29 Mar 2018 11:46:27 -0700 Subject: [PATCH 085/433] Bump version to 2018.4.0-alpha --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4995a9325801..2accd2c00a2b 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2018.3.0", + "version": "2018.4.0-alpha", "publisher": "ms-python", "author": { "name": "Microsoft Corporation" From ab1903687037fccca31a7bcd9e09058a23207672 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 29 Mar 2018 16:20:58 -0700 Subject: [PATCH 086/433] Update on the removal of `closed` labels --- CONTRIBUTING.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ef238b941add..79c6be5e1caa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -112,25 +112,23 @@ To help actively track what stage issues are at, various labels are used. Which labels are expected to be set vary from when an issue is open to when an issue is closed. -#### Open issues - When an [issue is first opened](https://github.com/Microsoft/vscode-python/issues), -it is triaged to contain at least three types of labels: +it is triaged to contain at least two types of labels: 1. `needs` -1. `feature` 1. `type` -These labels cover what is blocking the issue from closing, what -feature(s) of the extension are related to the issue, and what type of -issue it is, respectively. +These labels cover what is blocking the issue from closing and what kind of +issue it is. We also add a `feature` label when appropriate for what the issue +relates to. #### Closed issues -When an -[issue is closed](https://github.com/Microsoft/vscode-python/issues?q=is%3Aissue+is%3Aclosed), -it should have an appropriate `closed-` label. +When an issue is closed by a pull request we add a +[`validate fix`](https://github.com/Microsoft/vscode-python/issues?q=label%3A%22validate+fix%22+is%3Aclosed) +label in order to request people help us test the fix to validate the issue was +resolved successfully. ### Pull request workflow @@ -150,8 +148,9 @@ it should have an appropriate `closed-` label. 1. Make sure all status checks are green (e.g. CLA check, CI, etc.) 1. Address any review comments 1. [Maintainers only] Merge the pull request -1. [Maintainers only] Update affected issues to be: - 1. Closed (with an appropriate `closed-` label) +1. [Maintainers only] Update affected issues: + 1. Add the [`validate fix`](https://github.com/Microsoft/vscode-python/issues?q=label%3A%22validate+fix%22+is%3Aclosed) + label 1. The issue(s) are attached to the current milestone 1. Register OSS usage 1. Email CELA about any 3rd-party usage changes From 2d3bef1cc35847a6e9375f36b484f384ee500760 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 30 Mar 2018 09:28:29 -0700 Subject: [PATCH 087/433] Fixes issue that causes linter to fail when file path contains spaces. (#1241) (#1243) Forward-port from 2018.3.1 Fixes #1239 --- CHANGELOG.md | 8 +++++++- src/client/linters/flake8.ts | 2 +- src/client/linters/mypy.ts | 2 +- src/client/linters/pep8.ts | 2 +- src/client/linters/prospector.ts | 2 +- src/client/linters/pydocstyle.ts | 2 +- src/client/linters/pylama.ts | 2 +- src/client/linters/pylint.ts | 2 +- src/test/linters/lint.args.test.ts | 14 +++++++------- 9 files changed, 21 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 358776c756f8..0494063d280b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 2018.3.1 (29 Mar 2018) + +### Fixes + +1. Fixes issue that causes linter to fail when file path contains spaces. +([#1239](https://github.com/Microsoft/vscode-python/issues/1239)) + ## 2018.3.0 (28 Mar 2018) ### Enhancements @@ -829,4 +836,3 @@ the following people who contributed code: ## Version 0.0.3 * Added support for debugging using PDB - diff --git a/src/client/linters/flake8.ts b/src/client/linters/flake8.ts index 494174e15e5d..d2c000a47fb0 100644 --- a/src/client/linters/flake8.ts +++ b/src/client/linters/flake8.ts @@ -13,7 +13,7 @@ export class Flake8 extends BaseLinter { } protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise { - const messages = await this.run(['--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', document.uri.fsPath.fileToCommandArgument()], document, cancellation); + const messages = await this.run(['--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', document.uri.fsPath], document, cancellation); messages.forEach(msg => { msg.severity = this.parseMessagesSeverity(msg.type, this.pythonSettings.linting.flake8CategorySeverity); }); diff --git a/src/client/linters/mypy.ts b/src/client/linters/mypy.ts index 1064488700d5..5b0930e660bd 100644 --- a/src/client/linters/mypy.ts +++ b/src/client/linters/mypy.ts @@ -13,7 +13,7 @@ export class MyPy extends BaseLinter { } protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise { - const messages = await this.run([document.uri.fsPath.fileToCommandArgument()], document, cancellation, REGEX); + const messages = await this.run([document.uri.fsPath], document, cancellation, REGEX); messages.forEach(msg => { msg.severity = this.parseMessagesSeverity(msg.type, this.pythonSettings.linting.mypyCategorySeverity); msg.code = msg.type; diff --git a/src/client/linters/pep8.ts b/src/client/linters/pep8.ts index e13d6c91c2b0..959923c6ad5e 100644 --- a/src/client/linters/pep8.ts +++ b/src/client/linters/pep8.ts @@ -13,7 +13,7 @@ export class Pep8 extends BaseLinter { } protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise { - const messages = await this.run(['--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', document.uri.fsPath.fileToCommandArgument()], document, cancellation); + const messages = await this.run(['--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', document.uri.fsPath], document, cancellation); messages.forEach(msg => { msg.severity = this.parseMessagesSeverity(msg.type, this.pythonSettings.linting.pep8CategorySeverity); }); diff --git a/src/client/linters/prospector.ts b/src/client/linters/prospector.ts index 8bbef82c46a5..5642c5433848 100644 --- a/src/client/linters/prospector.ts +++ b/src/client/linters/prospector.ts @@ -28,7 +28,7 @@ export class Prospector extends BaseLinter { } protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise { - return this.run(['--absolute-paths', '--output-format=json', document.uri.fsPath.fileToCommandArgument()], document, cancellation); + return this.run(['--absolute-paths', '--output-format=json', document.uri.fsPath], document, cancellation); } protected async parseMessages(output: string, document: TextDocument, token: CancellationToken, regEx: string) { let parsedData: IProspectorResponse; diff --git a/src/client/linters/pydocstyle.ts b/src/client/linters/pydocstyle.ts index c22944f421d6..b23d52f66945 100644 --- a/src/client/linters/pydocstyle.ts +++ b/src/client/linters/pydocstyle.ts @@ -13,7 +13,7 @@ export class PyDocStyle extends BaseLinter { } protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise { - const messages = await this.run([document.uri.fsPath.fileToCommandArgument()], document, cancellation); + const messages = await this.run([document.uri.fsPath], document, cancellation); // All messages in pep8 are treated as warnings for now. messages.forEach(msg => { msg.severity = LintMessageSeverity.Warning; diff --git a/src/client/linters/pylama.ts b/src/client/linters/pylama.ts index ef66bc5446c3..edee2b44898f 100644 --- a/src/client/linters/pylama.ts +++ b/src/client/linters/pylama.ts @@ -14,7 +14,7 @@ export class PyLama extends BaseLinter { } protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise { - const messages = await this.run(['--format=parsable', document.uri.fsPath.fileToCommandArgument()], document, cancellation, REGEX); + const messages = await this.run(['--format=parsable', document.uri.fsPath], document, cancellation, REGEX); // All messages in pylama are treated as warnings for now. messages.forEach(msg => { msg.severity = LintMessageSeverity.Warning; diff --git a/src/client/linters/pylint.ts b/src/client/linters/pylint.ts index 4998fe22a46a..1e830283127d 100644 --- a/src/client/linters/pylint.ts +++ b/src/client/linters/pylint.ts @@ -70,7 +70,7 @@ export class Pylint extends BaseLinter { '--msg-template=\'{line},{column},{category},{msg_id}:{msg}\'', '--reports=n', '--output-format=text', - uri.fsPath.fileToCommandArgument() + uri.fsPath ]; const messages = await this.run(minArgs.concat(args), document, cancellation); messages.forEach(msg => { diff --git a/src/test/linters/lint.args.test.ts b/src/test/linters/lint.args.test.ts index 259f87e38ef7..780aefb2fe61 100644 --- a/src/test/linters/lint.args.test.ts +++ b/src/test/linters/lint.args.test.ts @@ -110,32 +110,32 @@ suite('Linting - Arguments', () => { [Uri.file(path.join('users', 'development path to', 'one.py')), Uri.file(path.join('users', 'development', 'one.py'))].forEach(fileUri => { test(`Flake8 (${fileUri.fsPath.indexOf(' ') > 0 ? 'with spaces' : 'without spaces'})`, async () => { const linter = new Flake8(outputChannel.object, serviceContainer); - const expectedArgs = ['--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', fileUri.fsPath.fileToCommandArgument()]; + const expectedArgs = ['--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', fileUri.fsPath]; await testLinter(linter, fileUri, expectedArgs); }); test(`Pep8 (${fileUri.fsPath.indexOf(' ') > 0 ? 'with spaces' : 'without spaces'})`, async () => { const linter = new Pep8(outputChannel.object, serviceContainer); - const expectedArgs = ['--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', fileUri.fsPath.fileToCommandArgument()]; + const expectedArgs = ['--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', fileUri.fsPath]; await testLinter(linter, fileUri, expectedArgs); }); test(`Prospector (${fileUri.fsPath.indexOf(' ') > 0 ? 'with spaces' : 'without spaces'})`, async () => { const linter = new Prospector(outputChannel.object, serviceContainer); - const expectedArgs = ['--absolute-paths', '--output-format=json', fileUri.fsPath.fileToCommandArgument()]; + const expectedArgs = ['--absolute-paths', '--output-format=json', fileUri.fsPath]; await testLinter(linter, fileUri, expectedArgs); }); test(`Pylama (${fileUri.fsPath.indexOf(' ') > 0 ? 'with spaces' : 'without spaces'})`, async () => { const linter = new PyLama(outputChannel.object, serviceContainer); - const expectedArgs = ['--format=parsable', fileUri.fsPath.fileToCommandArgument()]; + const expectedArgs = ['--format=parsable', fileUri.fsPath]; await testLinter(linter, fileUri, expectedArgs); }); test(`MyPy (${fileUri.fsPath.indexOf(' ') > 0 ? 'with spaces' : 'without spaces'})`, async () => { const linter = new MyPy(outputChannel.object, serviceContainer); - const expectedArgs = [fileUri.fsPath.fileToCommandArgument()]; + const expectedArgs = [fileUri.fsPath]; await testLinter(linter, fileUri, expectedArgs); }); test(`Pydocstyle (${fileUri.fsPath.indexOf(' ') > 0 ? 'with spaces' : 'without spaces'})`, async () => { const linter = new PyDocStyle(outputChannel.object, serviceContainer); - const expectedArgs = [fileUri.fsPath.fileToCommandArgument()]; + const expectedArgs = [fileUri.fsPath]; await testLinter(linter, fileUri, expectedArgs); }); test(`Pylint (${fileUri.fsPath.indexOf(' ') > 0 ? 'with spaces' : 'without spaces'})`, async () => { @@ -144,7 +144,7 @@ suite('Linting - Arguments', () => { let invoked = false; (linter as any).run = (args, doc, token) => { - expect(args[args.length - 1]).to.equal(fileUri.fsPath.fileToCommandArgument()); + expect(args[args.length - 1]).to.equal(fileUri.fsPath); invoked = true; return Promise.resolve([]); }; From b4fbab7bbb3f48d23d6f8fca0b1d09b8c5754d94 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 30 Mar 2018 12:45:11 -0700 Subject: [PATCH 088/433] Use an existing method to identify the active interpreter (#1215) * :hammer: use getActiveInterpreter to get active interpreter * tests for module installer * :memo: news entry * Fixes #1015 --- news/2 Fixes/1015.md | 1 + .../common/installer/moduleInstaller.ts | 12 +-- src/client/interpreter/contracts.ts | 4 +- src/test/common/installer/installer.test.ts | 2 +- .../common/installer/moduleInstaller.test.ts | 78 +++++++++++++++++++ src/test/common/moduleInstaller.test.ts | 3 +- 6 files changed, 87 insertions(+), 13 deletions(-) create mode 100644 news/2 Fixes/1015.md create mode 100644 src/test/common/installer/moduleInstaller.test.ts diff --git a/news/2 Fixes/1015.md b/news/2 Fixes/1015.md new file mode 100644 index 000000000000..c88fd33a17eb --- /dev/null +++ b/news/2 Fixes/1015.md @@ -0,0 +1 @@ +Use an existing method to identify the active interpreter. \ No newline at end of file diff --git a/src/client/common/installer/moduleInstaller.ts b/src/client/common/installer/moduleInstaller.ts index 39b07c97bed0..f69401faa9ff 100644 --- a/src/client/common/installer/moduleInstaller.ts +++ b/src/client/common/installer/moduleInstaller.ts @@ -8,12 +8,11 @@ import * as fs from 'fs'; import { injectable } from 'inversify'; import * as path from 'path'; import * as vscode from 'vscode'; -import { IInterpreterLocatorService, INTERPRETER_LOCATOR_SERVICE, InterpreterType } from '../../interpreter/contracts'; +import { IInterpreterService, InterpreterType } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; import { PythonSettings } from '../configSettings'; import { STANDARD_OUTPUT_CHANNEL } from '../constants'; import { noop } from '../core.utils'; -import { IFileSystem } from '../platform/types'; import { ITerminalServiceFactory } from '../terminal/types'; import { ExecutionInfo, IOutputChannel } from '../types'; @@ -29,13 +28,8 @@ export abstract class ModuleInstaller { const args = ['-m', 'pip'].concat(executionInfo.args); const pythonPath = settings.pythonPath; - const locator = this.serviceContainer.get(IInterpreterLocatorService, INTERPRETER_LOCATOR_SERVICE); - const fileSystem = this.serviceContainer.get(IFileSystem); - const interpreters = await locator.getInterpreters(resource); - - const currentInterpreter = interpreters.length > 1 - ? interpreters.filter(x => fileSystem.arePathsSame(x.path, pythonPath))[0] - : interpreters[0]; + const interpreterService = this.serviceContainer.get(IInterpreterService); + const currentInterpreter = await interpreterService.getActiveInterpreter(resource); if (!currentInterpreter || currentInterpreter.type !== InterpreterType.Unknown) { await terminalService.sendCommand(pythonPath, args); diff --git a/src/client/interpreter/contracts.ts b/src/client/interpreter/contracts.ts index b71c77eb5ccd..d2540595fa96 100644 --- a/src/client/interpreter/contracts.ts +++ b/src/client/interpreter/contracts.ts @@ -45,10 +45,10 @@ export interface ICondaService { isCondaAvailable(): Promise; getCondaVersion(): Promise; getCondaInfo(): Promise; - getCondaEnvironments(ignoreCache: boolean): Promise<({ name: string, path: string }[]) | undefined>; + getCondaEnvironments(ignoreCache: boolean): Promise<({ name: string; path: string }[]) | undefined>; getInterpreterPath(condaEnvironmentPath: string): string; isCondaEnvironment(interpreterPath: string): Promise; - getCondaEnvironment(interpreterPath: string): Promise<{ name: string, path: string } | undefined>; + getCondaEnvironment(interpreterPath: string): Promise<{ name: string; path: string } | undefined>; } export enum InterpreterType { diff --git a/src/test/common/installer/installer.test.ts b/src/test/common/installer/installer.test.ts index 84ef685e3d78..b1c37cf6bb85 100644 --- a/src/test/common/installer/installer.test.ts +++ b/src/test/common/installer/installer.test.ts @@ -14,7 +14,7 @@ import { IServiceContainer } from '../../../client/ioc/types'; use(chaiAsPromised); // tslint:disable-next-line:max-func-body-length -suite('Module Installerx', () => { +suite('Module Installer', () => { [undefined, Uri.file('resource')].forEach(resource => { EnumEx.getNamesAndValues(Product).forEach(product => { let disposables: Disposable[] = []; diff --git a/src/test/common/installer/moduleInstaller.test.ts b/src/test/common/installer/moduleInstaller.test.ts new file mode 100644 index 000000000000..ce0fd56cb567 --- /dev/null +++ b/src/test/common/installer/moduleInstaller.test.ts @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as path from 'path'; +import * as TypeMoq from 'typemoq'; +import { Disposable} from 'vscode'; +import { CondaInstaller } from '../../../client/common/installer/condaInstaller'; +import { PipInstaller } from '../../../client/common/installer/pipInstaller'; +import { IInstallationChannelManager, IModuleInstaller } from '../../../client/common/installer/types'; +import { ITerminalService, ITerminalServiceFactory } from '../../../client/common/terminal/types'; +import { IConfigurationService, IDisposableRegistry, IPythonSettings } from '../../../client/common/types'; +import { ICondaService, IInterpreterService } from '../../../client/interpreter/contracts'; +import { IServiceContainer } from '../../../client/ioc/types'; +import { initialize } from '../../initialize'; + +// tslint:disable-next-line:max-func-body-length +suite('Module Installer', () => { + const pythonPath = path.join(__dirname, 'python'); + suiteSetup(initialize); + [CondaInstaller, PipInstaller].forEach(installerClass => { + let disposables: Disposable[] = []; + let installer: IModuleInstaller; + let installationChannel: TypeMoq.IMock; + let serviceContainer: TypeMoq.IMock; + let terminalService: TypeMoq.IMock; + let pythonSettings: TypeMoq.IMock; + let interpreterService: TypeMoq.IMock; + setup(() => { + serviceContainer = TypeMoq.Mock.ofType(); + + disposables = []; + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IDisposableRegistry), TypeMoq.It.isAny())).returns(() => disposables); + + installationChannel = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInstallationChannelManager), TypeMoq.It.isAny())).returns(() => installationChannel.object); + + const condaService = TypeMoq.Mock.ofType(); + condaService.setup(c => c.getCondaFile()).returns(() => Promise.resolve('conda')); + condaService.setup(c => c.getCondaEnvironment(TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); + + const configService = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationService), TypeMoq.It.isAny())).returns(() => configService); + pythonSettings = TypeMoq.Mock.ofType(); + pythonSettings.setup(p => p.pythonPath).returns(() => pythonPath); + configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); + + terminalService = TypeMoq.Mock.ofType(); + const terminalServiceFactory = TypeMoq.Mock.ofType(); + terminalServiceFactory.setup(f => f.getTerminalService(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => terminalService.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ITerminalServiceFactory), TypeMoq.It.isAny())).returns(() => terminalServiceFactory.object); + + interpreterService = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInterpreterService), TypeMoq.It.isAny())).returns(() => interpreterService.object); + + installer = new installerClass(serviceContainer.object); + }); + teardown(() => { + disposables.forEach(disposable => { + if (disposable) { + disposable.dispose(); + } + }); + }); + test(`Ensure getActiveInterperter is used (${installerClass.name})`, async () => { + if (installer.displayName !== 'Pip') { + return; + } + interpreterService.setup(i => i.getActiveInterpreter(TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)).verifiable(); + try { + await installer.installModule('xyz'); + // tslint:disable-next-line:no-empty + } catch { } + interpreterService.verifyAll(); + }); + }); +}); diff --git a/src/test/common/moduleInstaller.test.ts b/src/test/common/moduleInstaller.test.ts index b1b4d5c52fcb..72201771d5fc 100644 --- a/src/test/common/moduleInstaller.test.ts +++ b/src/test/common/moduleInstaller.test.ts @@ -202,6 +202,8 @@ suite('Module Installer', () => { mockTerminalService .setup(t => t.sendCommand(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())) .returns((cmd: string, args: string[]) => { argsSent = args; return Promise.resolve(void 0); }); + // tslint:disable-next-line:no-any + interpreterService.setup(i => i.getActiveInterpreter(TypeMoq.It.isAny())).returns(() => Promise.resolve({ type: InterpreterType.Unknown } as any)); await pipInstaller.installModule(moduleName); expect(argsSent.join(' ')).equal(`-m pip install -U ${moduleName} --user`, 'Invalid command sent to terminal for installation.'); @@ -225,7 +227,6 @@ suite('Module Installer', () => { mockTerminalService .setup(t => t.sendCommand(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())) .returns((cmd: string, args: string[]) => { argsSent = args; return Promise.resolve(void 0); }); - await pipInstaller.installModule(moduleName); expect(argsSent.join(' ')).equal(`-m pip install -U ${moduleName}`, 'Invalid command sent to terminal for installation.'); From af9a5d5d6a970ccdecf97bfa9ef74873406440ed Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 30 Mar 2018 12:46:11 -0700 Subject: [PATCH 089/433] Improved extension development experience on Windows (#1217) * split compilation for faster compilation on windows * :memo: update docs and change log * :hammer: rename linting (else auto fix is not available) [skip ci] * :hammer: minor changes [skip ci] * Fixes #1216 --- .vscode/settings.json | 2 +- .vscode/tasks.json | 37 ++++++++++++++++++++++++++++++++++++- CONTRIBUTING.md | 2 +- gulpfile.js | 13 +++++++------ news/3 Code Health/1216.md | 1 + 5 files changed, 46 insertions(+), 9 deletions(-) create mode 100644 news/3 Code Health/1216.md diff --git a/.vscode/settings.json b/.vscode/settings.json index be66f967c5c4..e359168fd3a0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -19,4 +19,4 @@ "python.unitTest.promptToConfigure": false, "python.workspaceSymbols.enabled": false, "python.formatting.provider": "none" -} \ No newline at end of file +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index f4384e2537bf..826c669b3eeb 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -9,6 +9,23 @@ "tasks": [ { "label": "Compile", + "type": "npm", + "script": "compile", + "isBackground": true, + "problemMatcher": [ + "$tsc-watch", + { + "base": "$tslint5", + "fileLocation": "relative" + } + ], + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "Hygiene", "type": "gulp", "task": "watch", "isBackground": true, @@ -18,6 +35,24 @@ "focus": false, "panel": "dedicated" }, + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [] + }, + { + // Use this to run hygiene and display errors in problems window (very slow on Windows) + "label": "Hygiene (Problems Window)", + "type": "gulp", + "task": "watchProblems", + "isBackground": true, + "presentation": { + "echo": true, + "reveal": "never", + "focus": false, + "panel": "dedicated" + }, "problemMatcher": [ { "applyTo": "allDocuments", @@ -70,4 +105,4 @@ } } ] -} +} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 79c6be5e1caa..e9ce6ff30d39 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,7 +28,7 @@ You may see warnings that ```The engine "vscode" appears to be invalid.```, you ### Incremental Build -Run the `Compile` build Tasks from the [Command Palette](https://code.visualstudio.com/docs/editor/tasks) (short cut `CTRL+SHIFT+B` or `⇧⌘B`) +Run the `Compile` and `Hygiene` build Tasks from the [Command Palette](https://code.visualstudio.com/docs/editor/tasks) (short cut `CTRL+SHIFT+B` or `⇧⌘B`) ### Errors and Warnings diff --git a/gulpfile.js b/gulpfile.js index 6a8cbadb7e77..ba89b0a92e2f 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -78,6 +78,9 @@ gulp.task('compile', () => run({ mode: 'compile', skipFormatCheck: true, skipInd gulp.task('watch', ['hygiene-modified', 'hygiene-watch']); +// Duplicate to allow duplicate task in tasks.json (one ith problem matching, and one without) +gulp.task('watchProblems', ['hygiene-modified', 'hygiene-watch']); + gulp.task('debugger-coverage', () => buildDebugAdapterCoverage()); gulp.task('hygiene-watch', () => gulp.watch(tsFilter, debounce(() => run({ mode: 'changes', skipFormatCheck: true, skipIndentationCheck: true, skipCopyrightCheck: true }), 100))); @@ -149,16 +152,14 @@ function getTsProject(options) { } let configuration; -let program; -let linter; /** * * @param {hygieneOptions} options */ function getLinter(options) { configuration = configuration ? configuration : tslint.Configuration.findConfiguration(null, '.'); - program = program ? program : tslint.Linter.createProgram('./tsconfig.json'); - linter = linter ? linter : new tslint.Linter({ formatter: 'json' }, program); + const program = tslint.Linter.createProgram('./tsconfig.json'); + const linter = new tslint.Linter({ formatter: 'json' }, program); return { linter, configuration }; } let compilationInProgress = false; @@ -439,7 +440,7 @@ function getAddedFilesSync() { return out .split(/\r?\n/) .filter(l => !!l) - .filter(l => _.intersection(['A', '?'], l.substring(0, 2).trim().split()).length > 0) + .filter(l => _.intersection(['A', '?', 'U'], l.substring(0, 2).trim().split('')).length > 0) .map(l => path.join(__dirname, l.substring(2).trim())); } function getModifiedFilesSync() { @@ -447,7 +448,7 @@ function getModifiedFilesSync() { return out .split(/\r?\n/) .filter(l => !!l) - .filter(l => _.intersection(['M', 'A', 'R', 'C'], l.substring(0, 2).trim().split()).length > 0) + .filter(l => _.intersection(['M', 'A', 'R', 'C', 'U', '?'], l.substring(0, 2).trim().split('')).length > 0) .map(l => path.join(__dirname, l.substring(2).trim())); } diff --git a/news/3 Code Health/1216.md b/news/3 Code Health/1216.md new file mode 100644 index 000000000000..884f1ba6286a --- /dev/null +++ b/news/3 Code Health/1216.md @@ -0,0 +1 @@ +Improved developer experience of the Python Extension on Windows. \ No newline at end of file From 857d23684528687c5fc16befbd68095767a9a9e9 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Fri, 30 Mar 2018 13:44:07 -0700 Subject: [PATCH 090/433] VS Python analysis engine integration (#1231) * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * Undo changes * Test fixes * .NET Core check * Better find dotnet * Fix pip test * Linting tests * Undo accidental changes * Add clone and build PTVS * Appveyor PTVS build * Fix slashes * Enable build * Try absolute path * Fix xcopy switch * Activate Analysis Engine test on Appveyor * Temporary only run new tests * Disable PEP hint tests * Test fix * Disable appveyor build and tests for PTVS for now * Remove analysis engine test from the set * Remove VS image for now * Build/sign VSXI project * Run vsce from cmd * Rename * Abs path vsce * Path * Move project * Ignore publishing project * Try csproj * Add framework * Ignore build output folder * Package before build * Try batch instead of PS * Fix path quotes * #1096 The if statement is automatically formatted incorrectly * Merge fix * Add more tests * More tests * Typo * Test * Also better handle multiline arguments * Changes lost on squash * More lost changes * Restore Jedi/PTVS setting * Update tests to new PTVS * Signature tests * Add PTVS tests task * Analysis Engine contribution * Add Mac/Linux info * Disable csproj build * Add unzip to dependencies * Minor fixes to doc * Change setting type to bool * Report progress on status bar * Simplify * CR feedback * Fix launching fx-independent code on Mac/Linux * Add title --- .gitignore | 2 + .vscode/launch.json | 22 +- .vscodeignore | 8 + CONTRIBUTING - PYTHON_ANALYSIS.md | 61 ++ CONTRIBUTING.md | 3 +- appveyor.yml | 17 +- package.json | 9 + packageExtension.cmd | 1 + src/client/activation/analysis.ts | 270 +++++++ src/client/activation/analysisEngineHashes.ts | 9 + src/client/activation/classic.ts | 62 ++ src/client/activation/downloader.ts | 129 +++ src/client/activation/hashVerifier.ts | 28 + src/client/activation/platformData.ts | 49 ++ src/client/activation/types.ts | 9 + src/client/common/configSettings.ts | 761 +++++++++--------- src/client/common/configuration/service.ts | 31 +- src/client/common/types.ts | 16 +- src/client/extension.ts | 396 +++++---- src/test/.vscode/settings.json | 10 +- src/test/analysisEngineTest.ts | 16 + src/test/autocomplete/base.test.ts | 470 ++++++----- src/test/autocomplete/pep484.test.ts | 23 +- src/test/autocomplete/pep526.test.ts | 49 +- src/test/common/moduleInstaller.test.ts | 10 +- .../pythonProc.simple.multiroot.test.ts | 5 +- src/test/constants.ts | 39 +- .../{hover.test.ts => hover.jedi.test.ts} | 559 ++++++------- src/test/definitions/hover.ptvs.test.ts | 184 +++++ ...parallel.test.ts => parallel.jedi.test.ts} | 94 ++- src/test/definitions/parallel.ptvs.test.ts | 57 ++ src/test/linters/lint.commands.test.ts | 3 +- src/test/linters/lint.manager.test.ts | 2 + src/test/pythonFiles/autocomp/four.py | 2 +- src/test/pythonFiles/definition/four.py | 2 +- ...gnature.test.ts => signature.jedi.test.ts} | 9 +- src/test/signature/signature.ptvs.test.ts | 146 ++++ vscode-python-signing.csproj | 20 + 38 files changed, 2376 insertions(+), 1207 deletions(-) create mode 100644 CONTRIBUTING - PYTHON_ANALYSIS.md create mode 100644 packageExtension.cmd create mode 100644 src/client/activation/analysis.ts create mode 100644 src/client/activation/analysisEngineHashes.ts create mode 100644 src/client/activation/classic.ts create mode 100644 src/client/activation/downloader.ts create mode 100644 src/client/activation/hashVerifier.ts create mode 100644 src/client/activation/platformData.ts create mode 100644 src/client/activation/types.ts create mode 100644 src/test/analysisEngineTest.ts rename src/test/definitions/{hover.test.ts => hover.jedi.test.ts} (96%) create mode 100644 src/test/definitions/hover.ptvs.test.ts rename src/test/definitions/{parallel.test.ts => parallel.jedi.test.ts} (87%) create mode 100644 src/test/definitions/parallel.ptvs.test.ts rename src/test/signature/{signature.test.ts => signature.jedi.test.ts} (95%) create mode 100644 src/test/signature/signature.ptvs.test.ts create mode 100644 vscode-python-signing.csproj diff --git a/.gitignore b/.gitignore index cc941e968ccb..47c9018661ee 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ coverage/ pythonFiles/experimental/ptvsd/** debug_coverage*/** analysis/** +bin/** +obj/** diff --git a/.vscode/launch.json b/.vscode/launch.json index 4daf91a9249b..35f13ae91111 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -87,6 +87,26 @@ ], "preLaunchTask": "Compile" }, + { + "name": "Launch Analysis Engine Tests", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "${workspaceFolder}/src/test", + "--extensionDevelopmentPath=${workspaceFolder}", + "--extensionTestsPath=${workspaceFolder}/out/test" + ], + "stopOnEntry": false, + "sourceMaps": true, + "outFiles": [ + "${workspaceFolder}/out/**/*.js" + ], + "preLaunchTask": "Compile", + "env": { + "VSC_PYTHON_ANALYSIS": "1" + } + }, { "name": "Launch Tests (with code coverage)", "type": "extensionHost", @@ -114,4 +134,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/.vscodeignore b/.vscodeignore index ae242dcf887d..b762bf75dfc7 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -37,3 +37,11 @@ CODING_STANDARDS.md CONTRIBUTING.md news/** debug_coverage*/** +analysis/publish*.* +vscode-python-signing.* +packageExtension.cmd +bin/** +obj/** +BuildOutput/** + + diff --git a/CONTRIBUTING - PYTHON_ANALYSIS.md b/CONTRIBUTING - PYTHON_ANALYSIS.md new file mode 100644 index 000000000000..03563dac7efd --- /dev/null +++ b/CONTRIBUTING - PYTHON_ANALYSIS.md @@ -0,0 +1,61 @@ +# Contributing to Microsoft Python Analysis Engine +[![Contributing to Python Tools for Visual Studio](https://github.com/Microsoft/PTVS/blob/master/CONTRIBUTING.md)] + +[![Build Status (Travis)](https://travis-ci.org/Microsoft/vscode-python.svg?branch=master)](https://travis-ci.org/Microsoft/vscode-python) [![Build status (AppVeyor)](https://ci.appveyor.com/api/projects/status/s0pt8d79gqw222j7?svg=true)](https://ci.appveyor.com/project/DonJayamanne/vscode-python-v3vd6) [![codecov](https://codecov.io/gh/Microsoft/vscode-python/branch/master/graph/badge.svg)](https://codecov.io/gh/Microsoft/vscode-python) + + +## Contributing a pull request + +### Prerequisites + +1. .NET Core 2.0+ SDK + - [Windows](https://www.microsoft.com/net/learn/get-started/windows) + - [Mac OS](https://www.microsoft.com/net/learn/get-started/macos) + - [Linux](https://www.microsoft.com/net/learn/get-started/linux/rhel) +2. C# Extension to VS Code (all platforms) +3. Python 2.7 +4. Python 3.6 + +*Alternative:* [Visual Studio 2017](https://www.visualstudio.com/downloads/) (Windows only) with .NET Core and C# Workloads. Community Edition is free and is fully functional. + +### Setup + +```shell +git clone https://github.com/microsoft/ptvs +cd Python/Product/VsCode/AnalysisVsc +dotnet build +``` + +Visual Studio 2017: +1. Open solution in Python/Product/VsCode +2. Build AnalysisVsc project +3. Binaries arrive in *Python/BuildOutput/VsCode/raw* +4. Delete contents of the *analysis* folder in the Python Extension folder +5. Copy *.dll, *.pdb, *.json fron *Python/BuildOutput/VsCode/raw* to *analysis* + +### Debugging code in Python Extension to VS Code +Folow regular TypeScript debugging steps + +### Debugging C# code in Python Analysis Engine +1. Launch another instance of VS Code +2. Open Python/Product/VsCode/AnalysisVsc folder +3. Python Analysis Engine code is in *Python/Product/VsCode/Analysis* +4. Run extension from VS Code +5. In the instance with C# code select Dotnet Attach launch task. +6. Attach to *dotnet* process running *Microsoft.PythonTools.VsCode.dll* + +On Windows you can also attach from Visual Studio 2017. + +### Validate your changes + +1. Build C# code +2. Copy binaries to *analysis* folder +3. Use the `Launch Extension` launch option. + +### Unit Tests +1. Run the Unit Tests via the `Launch Analysis Engine Tests`. +2. On Windows you can also open complete PTVS solution in Visual Studio and run its tests (or at least the Analysis section). + + +### Coding Standards +See [![Contributing to Python Tools for Visual Studio](https://github.com/Microsoft/PTVS/blob/master/CONTRIBUTING.md)] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e9ce6ff30d39..9e066b582c40 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,8 @@ [![Build Status (Travis)](https://travis-ci.org/Microsoft/vscode-python.svg?branch=master)](https://travis-ci.org/Microsoft/vscode-python) [![Build status (AppVeyor)](https://ci.appveyor.com/api/projects/status/s0pt8d79gqw222j7?svg=true)](https://ci.appveyor.com/project/DonJayamanne/vscode-python-v3vd6) [![codecov](https://codecov.io/gh/Microsoft/vscode-python/branch/master/graph/badge.svg)](https://codecov.io/gh/Microsoft/vscode-python) - +# Contributing to Microsoft Python Analysis Engine +[![Contributing to Python Analysis Engine](https://github.com/Microsoft/vscode-python/blob/master/CONTRIBUTING - PYTHON_ANALYSIS.md)] ## Contributing a pull request diff --git a/appveyor.yml b/appveyor.yml index 774b4d93ca18..3b80a7267a18 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,3 +1,6 @@ +#image: Visual Studio 2017 +#shallow_clone: true + environment: matrix: - PYTHON: "C:\\Python36" @@ -20,11 +23,23 @@ install: - python -m easy_install -U setuptools - "%PYTHON%/Scripts/pip.exe install --upgrade -r requirements.txt" +build: off +# build_script: +# - git clone https://github.com/MikhailArkhipov/PTVS.git c:/projects/PTVS +# - "cd c:\\projects\\PTVS" +# - git checkout origin/vsc +# - "cd Python\\Product\\VSCode\\AnalysisVsc" +# - "dotnet --info" +# - "dotnet build" +# - "cd c:\\projects\\vscode-python" +# - "xcopy /S /I c:\\projects\\PTVS\\BuildOutput\\VsCode\\raw analysis" + test_script: - yarn run clean - yarn run vscode:prepublish - yarn run testDebugger --silent - yarn run testSingleWorkspace --silent - yarn run testMultiWorkspace --silent + # - yarn run testAnalysisEngine --silent + -build: off diff --git a/package.json b/package.json index 2accd2c00a2b..81128aa16bb6 100644 --- a/package.json +++ b/package.json @@ -1052,6 +1052,12 @@ "default": "${workspaceFolder}/.env", "scope": "resource" }, + "python.jediEnabled": { + "type": "boolean", + "default": true, + "description": "Enables Jedi as IntelliSense engine instead of Microsoft Python Analysis Engine.", + "scope": "resource" + }, "python.jediPath": { "type": "string", "default": "", @@ -1727,6 +1733,7 @@ "testDebugger": "node ./out/test/debuggerTest.js", "testSingleWorkspace": "node ./out/test/standardTest.js", "testMultiWorkspace": "node ./out/test/multiRootTest.js", + "testAnalysisEngine": "node ./out/test/analysisEngineTest.js", "precommit": "node gulpfile.js", "lint-staged": "node gulpfile.js", "lint": "tslint src/**/*.ts -t verbose", @@ -1750,6 +1757,7 @@ "opn": "^5.1.0", "pidusage": "^1.2.0", "reflect-metadata": "^0.1.12", + "request-progress": "^3.0.0", "rxjs": "^5.5.2", "semver": "^5.4.1", "sudo-prompt": "^8.0.0", @@ -1759,6 +1767,7 @@ "uint64be": "^1.0.1", "unicode": "^10.0.0", "untildify": "^3.0.2", + "unzip": "^0.1.11", "vscode-debugadapter": "^1.0.1", "vscode-debugprotocol": "^1.0.1", "vscode-extension-telemetry": "^0.0.14", diff --git a/packageExtension.cmd b/packageExtension.cmd new file mode 100644 index 000000000000..dc3026c20f3e --- /dev/null +++ b/packageExtension.cmd @@ -0,0 +1 @@ +%1\vsce package --out %2 \ No newline at end of file diff --git a/src/client/activation/analysis.ts b/src/client/activation/analysis.ts new file mode 100644 index 000000000000..3590d52b951c --- /dev/null +++ b/src/client/activation/analysis.ts @@ -0,0 +1,270 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as path from 'path'; +import { ExtensionContext, OutputChannel } from 'vscode'; +import { Disposable, LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient'; +import { IApplicationShell } from '../common/application/types'; +import { isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; +import '../common/extensions'; +import { IFileSystem, IPlatformService } from '../common/platform/types'; +import { IProcessService, IPythonExecutionFactory } from '../common/process/types'; +import { StopWatch } from '../common/stopWatch'; +import { IConfigurationService, IOutputChannel, IPythonSettings } from '../common/types'; +import { IInterpreterService } from '../interpreter/contracts'; +import { IServiceContainer } from '../ioc/types'; +import { AnalysisEngineDownloader } from './downloader'; +import { PlatformData } from './platformData'; +import { IExtensionActivator } from './types'; + +const PYTHON = 'python'; +const dotNetCommand = 'dotnet'; +const languageClientName = 'Python Tools'; +const analysisEngineFolder = 'analysis'; + +class InterpreterData { + constructor(public readonly version: string, public readonly prefix: string) { } +} + +export class AnalysisExtensionActivator implements IExtensionActivator { + private readonly executionFactory: IPythonExecutionFactory; + private readonly configuration: IConfigurationService; + private readonly appShell: IApplicationShell; + private readonly output: OutputChannel; + private readonly fs: IFileSystem; + private readonly sw = new StopWatch(); + private readonly platformData: PlatformData; + private languageClient: LanguageClient | undefined; + + constructor(private readonly services: IServiceContainer, pythonSettings: IPythonSettings) { + this.executionFactory = this.services.get(IPythonExecutionFactory); + this.configuration = this.services.get(IConfigurationService); + this.appShell = this.services.get(IApplicationShell); + this.output = this.services.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); + this.fs = this.services.get(IFileSystem); + this.platformData = new PlatformData(services.get(IPlatformService)); + } + + public async activate(context: ExtensionContext): Promise { + const clientOptions = await this.getAnalysisOptions(context); + if (!clientOptions) { + return false; + } + this.output.appendLine(`Options determined: ${this.sw.elapsedTime} ms`); + return this.startLanguageServer(context, clientOptions); + } + + public async deactivate(): Promise { + if (this.languageClient) { + await this.languageClient.stop(); + } + } + + private async startLanguageServer(context: ExtensionContext, clientOptions: LanguageClientOptions): Promise { + // Determine if we are running MSIL/Universal via dotnet or self-contained app. + const mscorlib = path.join(context.extensionPath, analysisEngineFolder, 'mscorlib.dll'); + let downloadPackage = false; + + if (!await this.fs.fileExistsAsync(mscorlib)) { + // Depends on .NET Runtime or SDK + this.languageClient = this.createSimpleLanguageClient(context, clientOptions); + const e = await this.tryStartLanguageClient(context, this.languageClient); + if (!e) { + return true; + } + if (await this.isDotNetInstalled()) { + this.appShell.showErrorMessage(`.NET Runtime appears to be installed but the language server did not start. Error ${e}`); + return false; + } + // No .NET Runtime, no mscorlib - need to download self-contained package. + downloadPackage = true; + } + + if (downloadPackage) { + const downloader = new AnalysisEngineDownloader(this.services, analysisEngineFolder); + await downloader.downloadAnalysisEngine(context); + } + + const serverModule = path.join(context.extensionPath, analysisEngineFolder, this.platformData.getEngineExecutableName()); + // Now try to start self-contained app + this.languageClient = this.createSelfContainedLanguageClient(context, serverModule, clientOptions); + const error = await this.tryStartLanguageClient(context, this.languageClient); + if (!error) { + return true; + } + this.appShell.showErrorMessage(`Language server failed to start. Error ${error}`); + return false; + } + + private async tryStartLanguageClient(context: ExtensionContext, lc: LanguageClient): Promise { + let disposable: Disposable | undefined; + try { + disposable = lc.start(); + await lc.onReady(); + this.output.appendLine(`Language server ready: ${this.sw.elapsedTime} ms`); + context.subscriptions.push(disposable); + } catch (ex) { + if (disposable) { + disposable.dispose(); + return ex; + } + } + } + + private createSimpleLanguageClient(context: ExtensionContext, clientOptions: LanguageClientOptions): LanguageClient { + const commandOptions = { stdio: 'pipe' }; + const serverModule = path.join(context.extensionPath, analysisEngineFolder, this.platformData.getEngineDllName()); + const serverOptions: ServerOptions = { + run: { command: dotNetCommand, args: [serverModule], options: commandOptions }, + debug: { command: dotNetCommand, args: [serverModule, '--debug'], options: commandOptions } + }; + return new LanguageClient(PYTHON, languageClientName, serverOptions, clientOptions); + } + + private createSelfContainedLanguageClient(context: ExtensionContext, serverModule: string, clientOptions: LanguageClientOptions): LanguageClient { + const options = { stdio: 'pipe' }; + const serverOptions: ServerOptions = { + run: { command: serverModule, rgs: [], options: options }, + debug: { command: serverModule, args: ['--debug'], options } + }; + return new LanguageClient(PYTHON, languageClientName, serverOptions, clientOptions); + } + + private async getAnalysisOptions(context: ExtensionContext): Promise { + // tslint:disable-next-line:no-any + const properties = new Map(); + + // Microsoft Python code analysis engine needs full path to the interpreter + const interpreterService = this.services.get(IInterpreterService); + const interpreter = await interpreterService.getActiveInterpreter(); + + if (interpreter) { + // tslint:disable-next-line:no-string-literal + properties['InterpreterPath'] = interpreter.path; + if (interpreter.displayName) { + // tslint:disable-next-line:no-string-literal + properties['Description'] = interpreter.displayName; + } + const interpreterData = await this.getInterpreterData(); + + // tslint:disable-next-line:no-string-literal + properties['Version'] = interpreterData.version; + // tslint:disable-next-line:no-string-literal + properties['PrefixPath'] = interpreterData.prefix; + // tslint:disable-next-line:no-string-literal + properties['DatabasePath'] = path.join(context.extensionPath, analysisEngineFolder); + + let searchPaths = await this.getSearchPaths(); + const settings = this.configuration.getSettings(); + if (settings.autoComplete) { + const extraPaths = settings.autoComplete.extraPaths; + if (extraPaths && extraPaths.length > 0) { + searchPaths = `${searchPaths};${extraPaths.join(';')}`; + } + } + // tslint:disable-next-line:no-string-literal + properties['SearchPaths'] = searchPaths; + + if (isTestExecution()) { + // tslint:disable-next-line:no-string-literal + properties['TestEnvironment'] = true; + } + } else { + const appShell = this.services.get(IApplicationShell); + const pythonPath = this.configuration.getSettings().pythonPath; + appShell.showErrorMessage(`Interpreter ${pythonPath} does not exist.`); + return; + } + + const selector: string[] = [PYTHON]; + // Options to control the language client + return { + // Register the server for Python documents + documentSelector: selector, + synchronize: { + configurationSection: PYTHON + }, + outputChannel: this.output, + initializationOptions: { + interpreter: { + properties + } + } + }; + } + + private async getInterpreterData(): Promise { + // Not appropriate for multiroot workspaces. + // See https://github.com/Microsoft/vscode-python/issues/1149 + const execService = await this.executionFactory.create(); + const result = await execService.exec(['-c', 'import sys; print(sys.version_info); print(sys.prefix)'], {}); + // 2.7.14 (v2.7.14:84471935ed, Sep 16 2017, 20:19:30) <> + // [MSC v.1500 32 bit (Intel)] + // C:\Python27 + if (!result.stdout) { + throw Error('Unable to determine Python interpreter version and system prefix.'); + } + const output = result.stdout.splitLines({ removeEmptyEntries: true, trim: true }); + if (!output || output.length < 2) { + throw Error('Unable to parse version and and system prefix from the Python interpreter output.'); + } + const majorMatches = output[0].match(/major=(\d*?),/); + const minorMatches = output[0].match(/minor=(\d*?),/); + if (!majorMatches || majorMatches.length < 2 || !minorMatches || minorMatches.length < 2) { + throw Error('Unable to parse interpreter version.'); + } + const prefix = output[output.length - 1]; + return new InterpreterData(`${majorMatches[1]}.${minorMatches[1]}`, prefix); + } + + private async getSearchPaths(): Promise { + // Not appropriate for multiroot workspaces. + // See https://github.com/Microsoft/vscode-python/issues/1149 + const execService = await this.executionFactory.create(); + const result = await execService.exec(['-c', 'import sys; print(sys.path);'], {}); + if (!result.stdout) { + throw Error('Unable to determine Python interpreter search paths.'); + } + // tslint:disable-next-line:no-unnecessary-local-variable + const paths = result.stdout.split(',') + .filter(p => this.isValidPath(p)) + .map(p => this.pathCleanup(p)); + return paths.join(';'); + } + + private pathCleanup(s: string): string { + s = s.trim(); + if (s[0] === '\'') { + s = s.substr(1); + } + if (s[s.length - 1] === ']') { + s = s.substr(0, s.length - 1); + } + if (s[s.length - 1] === '\'') { + s = s.substr(0, s.length - 1); + } + return s; + } + + private isValidPath(s: string): boolean { + return s.length > 0 && s[0] !== '['; + } + + // private async checkNetCoreRuntime(): Promise { + // if (!await this.isDotNetInstalled()) { + // const appShell = this.services.get(IApplicationShell); + // if (await appShell.showErrorMessage('Python Tools require .NET Core Runtime. Would you like to install it now?', 'Yes', 'No') === 'Yes') { + // appShell.openUrl('https://www.microsoft.com/net/download/core#/runtime'); + // appShell.showWarningMessage('Please restart VS Code after .NET Runtime installation is complete.'); + // } + // return false; + // } + // return true; + // } + + private async isDotNetInstalled(): Promise { + const ps = this.services.get(IProcessService); + const result = await ps.exec('dotnet', ['--version']).catch(() => { return { stdout: '' }; }); + return result.stdout.trim().startsWith('2.'); + } +} diff --git a/src/client/activation/analysisEngineHashes.ts b/src/client/activation/analysisEngineHashes.ts new file mode 100644 index 000000000000..52761329113e --- /dev/null +++ b/src/client/activation/analysisEngineHashes.ts @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// This file will be replaced by a generated one during the release build +// with actual hashes of the uploaded packages. +export const analysis_engine_win_x86_sha512 = ''; +export const analysis_engine_win_x64_sha512 = ''; +export const analysis_engine_osx_x64_sha512 = ''; +export const analysis_engine_linux_x64_sha512 = ''; diff --git a/src/client/activation/classic.ts b/src/client/activation/classic.ts new file mode 100644 index 000000000000..61b82fae79f9 --- /dev/null +++ b/src/client/activation/classic.ts @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { DocumentFilter, ExtensionContext, languages, OutputChannel } from 'vscode'; +import { STANDARD_OUTPUT_CHANNEL } from '../common/constants'; +import { IOutputChannel, IPythonSettings } from '../common/types'; +import { IShebangCodeLensProvider } from '../interpreter/contracts'; +import { IServiceManager } from '../ioc/types'; +import { JediFactory } from '../languageServices/jediProxyFactory'; +import { PythonCompletionItemProvider } from '../providers/completionProvider'; +import { PythonDefinitionProvider } from '../providers/definitionProvider'; +import { PythonHoverProvider } from '../providers/hoverProvider'; +import { activateGoToObjectDefinitionProvider } from '../providers/objectDefinitionProvider'; +import { PythonReferenceProvider } from '../providers/referenceProvider'; +import { PythonRenameProvider } from '../providers/renameProvider'; +import { PythonSignatureProvider } from '../providers/signatureProvider'; +import { activateSimplePythonRefactorProvider } from '../providers/simpleRefactorProvider'; +import { PythonSymbolProvider } from '../providers/symbolProvider'; +import { TEST_OUTPUT_CHANNEL } from '../unittests/common/constants'; +import * as tests from '../unittests/main'; +import { IExtensionActivator } from './types'; + +const PYTHON: DocumentFilter = { language: 'python' }; + +export class ClassicExtensionActivator implements IExtensionActivator { + constructor(private serviceManager: IServiceManager, private pythonSettings: IPythonSettings) { + } + + public async activate(context: ExtensionContext): Promise { + const standardOutputChannel = this.serviceManager.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); + activateSimplePythonRefactorProvider(context, standardOutputChannel, this.serviceManager); + + const jediFactory = new JediFactory(context.asAbsolutePath('.'), this.serviceManager); + context.subscriptions.push(jediFactory); + context.subscriptions.push(...activateGoToObjectDefinitionProvider(jediFactory)); + + context.subscriptions.push(jediFactory); + context.subscriptions.push(languages.registerRenameProvider(PYTHON, new PythonRenameProvider(this.serviceManager))); + const definitionProvider = new PythonDefinitionProvider(jediFactory); + + context.subscriptions.push(languages.registerDefinitionProvider(PYTHON, definitionProvider)); + context.subscriptions.push(languages.registerHoverProvider(PYTHON, new PythonHoverProvider(jediFactory))); + context.subscriptions.push(languages.registerReferenceProvider(PYTHON, new PythonReferenceProvider(jediFactory))); + context.subscriptions.push(languages.registerCompletionItemProvider(PYTHON, new PythonCompletionItemProvider(jediFactory, this.serviceManager), '.')); + context.subscriptions.push(languages.registerCodeLensProvider(PYTHON, this.serviceManager.get(IShebangCodeLensProvider))); + + const symbolProvider = new PythonSymbolProvider(jediFactory); + context.subscriptions.push(languages.registerDocumentSymbolProvider(PYTHON, symbolProvider)); + + if (this.pythonSettings.devOptions.indexOf('DISABLE_SIGNATURE') === -1) { + context.subscriptions.push(languages.registerSignatureHelpProvider(PYTHON, new PythonSignatureProvider(jediFactory), '(', ',')); + } + + const unitTestOutChannel = this.serviceManager.get(IOutputChannel, TEST_OUTPUT_CHANNEL); + tests.activate(context, unitTestOutChannel, symbolProvider, this.serviceManager); + + return true; + } + + // tslint:disable-next-line:no-empty + public async deactivate(): Promise { } +} diff --git a/src/client/activation/downloader.ts b/src/client/activation/downloader.ts new file mode 100644 index 000000000000..de634d627316 --- /dev/null +++ b/src/client/activation/downloader.ts @@ -0,0 +1,129 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as fs from 'fs'; +import * as path from 'path'; +import * as request from 'request'; +import * as requestProgress from 'request-progress'; +import * as unzip from 'unzip'; +import { ExtensionContext, OutputChannel, ProgressLocation, window } from 'vscode'; +import { STANDARD_OUTPUT_CHANNEL } from '../common/constants'; +import { noop } from '../common/core.utils'; +import { createDeferred, createTemporaryFile } from '../common/helpers'; +import { IPlatformService } from '../common/platform/types'; +import { IOutputChannel } from '../common/types'; +import { IServiceContainer } from '../ioc/types'; +import { HashVerifier } from './hashVerifier'; +import { PlatformData } from './platformData'; + +const downloadUriPrefix = 'https://pvsc.blob.core.windows.net/python-analysis'; +const downloadBaseFileName = 'python-analysis-vscode'; +const downloadVersion = '0.1.0'; +const downloadFileExtension = '.nupkg'; + +export class AnalysisEngineDownloader { + private readonly output: OutputChannel; + private readonly platform: IPlatformService; + private readonly platformData: PlatformData; + + constructor(private readonly services: IServiceContainer, private engineFolder: string) { + this.output = this.services.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); + this.platform = this.services.get(IPlatformService); + this.platformData = new PlatformData(this.platform); + } + + public async downloadAnalysisEngine(context: ExtensionContext): Promise { + const localTempFilePath = await this.downloadFile(); + try { + await this.verifyDownload(localTempFilePath); + await this.unpackArchive(context.extensionPath, localTempFilePath); + } catch (err) { + this.output.appendLine('failed.'); + this.output.appendLine(err); + throw new Error(err); + } finally { + fs.unlink(localTempFilePath, noop); + } + } + + private async downloadFile(): Promise { + const platformString = this.platformData.getPlatformDesignator(); + const remoteFileName = `${downloadBaseFileName}-${platformString}.${downloadVersion}${downloadFileExtension}`; + const uri = `${downloadUriPrefix}/${remoteFileName}`; + this.output.append(`Downloading ${uri}... `); + const tempFile = await createTemporaryFile(downloadFileExtension); + + const deferred = createDeferred(); + const fileStream = fs.createWriteStream(tempFile.filePath); + fileStream.on('finish', () => { + fileStream.close(); + }).on('error', (err) => { + tempFile.cleanupCallback(); + deferred.reject(err); + }); + + const title = 'Downloading Python Analysis Engine... '; + await window.withProgress({ + location: ProgressLocation.Window, + title + }, (progress) => { + + requestProgress(request(uri)) + .on('progress', (state) => { + // https://www.npmjs.com/package/request-progress + const received = Math.round(state.size.transferred / 1024); + const total = Math.round(state.size.total / 1024); + const percentage = Math.round(100 * state.percent); + progress.report({ + message: `${title}${received} of ${total} KB (${percentage}%)` + }); + }) + .on('error', (err) => { + deferred.reject(err); + }) + .on('end', () => { + this.output.append('complete.'); + deferred.resolve(); + }) + .pipe(fileStream); + return deferred.promise; + }); + + return tempFile.filePath; + } + + private async verifyDownload(filePath: string): Promise { + this.output.appendLine(''); + this.output.append('Verifying download... '); + const verifier = new HashVerifier(); + if (!await verifier.verifyHash(filePath, this.platformData.getExpectedHash())) { + throw new Error('Hash of the downloaded file does not match.'); + } + this.output.append('valid.'); + } + + private async unpackArchive(extensionPath: string, tempFilePath: string): Promise { + this.output.appendLine(''); + this.output.append('Unpacking archive... '); + + const installFolder = path.join(extensionPath, this.engineFolder); + const deferred = createDeferred(); + + fs.createReadStream(tempFilePath) + .pipe(unzip.Extract({ path: installFolder })) + .on('finish', () => { + deferred.resolve(); + }) + .on('error', (err) => { + deferred.reject(err); + }); + await deferred.promise; + this.output.append('done.'); + + // Set file to executable + if (!this.platform.isWindows) { + const executablePath = path.join(installFolder, this.platformData.getEngineExecutableName()); + fs.chmodSync(executablePath, '0764'); // -rwxrw-r-- + } + } +} diff --git a/src/client/activation/hashVerifier.ts b/src/client/activation/hashVerifier.ts new file mode 100644 index 000000000000..950f02d869f9 --- /dev/null +++ b/src/client/activation/hashVerifier.ts @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { createHash } from 'crypto'; +import * as fs from 'fs'; +import { createDeferred } from '../common/helpers'; + +export class HashVerifier { + public async verifyHash(filePath: string, expectedDigest: string): Promise { + const readStream = fs.createReadStream(filePath); + const deferred = createDeferred(); + const hash = createHash('sha512'); + hash.setEncoding('hex'); + readStream + .on('end', () => { + hash.end(); + deferred.resolve(); + }) + .on('error', (err) => { + deferred.reject(`Unable to calculate file hash. Error ${err}`); + }); + + readStream.pipe(hash); + await deferred.promise; + const actual = hash.read(); + return expectedDigest === '' ? true : actual === expectedDigest; + } +} diff --git a/src/client/activation/platformData.ts b/src/client/activation/platformData.ts new file mode 100644 index 000000000000..541e5a602bef --- /dev/null +++ b/src/client/activation/platformData.ts @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { IPlatformService } from '../common/platform/types'; +import { + analysis_engine_linux_x64_sha512, + analysis_engine_osx_x64_sha512, + analysis_engine_win_x64_sha512, + analysis_engine_win_x86_sha512 +} from './analysisEngineHashes'; + +export class PlatformData { + constructor(private platform: IPlatformService) { } + public getPlatformDesignator(): string { + if (this.platform.isWindows) { + return this.platform.is64bit ? 'win-x64' : 'win-x86'; + } + if (this.platform.isMac) { + return 'osx-x64'; + } + if (this.platform.isLinux && this.platform.is64bit) { + return 'linux-x64'; + } + throw new Error('Python Analysis Engine does not support 32-bit Linux.'); + } + + public getEngineDllName(): string { + return 'Microsoft.PythonTools.VsCode.dll'; + } + + public getEngineExecutableName(): string { + return this.platform.isWindows + ? 'Microsoft.PythonTools.VsCode.exe' + : 'Microsoft.PythonTools.VsCode'; + } + + public getExpectedHash(): string { + if (this.platform.isWindows) { + return this.platform.is64bit ? analysis_engine_win_x64_sha512 : analysis_engine_win_x86_sha512; + } + if (this.platform.isMac) { + return analysis_engine_osx_x64_sha512; + } + if (this.platform.isLinux && this.platform.is64bit) { + return analysis_engine_linux_x64_sha512; + } + throw new Error('Unknown platform.'); + } +} diff --git a/src/client/activation/types.ts b/src/client/activation/types.ts new file mode 100644 index 000000000000..f8366a6ce5dd --- /dev/null +++ b/src/client/activation/types.ts @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as vscode from 'vscode'; + +export interface IExtensionActivator { + activate(context: vscode.ExtensionContext): Promise; + deactivate(): Promise; +} diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index d4a16e776b0c..b88b82ce65bf 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -1,376 +1,385 @@ -'use strict'; - -import * as child_process from 'child_process'; -import { EventEmitter } from 'events'; -import * as path from 'path'; -import * as vscode from 'vscode'; -import { ConfigurationTarget, Uri } from 'vscode'; -import { isTestExecution } from './constants'; -import { - IAutoCompeteSettings, - IFormattingSettings, - ILintingSettings, - IPythonSettings, - ISortImportSettings, - ITerminalSettings, - IUnitTestSettings, - IWorkspaceSymbolSettings -} from './types'; -import { SystemVariables } from './variables/systemVariables'; - -// tslint:disable-next-line:no-require-imports no-var-requires -const untildify = require('untildify'); - -export const IS_WINDOWS = /^win/.test(process.platform); - -// tslint:disable-next-line:completed-docs -export class PythonSettings extends EventEmitter implements IPythonSettings { - private static pythonSettings: Map = new Map(); - - public jediPath: string; - public jediMemoryLimit: number; - public envFile: string; - public disablePromptForFeatures: string[]; - public venvPath: string; - public venvFolders: string[]; - public devOptions: string[]; - public linting: ILintingSettings; - public formatting: IFormattingSettings; - public autoComplete: IAutoCompeteSettings; - public unitTest: IUnitTestSettings; - public terminal: ITerminalSettings; - public sortImports: ISortImportSettings; - public workspaceSymbols: IWorkspaceSymbolSettings; - public disableInstallationChecks: boolean; - public globalModuleInstallation: boolean; - - private workspaceRoot: vscode.Uri; - private disposables: vscode.Disposable[] = []; - // tslint:disable-next-line:variable-name - private _pythonPath: string; - constructor(workspaceFolder?: Uri) { - super(); - this.workspaceRoot = workspaceFolder ? workspaceFolder : vscode.Uri.file(__dirname); - this.disposables.push(vscode.workspace.onDidChangeConfiguration(() => { - this.initializeSettings(); - - // If workspace config changes, then we could have a cascading effect of on change events. - // Let's defer the change notification. - setTimeout(() => this.emit('change'), 1); - })); - - this.initializeSettings(); - } - // tslint:disable-next-line:function-name - public static getInstance(resource?: Uri): PythonSettings { - const workspaceFolderUri = PythonSettings.getSettingsUriAndTarget(resource).uri; - const workspaceFolderKey = workspaceFolderUri ? workspaceFolderUri.fsPath : ''; - - if (!PythonSettings.pythonSettings.has(workspaceFolderKey)) { - const settings = new PythonSettings(workspaceFolderUri); - PythonSettings.pythonSettings.set(workspaceFolderKey, settings); - } - // tslint:disable-next-line:no-non-null-assertion - return PythonSettings.pythonSettings.get(workspaceFolderKey)!; - } - - public static getSettingsUriAndTarget(resource?: Uri): { uri: Uri | undefined, target: ConfigurationTarget } { - const workspaceFolder = resource ? vscode.workspace.getWorkspaceFolder(resource) : undefined; - let workspaceFolderUri: Uri | undefined = workspaceFolder ? workspaceFolder.uri : undefined; - - if (!workspaceFolderUri && Array.isArray(vscode.workspace.workspaceFolders) && vscode.workspace.workspaceFolders.length > 0) { - workspaceFolderUri = vscode.workspace.workspaceFolders[0].uri; - } - - const target = workspaceFolderUri ? ConfigurationTarget.WorkspaceFolder : ConfigurationTarget.Global; - return { uri: workspaceFolderUri, target }; - } - - // tslint:disable-next-line:function-name - public static dispose() { - if (!isTestExecution()) { - throw new Error('Dispose can only be called from unit tests'); - } - // tslint:disable-next-line:no-void-expression - PythonSettings.pythonSettings.forEach(item => item.dispose()); - PythonSettings.pythonSettings.clear(); - } - public dispose() { - // tslint:disable-next-line:no-unsafe-any - this.disposables.forEach(disposable => disposable.dispose()); - this.disposables = []; - } - - // tslint:disable-next-line:cyclomatic-complexity max-func-body-length - private initializeSettings() { - const workspaceRoot = this.workspaceRoot.fsPath; - const systemVariables: SystemVariables = new SystemVariables(this.workspaceRoot ? this.workspaceRoot.fsPath : undefined); - const pythonSettings = vscode.workspace.getConfiguration('python', this.workspaceRoot); - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - this.pythonPath = systemVariables.resolveAny(pythonSettings.get('pythonPath'))!; - this.pythonPath = getAbsolutePath(this.pythonPath, workspaceRoot); - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - this.venvPath = systemVariables.resolveAny(pythonSettings.get('venvPath'))!; - this.venvFolders = systemVariables.resolveAny(pythonSettings.get('venvFolders'))!; - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - this.jediPath = systemVariables.resolveAny(pythonSettings.get('jediPath'))!; - if (typeof this.jediPath === 'string' && this.jediPath.length > 0) { - this.jediPath = getAbsolutePath(systemVariables.resolveAny(this.jediPath), workspaceRoot); - } else { - this.jediPath = ''; - } - this.jediMemoryLimit = pythonSettings.get('jediMemoryLimit')!; - - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - this.envFile = systemVariables.resolveAny(pythonSettings.get('envFile'))!; - // tslint:disable-next-line:no-any - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion no-any - this.devOptions = systemVariables.resolveAny(pythonSettings.get('devOptions'))!; - this.devOptions = Array.isArray(this.devOptions) ? this.devOptions : []; - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - const lintingSettings = systemVariables.resolveAny(pythonSettings.get('linting'))!; - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - this.disablePromptForFeatures = pythonSettings.get('disablePromptForFeatures')!; - this.disablePromptForFeatures = Array.isArray(this.disablePromptForFeatures) ? this.disablePromptForFeatures : []; - if (this.linting) { - Object.assign(this.linting, lintingSettings); - } else { - this.linting = lintingSettings; - } - - this.disableInstallationChecks = pythonSettings.get('disableInstallationCheck') === true; - this.globalModuleInstallation = pythonSettings.get('globalModuleInstallation') === true; - - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - const sortImportSettings = systemVariables.resolveAny(pythonSettings.get('sortImports'))!; - if (this.sortImports) { - Object.assign(this.sortImports, sortImportSettings); - } else { - this.sortImports = sortImportSettings; - } - // Support for travis. - this.sortImports = this.sortImports ? this.sortImports : { path: '', args: [] }; - // Support for travis. - this.linting = this.linting ? this.linting : { - enabled: false, - ignorePatterns: [], - flake8Args: [], flake8Enabled: false, flake8Path: 'flake', - lintOnSave: false, maxNumberOfProblems: 100, - mypyArgs: [], mypyEnabled: false, mypyPath: 'mypy', - pep8Args: [], pep8Enabled: false, pep8Path: 'pep8', - pylamaArgs: [], pylamaEnabled: false, pylamaPath: 'pylama', - prospectorArgs: [], prospectorEnabled: false, prospectorPath: 'prospector', - pydocstyleArgs: [], pydocstyleEnabled: false, pydocstylePath: 'pydocstyle', - pylintArgs: [], pylintEnabled: false, pylintPath: 'pylint', - pylintCategorySeverity: { - convention: vscode.DiagnosticSeverity.Hint, - error: vscode.DiagnosticSeverity.Error, - fatal: vscode.DiagnosticSeverity.Error, - refactor: vscode.DiagnosticSeverity.Hint, - warning: vscode.DiagnosticSeverity.Warning - }, - pep8CategorySeverity: { - E: vscode.DiagnosticSeverity.Error, - W: vscode.DiagnosticSeverity.Warning - }, - flake8CategorySeverity: { - E: vscode.DiagnosticSeverity.Error, - W: vscode.DiagnosticSeverity.Warning, - // Per http://flake8.pycqa.org/en/latest/glossary.html#term-error-code - // 'F' does not mean 'fatal as in PyLint but rather 'pyflakes' such as - // unused imports, variables, etc. - F: vscode.DiagnosticSeverity.Warning - }, - mypyCategorySeverity: { - error: vscode.DiagnosticSeverity.Error, - note: vscode.DiagnosticSeverity.Hint - }, - pylintUseMinimalCheckers: false - }; - this.linting.pylintPath = getAbsolutePath(systemVariables.resolveAny(this.linting.pylintPath), workspaceRoot); - this.linting.flake8Path = getAbsolutePath(systemVariables.resolveAny(this.linting.flake8Path), workspaceRoot); - this.linting.pep8Path = getAbsolutePath(systemVariables.resolveAny(this.linting.pep8Path), workspaceRoot); - this.linting.pylamaPath = getAbsolutePath(systemVariables.resolveAny(this.linting.pylamaPath), workspaceRoot); - this.linting.prospectorPath = getAbsolutePath(systemVariables.resolveAny(this.linting.prospectorPath), workspaceRoot); - this.linting.pydocstylePath = getAbsolutePath(systemVariables.resolveAny(this.linting.pydocstylePath), workspaceRoot); - this.linting.mypyPath = getAbsolutePath(systemVariables.resolveAny(this.linting.mypyPath), workspaceRoot); - - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - const formattingSettings = systemVariables.resolveAny(pythonSettings.get('formatting'))!; - if (this.formatting) { - Object.assign(this.formatting, formattingSettings); - } else { - this.formatting = formattingSettings; - } - // Support for travis. - this.formatting = this.formatting ? this.formatting : { - autopep8Args: [], autopep8Path: 'autopep8', - provider: 'autopep8', - yapfArgs: [], yapfPath: 'yapf' - }; - this.formatting.autopep8Path = getAbsolutePath(systemVariables.resolveAny(this.formatting.autopep8Path), workspaceRoot); - this.formatting.yapfPath = getAbsolutePath(systemVariables.resolveAny(this.formatting.yapfPath), workspaceRoot); - - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - const autoCompleteSettings = systemVariables.resolveAny(pythonSettings.get('autoComplete'))!; - if (this.autoComplete) { - Object.assign(this.autoComplete, autoCompleteSettings); - } else { - this.autoComplete = autoCompleteSettings; - } - // Support for travis. - this.autoComplete = this.autoComplete ? this.autoComplete : { - extraPaths: [], - addBrackets: false, - preloadModules: [] - }; - - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - const workspaceSymbolsSettings = systemVariables.resolveAny(pythonSettings.get('workspaceSymbols'))!; - if (this.workspaceSymbols) { - Object.assign(this.workspaceSymbols, workspaceSymbolsSettings); - } else { - this.workspaceSymbols = workspaceSymbolsSettings; - } - // Support for travis. - this.workspaceSymbols = this.workspaceSymbols ? this.workspaceSymbols : { - ctagsPath: 'ctags', - enabled: true, - exclusionPatterns: [], - rebuildOnFileSave: true, - rebuildOnStart: true, - tagFilePath: path.join(workspaceRoot, 'tags') - }; - this.workspaceSymbols.tagFilePath = getAbsolutePath(systemVariables.resolveAny(this.workspaceSymbols.tagFilePath), workspaceRoot); - - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - const unitTestSettings = systemVariables.resolveAny(pythonSettings.get('unitTest'))!; - if (this.unitTest) { - Object.assign(this.unitTest, unitTestSettings); - } else { - this.unitTest = unitTestSettings; - if (isTestExecution() && !this.unitTest) { - // tslint:disable-next-line:prefer-type-cast - this.unitTest = { - nosetestArgs: [], pyTestArgs: [], unittestArgs: [], - promptToConfigure: true, debugPort: 3000, - nosetestsEnabled: false, pyTestEnabled: false, unittestEnabled: false, - nosetestPath: 'nosetests', pyTestPath: 'pytest' - } as IUnitTestSettings; - } - } - - // Support for travis. - this.unitTest = this.unitTest ? this.unitTest : { - promptToConfigure: true, - debugPort: 3000, - nosetestArgs: [], nosetestPath: 'nosetest', nosetestsEnabled: false, - pyTestArgs: [], pyTestEnabled: false, pyTestPath: 'pytest', - unittestArgs: [], unittestEnabled: false - }; - this.unitTest.pyTestPath = getAbsolutePath(systemVariables.resolveAny(this.unitTest.pyTestPath), workspaceRoot); - this.unitTest.nosetestPath = getAbsolutePath(systemVariables.resolveAny(this.unitTest.nosetestPath), workspaceRoot); - if (this.unitTest.cwd) { - this.unitTest.cwd = getAbsolutePath(systemVariables.resolveAny(this.unitTest.cwd), workspaceRoot); - } - - // Resolve any variables found in the test arguments. - this.unitTest.nosetestArgs = this.unitTest.nosetestArgs.map(arg => systemVariables.resolveAny(arg)); - this.unitTest.pyTestArgs = this.unitTest.pyTestArgs.map(arg => systemVariables.resolveAny(arg)); - this.unitTest.unittestArgs = this.unitTest.unittestArgs.map(arg => systemVariables.resolveAny(arg)); - - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - const terminalSettings = systemVariables.resolveAny(pythonSettings.get('terminal'))!; - if (this.terminal) { - Object.assign(this.terminal, terminalSettings); - } else { - this.terminal = terminalSettings; - if (isTestExecution() && !this.terminal) { - // tslint:disable-next-line:prefer-type-cast - this.terminal = {} as ITerminalSettings; - } - } - // Support for travis. - this.terminal = this.terminal ? this.terminal : { - executeInFileDir: true, - launchArgs: [], - activateEnvironment: true - }; - } - - public get pythonPath(): string { - return this._pythonPath; - } - public set pythonPath(value: string) { - if (this._pythonPath === value) { - return; - } - // Add support for specifying just the directory where the python executable will be located. - // E.g. virtual directory name. - try { - this._pythonPath = getPythonExecutable(value); - } catch (ex) { - this._pythonPath = value; - } - } -} - -function getAbsolutePath(pathToCheck: string, rootDir: string): string { - // tslint:disable-next-line:prefer-type-cast no-unsafe-any - pathToCheck = untildify(pathToCheck) as string; - if (isTestExecution() && !pathToCheck) { return rootDir; } - if (pathToCheck.indexOf(path.sep) === -1) { - return pathToCheck; - } - return path.isAbsolute(pathToCheck) ? pathToCheck : path.resolve(rootDir, pathToCheck); -} - -function getPythonExecutable(pythonPath: string): string { - // tslint:disable-next-line:prefer-type-cast no-unsafe-any - pythonPath = untildify(pythonPath) as string; - - // If only 'python'. - if (pythonPath === 'python' || - pythonPath.indexOf(path.sep) === -1 || - path.basename(pythonPath) === path.dirname(pythonPath)) { - return pythonPath; - } - - if (isValidPythonPath(pythonPath)) { - return pythonPath; - } - // Keep python right on top, for backwards compatibility. - // tslint:disable-next-line:variable-name - const KnownPythonExecutables = ['python', 'python4', 'python3.6', 'python3.5', 'python3', 'python2.7', 'python2']; - - for (let executableName of KnownPythonExecutables) { - // Suffix with 'python' for linux and 'osx', and 'python.exe' for 'windows'. - if (IS_WINDOWS) { - executableName = `${executableName}.exe`; - if (isValidPythonPath(path.join(pythonPath, executableName))) { - return path.join(pythonPath, executableName); - } - if (isValidPythonPath(path.join(pythonPath, 'scripts', executableName))) { - return path.join(pythonPath, 'scripts', executableName); - } - } else { - if (isValidPythonPath(path.join(pythonPath, executableName))) { - return path.join(pythonPath, executableName); - } - if (isValidPythonPath(path.join(pythonPath, 'bin', executableName))) { - return path.join(pythonPath, 'bin', executableName); - } - } - } - - return pythonPath; -} - -function isValidPythonPath(pythonPath: string): boolean { - try { - const output = child_process.execFileSync(pythonPath, ['-c', 'print(1234)'], { encoding: 'utf8' }); - return output.startsWith('1234'); - } catch (ex) { - return false; - } -} +'use strict'; + +import * as child_process from 'child_process'; +import { EventEmitter } from 'events'; +import * as path from 'path'; +import { ConfigurationTarget, DiagnosticSeverity, Disposable, Uri, workspace } from 'vscode'; +import { isTestExecution } from './constants'; +import { + IAutoCompeteSettings, + IFormattingSettings, + ILintingSettings, + IPythonSettings, + ISortImportSettings, + ITerminalSettings, + IUnitTestSettings, + IWorkspaceSymbolSettings +} from './types'; +import { SystemVariables } from './variables/systemVariables'; + +// tslint:disable-next-line:no-require-imports no-var-requires +const untildify = require('untildify'); + +export const IS_WINDOWS = /^win/.test(process.platform); + +// tslint:disable-next-line:completed-docs +export class PythonSettings extends EventEmitter implements IPythonSettings { + private static pythonSettings: Map = new Map(); + public jediEnabled = true; + public jediPath = ''; + public jediMemoryLimit = 1024; + public envFile = ''; + public disablePromptForFeatures: string[] = []; + public venvPath = ''; + public venvFolders: string[] = []; + public devOptions: string[] = []; + public linting?: ILintingSettings; + public formatting?: IFormattingSettings; + public autoComplete?: IAutoCompeteSettings; + public unitTest?: IUnitTestSettings; + public terminal?: ITerminalSettings; + public sortImports?: ISortImportSettings; + public workspaceSymbols?: IWorkspaceSymbolSettings; + public disableInstallationChecks = false; + public globalModuleInstallation = false; + + private workspaceRoot: Uri; + private disposables: Disposable[] = []; + // tslint:disable-next-line:variable-name + private _pythonPath = ''; + + constructor(workspaceFolder?: Uri) { + super(); + this.workspaceRoot = workspaceFolder ? workspaceFolder : Uri.file(__dirname); + this.disposables.push(workspace.onDidChangeConfiguration(() => { + this.initializeSettings(); + + // If workspace config changes, then we could have a cascading effect of on change events. + // Let's defer the change notification. + setTimeout(() => this.emit('change'), 1); + })); + + this.initializeSettings(); + } + // tslint:disable-next-line:function-name + public static getInstance(resource?: Uri): PythonSettings { + const workspaceFolderUri = PythonSettings.getSettingsUriAndTarget(resource).uri; + const workspaceFolderKey = workspaceFolderUri ? workspaceFolderUri.fsPath : ''; + + if (!PythonSettings.pythonSettings.has(workspaceFolderKey)) { + const settings = new PythonSettings(workspaceFolderUri); + PythonSettings.pythonSettings.set(workspaceFolderKey, settings); + } + // tslint:disable-next-line:no-non-null-assertion + return PythonSettings.pythonSettings.get(workspaceFolderKey)!; + } + + // tslint:disable-next-line:type-literal-delimiter + public static getSettingsUriAndTarget(resource?: Uri): { uri: Uri | undefined, target: ConfigurationTarget } { + const workspaceFolder = resource ? workspace.getWorkspaceFolder(resource) : undefined; + let workspaceFolderUri: Uri | undefined = workspaceFolder ? workspaceFolder.uri : undefined; + + if (!workspaceFolderUri && Array.isArray(workspace.workspaceFolders) && workspace.workspaceFolders.length > 0) { + workspaceFolderUri = workspace.workspaceFolders[0].uri; + } + + const target = workspaceFolderUri ? ConfigurationTarget.WorkspaceFolder : ConfigurationTarget.Global; + return { uri: workspaceFolderUri, target }; + } + + // tslint:disable-next-line:function-name + public static dispose() { + if (!isTestExecution()) { + throw new Error('Dispose can only be called from unit tests'); + } + // tslint:disable-next-line:no-void-expression + PythonSettings.pythonSettings.forEach(item => item.dispose()); + PythonSettings.pythonSettings.clear(); + } + public dispose() { + // tslint:disable-next-line:no-unsafe-any + this.disposables.forEach(disposable => disposable.dispose()); + this.disposables = []; + } + + // tslint:disable-next-line:cyclomatic-complexity max-func-body-length + private initializeSettings() { + const workspaceRoot = this.workspaceRoot.fsPath; + const systemVariables: SystemVariables = new SystemVariables(this.workspaceRoot ? this.workspaceRoot.fsPath : undefined); + const pythonSettings = workspace.getConfiguration('python', this.workspaceRoot); + + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + this.pythonPath = systemVariables.resolveAny(pythonSettings.get('pythonPath'))!; + this.pythonPath = getAbsolutePath(this.pythonPath, workspaceRoot); + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + this.venvPath = systemVariables.resolveAny(pythonSettings.get('venvPath'))!; + this.venvFolders = systemVariables.resolveAny(pythonSettings.get('venvFolders'))!; + + this.jediEnabled = systemVariables.resolveAny(pythonSettings.get('jediEnabled'))!; + if (this.jediEnabled) { + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + this.jediPath = systemVariables.resolveAny(pythonSettings.get('jediPath'))!; + if (typeof this.jediPath === 'string' && this.jediPath.length > 0) { + this.jediPath = getAbsolutePath(systemVariables.resolveAny(this.jediPath), workspaceRoot); + } else { + this.jediPath = ''; + } + this.jediMemoryLimit = pythonSettings.get('jediMemoryLimit')!; + } + + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + this.envFile = systemVariables.resolveAny(pythonSettings.get('envFile'))!; + // tslint:disable-next-line:no-any + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion no-any + this.devOptions = systemVariables.resolveAny(pythonSettings.get('devOptions'))!; + this.devOptions = Array.isArray(this.devOptions) ? this.devOptions : []; + + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const lintingSettings = systemVariables.resolveAny(pythonSettings.get('linting'))!; + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + this.disablePromptForFeatures = pythonSettings.get('disablePromptForFeatures')!; + this.disablePromptForFeatures = Array.isArray(this.disablePromptForFeatures) ? this.disablePromptForFeatures : []; + if (this.linting) { + Object.assign(this.linting, lintingSettings); + } else { + this.linting = lintingSettings; + } + + this.disableInstallationChecks = pythonSettings.get('disableInstallationCheck') === true; + this.globalModuleInstallation = pythonSettings.get('globalModuleInstallation') === true; + + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const sortImportSettings = systemVariables.resolveAny(pythonSettings.get('sortImports'))!; + if (this.sortImports) { + Object.assign(this.sortImports, sortImportSettings); + } else { + this.sortImports = sortImportSettings; + } + // Support for travis. + this.sortImports = this.sortImports ? this.sortImports : { path: '', args: [] }; + // Support for travis. + this.linting = this.linting ? this.linting : { + enabled: false, + ignorePatterns: [], + flake8Args: [], flake8Enabled: false, flake8Path: 'flake', + lintOnSave: false, maxNumberOfProblems: 100, + mypyArgs: [], mypyEnabled: false, mypyPath: 'mypy', + pep8Args: [], pep8Enabled: false, pep8Path: 'pep8', + pylamaArgs: [], pylamaEnabled: false, pylamaPath: 'pylama', + prospectorArgs: [], prospectorEnabled: false, prospectorPath: 'prospector', + pydocstyleArgs: [], pydocstyleEnabled: false, pydocstylePath: 'pydocstyle', + pylintArgs: [], pylintEnabled: false, pylintPath: 'pylint', + pylintCategorySeverity: { + convention: DiagnosticSeverity.Hint, + error: DiagnosticSeverity.Error, + fatal: DiagnosticSeverity.Error, + refactor: DiagnosticSeverity.Hint, + warning: DiagnosticSeverity.Warning + }, + pep8CategorySeverity: { + E: DiagnosticSeverity.Error, + W: DiagnosticSeverity.Warning + }, + flake8CategorySeverity: { + E: DiagnosticSeverity.Error, + W: DiagnosticSeverity.Warning, + // Per http://flake8.pycqa.org/en/latest/glossary.html#term-error-code + // 'F' does not mean 'fatal as in PyLint but rather 'pyflakes' such as + // unused imports, variables, etc. + F: DiagnosticSeverity.Warning + }, + mypyCategorySeverity: { + error: DiagnosticSeverity.Error, + note: DiagnosticSeverity.Hint + }, + pylintUseMinimalCheckers: false + }; + this.linting.pylintPath = getAbsolutePath(systemVariables.resolveAny(this.linting.pylintPath), workspaceRoot); + this.linting.flake8Path = getAbsolutePath(systemVariables.resolveAny(this.linting.flake8Path), workspaceRoot); + this.linting.pep8Path = getAbsolutePath(systemVariables.resolveAny(this.linting.pep8Path), workspaceRoot); + this.linting.pylamaPath = getAbsolutePath(systemVariables.resolveAny(this.linting.pylamaPath), workspaceRoot); + this.linting.prospectorPath = getAbsolutePath(systemVariables.resolveAny(this.linting.prospectorPath), workspaceRoot); + this.linting.pydocstylePath = getAbsolutePath(systemVariables.resolveAny(this.linting.pydocstylePath), workspaceRoot); + this.linting.mypyPath = getAbsolutePath(systemVariables.resolveAny(this.linting.mypyPath), workspaceRoot); + + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const formattingSettings = systemVariables.resolveAny(pythonSettings.get('formatting'))!; + if (this.formatting) { + Object.assign(this.formatting, formattingSettings); + } else { + this.formatting = formattingSettings; + } + // Support for travis. + this.formatting = this.formatting ? this.formatting : { + autopep8Args: [], autopep8Path: 'autopep8', + provider: 'autopep8', + yapfArgs: [], yapfPath: 'yapf' + }; + this.formatting.autopep8Path = getAbsolutePath(systemVariables.resolveAny(this.formatting.autopep8Path), workspaceRoot); + this.formatting.yapfPath = getAbsolutePath(systemVariables.resolveAny(this.formatting.yapfPath), workspaceRoot); + + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const autoCompleteSettings = systemVariables.resolveAny(pythonSettings.get('autoComplete'))!; + if (this.autoComplete) { + Object.assign(this.autoComplete, autoCompleteSettings); + } else { + this.autoComplete = autoCompleteSettings; + } + // Support for travis. + this.autoComplete = this.autoComplete ? this.autoComplete : { + extraPaths: [], + addBrackets: false, + preloadModules: [] + }; + + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const workspaceSymbolsSettings = systemVariables.resolveAny(pythonSettings.get('workspaceSymbols'))!; + if (this.workspaceSymbols) { + Object.assign(this.workspaceSymbols, workspaceSymbolsSettings); + } else { + this.workspaceSymbols = workspaceSymbolsSettings; + } + // Support for travis. + this.workspaceSymbols = this.workspaceSymbols ? this.workspaceSymbols : { + ctagsPath: 'ctags', + enabled: true, + exclusionPatterns: [], + rebuildOnFileSave: true, + rebuildOnStart: true, + tagFilePath: path.join(workspaceRoot, 'tags') + }; + this.workspaceSymbols.tagFilePath = getAbsolutePath(systemVariables.resolveAny(this.workspaceSymbols.tagFilePath), workspaceRoot); + + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const unitTestSettings = systemVariables.resolveAny(pythonSettings.get('unitTest'))!; + if (this.unitTest) { + Object.assign(this.unitTest, unitTestSettings); + } else { + this.unitTest = unitTestSettings; + if (isTestExecution() && !this.unitTest) { + // tslint:disable-next-line:prefer-type-cast + // tslint:disable-next-line:no-object-literal-type-assertion + this.unitTest = { + nosetestArgs: [], pyTestArgs: [], unittestArgs: [], + promptToConfigure: true, debugPort: 3000, + nosetestsEnabled: false, pyTestEnabled: false, unittestEnabled: false, + nosetestPath: 'nosetests', pyTestPath: 'pytest' + } as IUnitTestSettings; + } + } + + // Support for travis. + this.unitTest = this.unitTest ? this.unitTest : { + promptToConfigure: true, + debugPort: 3000, + nosetestArgs: [], nosetestPath: 'nosetest', nosetestsEnabled: false, + pyTestArgs: [], pyTestEnabled: false, pyTestPath: 'pytest', + unittestArgs: [], unittestEnabled: false + }; + this.unitTest.pyTestPath = getAbsolutePath(systemVariables.resolveAny(this.unitTest.pyTestPath), workspaceRoot); + this.unitTest.nosetestPath = getAbsolutePath(systemVariables.resolveAny(this.unitTest.nosetestPath), workspaceRoot); + if (this.unitTest.cwd) { + this.unitTest.cwd = getAbsolutePath(systemVariables.resolveAny(this.unitTest.cwd), workspaceRoot); + } + + // Resolve any variables found in the test arguments. + this.unitTest.nosetestArgs = this.unitTest.nosetestArgs.map(arg => systemVariables.resolveAny(arg)); + this.unitTest.pyTestArgs = this.unitTest.pyTestArgs.map(arg => systemVariables.resolveAny(arg)); + this.unitTest.unittestArgs = this.unitTest.unittestArgs.map(arg => systemVariables.resolveAny(arg)); + + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const terminalSettings = systemVariables.resolveAny(pythonSettings.get('terminal'))!; + if (this.terminal) { + Object.assign(this.terminal, terminalSettings); + } else { + this.terminal = terminalSettings; + if (isTestExecution() && !this.terminal) { + // tslint:disable-next-line:prefer-type-cast + // tslint:disable-next-line:no-object-literal-type-assertion + this.terminal = {} as ITerminalSettings; + } + } + // Support for travis. + this.terminal = this.terminal ? this.terminal : { + executeInFileDir: true, + launchArgs: [], + activateEnvironment: true + }; + } + + public get pythonPath(): string { + return this._pythonPath; + } + public set pythonPath(value: string) { + if (this._pythonPath === value) { + return; + } + // Add support for specifying just the directory where the python executable will be located. + // E.g. virtual directory name. + try { + this._pythonPath = getPythonExecutable(value); + } catch (ex) { + this._pythonPath = value; + } + } +} + +function getAbsolutePath(pathToCheck: string, rootDir: string): string { + // tslint:disable-next-line:prefer-type-cast no-unsafe-any + pathToCheck = untildify(pathToCheck) as string; + if (isTestExecution() && !pathToCheck) { return rootDir; } + if (pathToCheck.indexOf(path.sep) === -1) { + return pathToCheck; + } + return path.isAbsolute(pathToCheck) ? pathToCheck : path.resolve(rootDir, pathToCheck); +} + +function getPythonExecutable(pythonPath: string): string { + // tslint:disable-next-line:prefer-type-cast no-unsafe-any + pythonPath = untildify(pythonPath) as string; + + // If only 'python'. + if (pythonPath === 'python' || + pythonPath.indexOf(path.sep) === -1 || + path.basename(pythonPath) === path.dirname(pythonPath)) { + return pythonPath; + } + + if (isValidPythonPath(pythonPath)) { + return pythonPath; + } + // Keep python right on top, for backwards compatibility. + // tslint:disable-next-line:variable-name + const KnownPythonExecutables = ['python', 'python4', 'python3.6', 'python3.5', 'python3', 'python2.7', 'python2']; + + for (let executableName of KnownPythonExecutables) { + // Suffix with 'python' for linux and 'osx', and 'python.exe' for 'windows'. + if (IS_WINDOWS) { + executableName = `${executableName}.exe`; + if (isValidPythonPath(path.join(pythonPath, executableName))) { + return path.join(pythonPath, executableName); + } + if (isValidPythonPath(path.join(pythonPath, 'scripts', executableName))) { + return path.join(pythonPath, 'scripts', executableName); + } + } else { + if (isValidPythonPath(path.join(pythonPath, executableName))) { + return path.join(pythonPath, executableName); + } + if (isValidPythonPath(path.join(pythonPath, 'bin', executableName))) { + return path.join(pythonPath, 'bin', executableName); + } + } + } + + return pythonPath; +} + +function isValidPythonPath(pythonPath: string): boolean { + try { + const output = child_process.execFileSync(pythonPath, ['-c', 'print(1234)'], { encoding: 'utf8' }); + return output.startsWith('1234'); + } catch (ex) { + return false; + } +} diff --git a/src/client/common/configuration/service.ts b/src/client/common/configuration/service.ts index effbba72efed..e5d862930ca3 100644 --- a/src/client/common/configuration/service.ts +++ b/src/client/common/configuration/service.ts @@ -1,16 +1,23 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { injectable } from 'inversify'; +import { inject, injectable } from 'inversify'; import { ConfigurationTarget, Uri, workspace, WorkspaceConfiguration } from 'vscode'; +import { IServiceContainer } from '../../ioc/types'; +import { IApplicationShell } from '../application/types'; import { PythonSettings } from '../configSettings'; +import { IProcessService } from '../process/types'; import { IConfigurationService, IPythonSettings } from '../types'; @injectable() export class ConfigurationService implements IConfigurationService { + constructor(@inject(IServiceContainer) private services: IServiceContainer) { + } + public getSettings(resource?: Uri): IPythonSettings { return PythonSettings.getInstance(resource); } + public async updateSettingAsync(setting: string, value?: {}, resource?: Uri, configTarget?: ConfigurationTarget): Promise { const settingsInfo = PythonSettings.getSettingsUriAndTarget(resource); @@ -32,6 +39,10 @@ export class ConfigurationService implements IConfigurationService { return process.env.VSC_PYTHON_CI_TEST === '1'; } + public async checkDependencies(): Promise { + return this.checkDotNet(); + } + private async verifySetting(pythonConfig: WorkspaceConfiguration, target: ConfigurationTarget, settingName: string, value?: {}): Promise { if (this.isTestExecution()) { let retries = 0; @@ -55,4 +66,22 @@ export class ConfigurationService implements IConfigurationService { } while (retries < 20); } } + + private async checkDotNet(): Promise { + if (!await this.isDotNetInstalled()) { + const appShell = this.services.get(IApplicationShell); + if (await appShell.showErrorMessage('Python Tools require .NET Core Runtime. Would you like to install it now?', 'Yes', 'No') === 'Yes') { + appShell.openUrl('https://www.microsoft.com/net/download/core#/runtime'); + appShell.showWarningMessage('Please restart VS Code after .NET Runtime installation is complete.'); + } + return false; + } + return true; + } + + private async isDotNetInstalled(): Promise { + const ps = this.services.get(IProcessService); + const result = await ps.exec('dotnet', ['--version']); + return result.stdout.trim().startsWith('2.'); + } } diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 4083b0965fd9..266d1a812cb0 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -98,16 +98,17 @@ export interface IPythonSettings { readonly pythonPath: string; readonly venvPath: string; readonly venvFolders: string[]; + readonly jediEnabled: boolean; readonly jediPath: string; readonly jediMemoryLimit: number; readonly devOptions: string[]; - readonly linting: ILintingSettings; - readonly formatting: IFormattingSettings; - readonly unitTest: IUnitTestSettings; - readonly autoComplete: IAutoCompeteSettings; - readonly terminal: ITerminalSettings; - readonly sortImports: ISortImportSettings; - readonly workspaceSymbols: IWorkspaceSymbolSettings; + readonly linting?: ILintingSettings; + readonly formatting?: IFormattingSettings; + readonly unitTest?: IUnitTestSettings; + readonly autoComplete?: IAutoCompeteSettings; + readonly terminal?: ITerminalSettings; + readonly sortImports?: ISortImportSettings; + readonly workspaceSymbols?: IWorkspaceSymbolSettings; readonly envFile: string; readonly disablePromptForFeatures: string[]; readonly disableInstallationChecks: boolean; @@ -218,6 +219,7 @@ export interface IConfigurationService { getSettings(resource?: Uri): IPythonSettings; isTestExecution(): boolean; updateSettingAsync(setting: string, value?: {}, resource?: Uri, configTarget?: ConfigurationTarget): Promise; + checkDependencies(): Promise; } export const ISocketServer = Symbol('ISocketServer'); diff --git a/src/client/extension.ts b/src/client/extension.ts index e680bfb21553..e86959d62255 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -1,205 +1,191 @@ -'use strict'; -// This line should always be right on top. -// tslint:disable-next-line:no-any -if ((Reflect as any).metadata === undefined) { - // tslint:disable-next-line:no-require-imports no-var-requires - require('reflect-metadata'); -} -import { Container } from 'inversify'; -import { - debug, Disposable, DocumentFilter, ExtensionContext, - extensions, IndentAction, languages, Memento, - OutputChannel, window -} from 'vscode'; -import { PythonSettings } from './common/configSettings'; -import { STANDARD_OUTPUT_CHANNEL } from './common/constants'; -import { FeatureDeprecationManager } from './common/featureDeprecationManager'; -import { createDeferred } from './common/helpers'; -import { PythonInstaller } from './common/installer/pythonInstallation'; -import { registerTypes as installerRegisterTypes } from './common/installer/serviceRegistry'; -import { registerTypes as platformRegisterTypes } from './common/platform/serviceRegistry'; -import { registerTypes as processRegisterTypes } from './common/process/serviceRegistry'; -import { registerTypes as commonRegisterTypes } from './common/serviceRegistry'; -import { StopWatch } from './common/stopWatch'; -import { GLOBAL_MEMENTO, IDisposableRegistry, ILogger, IMemento, IOutputChannel, IPersistentStateFactory, WORKSPACE_MEMENTO } from './common/types'; -import { registerTypes as variableRegisterTypes } from './common/variables/serviceRegistry'; -import { BaseConfigurationProvider } from './debugger/configProviders/baseProvider'; -import { registerTypes as debugConfigurationRegisterTypes } from './debugger/configProviders/serviceRegistry'; -import { IDebugConfigurationProvider } from './debugger/types'; -import { registerTypes as formattersRegisterTypes } from './formatters/serviceRegistry'; -import { IInterpreterSelector } from './interpreter/configuration/types'; -import { ICondaService, IInterpreterService, IShebangCodeLensProvider } from './interpreter/contracts'; -import { registerTypes as interpretersRegisterTypes } from './interpreter/serviceRegistry'; -import { ServiceContainer } from './ioc/container'; -import { ServiceManager } from './ioc/serviceManager'; -import { IServiceContainer } from './ioc/types'; -import { JediFactory } from './languageServices/jediProxyFactory'; -import { LinterCommands } from './linters/linterCommands'; -import { registerTypes as lintersRegisterTypes } from './linters/serviceRegistry'; -import { ILintingEngine } from './linters/types'; -import { PythonCompletionItemProvider } from './providers/completionProvider'; -import { PythonDefinitionProvider } from './providers/definitionProvider'; -import { PythonFormattingEditProvider } from './providers/formatProvider'; -import { PythonHoverProvider } from './providers/hoverProvider'; -import { LinterProvider } from './providers/linterProvider'; -import { activateGoToObjectDefinitionProvider } from './providers/objectDefinitionProvider'; -import { PythonReferenceProvider } from './providers/referenceProvider'; -import { PythonRenameProvider } from './providers/renameProvider'; -import { ReplProvider } from './providers/replProvider'; -import { PythonSignatureProvider } from './providers/signatureProvider'; -import { activateSimplePythonRefactorProvider } from './providers/simpleRefactorProvider'; -import { PythonSymbolProvider } from './providers/symbolProvider'; -import { TerminalProvider } from './providers/terminalProvider'; -import { activateUpdateSparkLibraryProvider } from './providers/updateSparkLibraryProvider'; -import * as sortImports from './sortImports'; -import { sendTelemetryEvent } from './telemetry'; -import { EDITOR_LOAD } from './telemetry/constants'; -import { registerTypes as commonRegisterTerminalTypes } from './terminals/serviceRegistry'; -import { ICodeExecutionManager } from './terminals/types'; -import { BlockFormatProviders } from './typeFormatters/blockFormatProvider'; -import { OnEnterFormatter } from './typeFormatters/onEnterFormatter'; -import { TEST_OUTPUT_CHANNEL } from './unittests/common/constants'; -import * as tests from './unittests/main'; -import { registerTypes as unitTestsRegisterTypes } from './unittests/serviceRegistry'; -import { WorkspaceSymbols } from './workspaceSymbols/main'; - -const PYTHON: DocumentFilter = { language: 'python' }; -const activationDeferred = createDeferred(); -export const activated = activationDeferred.promise; - -// tslint:disable-next-line:max-func-body-length -export async function activate(context: ExtensionContext) { - const cont = new Container(); - const serviceManager = new ServiceManager(cont); - const serviceContainer = new ServiceContainer(cont); - serviceManager.addSingletonInstance(IServiceContainer, serviceContainer); - serviceManager.addSingletonInstance(IDisposableRegistry, context.subscriptions); - serviceManager.addSingletonInstance(IMemento, context.globalState, GLOBAL_MEMENTO); - serviceManager.addSingletonInstance(IMemento, context.workspaceState, WORKSPACE_MEMENTO); - - const standardOutputChannel = window.createOutputChannel('Python'); - const unitTestOutChannel = window.createOutputChannel('Python Test Log'); - serviceManager.addSingletonInstance(IOutputChannel, standardOutputChannel, STANDARD_OUTPUT_CHANNEL); - serviceManager.addSingletonInstance(IOutputChannel, unitTestOutChannel, TEST_OUTPUT_CHANNEL); - - commonRegisterTypes(serviceManager); - processRegisterTypes(serviceManager); - variableRegisterTypes(serviceManager); - unitTestsRegisterTypes(serviceManager); - lintersRegisterTypes(serviceManager); - interpretersRegisterTypes(serviceManager); - formattersRegisterTypes(serviceManager); - platformRegisterTypes(serviceManager); - installerRegisterTypes(serviceManager); - commonRegisterTerminalTypes(serviceManager); - debugConfigurationRegisterTypes(serviceManager); - - serviceManager.get(ICodeExecutionManager).registerCommands(); - - const persistentStateFactory = serviceManager.get(IPersistentStateFactory); - const pythonSettings = PythonSettings.getInstance(); - // tslint:disable-next-line:no-floating-promises - sendStartupTelemetry(activated, serviceContainer); - - sortImports.activate(context, standardOutputChannel, serviceContainer); - const interpreterManager = serviceContainer.get(IInterpreterService); - - // This must be completed before we can continue. - interpreterManager.initialize(); - await interpreterManager.autoSetInterpreter(); - - const pythonInstaller = new PythonInstaller(serviceContainer); - pythonInstaller.checkPythonInstallation(PythonSettings.getInstance()) - .catch(ex => console.error('Python Extension: pythonInstaller.checkPythonInstallation', ex)); - - interpreterManager.refresh() - .catch(ex => console.error('Python Extension: interpreterManager.refresh', ex)); - - context.subscriptions.push(serviceContainer.get(IInterpreterSelector)); - context.subscriptions.push(activateUpdateSparkLibraryProvider()); - activateSimplePythonRefactorProvider(context, standardOutputChannel, serviceContainer); - const jediFactory = new JediFactory(context.asAbsolutePath('.'), serviceContainer); - context.subscriptions.push(...activateGoToObjectDefinitionProvider(jediFactory)); - - context.subscriptions.push(new ReplProvider(serviceContainer)); - context.subscriptions.push(new TerminalProvider(serviceContainer)); - context.subscriptions.push(new LinterCommands(serviceContainer)); - - // Enable indentAction - // tslint:disable-next-line:no-non-null-assertion - languages.setLanguageConfiguration(PYTHON.language!, { - onEnterRules: [ - { - beforeText: /^\s*(?:def|class|for|if|elif|else|while|try|with|finally|except|async)\b.*/, - action: { indentAction: IndentAction.Indent } - }, - { - beforeText: /^\s*#.*/, - afterText: /.+$/, - action: { indentAction: IndentAction.None, appendText: '# ' } - }, - { - beforeText: /^\s+(continue|break|return)\b.*/, - afterText: /\s+$/, - action: { indentAction: IndentAction.Outdent } - } - ] - }); - - context.subscriptions.push(jediFactory); - context.subscriptions.push(languages.registerRenameProvider(PYTHON, new PythonRenameProvider(serviceContainer))); - const definitionProvider = new PythonDefinitionProvider(jediFactory); - context.subscriptions.push(languages.registerDefinitionProvider(PYTHON, definitionProvider)); - context.subscriptions.push(languages.registerHoverProvider(PYTHON, new PythonHoverProvider(jediFactory))); - context.subscriptions.push(languages.registerReferenceProvider(PYTHON, new PythonReferenceProvider(jediFactory))); - context.subscriptions.push(languages.registerCompletionItemProvider(PYTHON, new PythonCompletionItemProvider(jediFactory, serviceContainer), '.')); - context.subscriptions.push(languages.registerCodeLensProvider(PYTHON, serviceContainer.get(IShebangCodeLensProvider))); - - const symbolProvider = new PythonSymbolProvider(jediFactory); - context.subscriptions.push(languages.registerDocumentSymbolProvider(PYTHON, symbolProvider)); - if (pythonSettings.devOptions.indexOf('DISABLE_SIGNATURE') === -1) { - context.subscriptions.push(languages.registerSignatureHelpProvider(PYTHON, new PythonSignatureProvider(jediFactory), '(', ',')); - } - if (pythonSettings.formatting.provider !== 'none') { - const formatProvider = new PythonFormattingEditProvider(context, serviceContainer); - context.subscriptions.push(languages.registerDocumentFormattingEditProvider(PYTHON, formatProvider)); - context.subscriptions.push(languages.registerDocumentRangeFormattingEditProvider(PYTHON, formatProvider)); - } - - const linterProvider = new LinterProvider(context, serviceContainer); - context.subscriptions.push(linterProvider); - - const jupyterExtension = extensions.getExtension('donjayamanne.jupyter'); - const lintingEngine = serviceContainer.get(ILintingEngine); - lintingEngine.linkJupiterExtension(jupyterExtension).ignoreErrors(); - - tests.activate(context, unitTestOutChannel, symbolProvider, serviceContainer); - - context.subscriptions.push(new WorkspaceSymbols(serviceContainer)); - context.subscriptions.push(languages.registerOnTypeFormattingEditProvider(PYTHON, new BlockFormatProviders(), ':')); - context.subscriptions.push(languages.registerOnTypeFormattingEditProvider(PYTHON, new OnEnterFormatter(), '\n')); - - serviceContainer.getAll(IDebugConfigurationProvider).forEach(debugConfig => { - context.subscriptions.push(debug.registerDebugConfigurationProvider(debugConfig.debugType, debugConfig)); - }); - activationDeferred.resolve(); - - const deprecationMgr = new FeatureDeprecationManager(persistentStateFactory, !!jupyterExtension); - deprecationMgr.initialize(); - context.subscriptions.push(new FeatureDeprecationManager(persistentStateFactory, !!jupyterExtension)); -} - -async function sendStartupTelemetry(activatedPromise: Promise, serviceContainer: IServiceContainer) { - const stopWatch = new StopWatch(); - const logger = serviceContainer.get(ILogger); - try { - await activatedPromise; - const duration = stopWatch.elapsedTime; - const condaLocator = serviceContainer.get(ICondaService); - const condaVersion = await condaLocator.getCondaVersion().catch(() => undefined); - const props = condaVersion ? { condaVersion } : undefined; - sendTelemetryEvent(EDITOR_LOAD, duration, props); - } catch (ex) { - logger.logError('sendStartupTelemetry failed.', ex); - } -} +'use strict'; +// This line should always be right on top. +// tslint:disable-next-line:no-any +if ((Reflect as any).metadata === undefined) { + // tslint:disable-next-line:no-require-imports no-var-requires + require('reflect-metadata'); +} +import { Container } from 'inversify'; +import { + debug, Disposable, DocumentFilter, ExtensionContext, + extensions, IndentAction, languages, Memento, + OutputChannel, window +} from 'vscode'; +import { IS_ANALYSIS_ENGINE_TEST } from '../test/constants'; +import { AnalysisExtensionActivator } from './activation/analysis'; +import { ClassicExtensionActivator } from './activation/classic'; +import { IExtensionActivator } from './activation/types'; +import { PythonSettings } from './common/configSettings'; +import { STANDARD_OUTPUT_CHANNEL } from './common/constants'; +import { FeatureDeprecationManager } from './common/featureDeprecationManager'; +import { createDeferred } from './common/helpers'; +import { PythonInstaller } from './common/installer/pythonInstallation'; +import { registerTypes as installerRegisterTypes } from './common/installer/serviceRegistry'; +import { registerTypes as platformRegisterTypes } from './common/platform/serviceRegistry'; +import { registerTypes as processRegisterTypes } from './common/process/serviceRegistry'; +import { registerTypes as commonRegisterTypes } from './common/serviceRegistry'; +import { StopWatch } from './common/stopWatch'; +import { GLOBAL_MEMENTO, IConfigurationService, IDisposableRegistry, ILogger, IMemento, IOutputChannel, IPersistentStateFactory, WORKSPACE_MEMENTO } from './common/types'; +import { registerTypes as variableRegisterTypes } from './common/variables/serviceRegistry'; +import { BaseConfigurationProvider } from './debugger/configProviders/baseProvider'; +import { registerTypes as debugConfigurationRegisterTypes } from './debugger/configProviders/serviceRegistry'; +import { IDebugConfigurationProvider } from './debugger/types'; +import { registerTypes as formattersRegisterTypes } from './formatters/serviceRegistry'; +import { IInterpreterSelector } from './interpreter/configuration/types'; +import { ICondaService, IInterpreterService } from './interpreter/contracts'; +import { registerTypes as interpretersRegisterTypes } from './interpreter/serviceRegistry'; +import { ServiceContainer } from './ioc/container'; +import { ServiceManager } from './ioc/serviceManager'; +import { IServiceContainer } from './ioc/types'; +import { LinterCommands } from './linters/linterCommands'; +import { registerTypes as lintersRegisterTypes } from './linters/serviceRegistry'; +import { ILintingEngine } from './linters/types'; +import { PythonFormattingEditProvider } from './providers/formatProvider'; +import { LinterProvider } from './providers/linterProvider'; +import { ReplProvider } from './providers/replProvider'; +import { TerminalProvider } from './providers/terminalProvider'; +import { activateUpdateSparkLibraryProvider } from './providers/updateSparkLibraryProvider'; +import * as sortImports from './sortImports'; +import { sendTelemetryEvent } from './telemetry'; +import { EDITOR_LOAD } from './telemetry/constants'; +import { registerTypes as commonRegisterTerminalTypes } from './terminals/serviceRegistry'; +import { ICodeExecutionManager } from './terminals/types'; +import { BlockFormatProviders } from './typeFormatters/blockFormatProvider'; +import { OnEnterFormatter } from './typeFormatters/onEnterFormatter'; +import { TEST_OUTPUT_CHANNEL } from './unittests/common/constants'; +import { registerTypes as unitTestsRegisterTypes } from './unittests/serviceRegistry'; +import { WorkspaceSymbols } from './workspaceSymbols/main'; + +const activationDeferred = createDeferred(); +export const activated = activationDeferred.promise; +const PYTHON: DocumentFilter = { language: 'python' }; + +// tslint:disable-next-line:max-func-body-length +export async function activate(context: ExtensionContext) { + const cont = new Container(); + const serviceManager = new ServiceManager(cont); + const serviceContainer = new ServiceContainer(cont); + registerServices(context, serviceManager, serviceContainer); + + const interpreterManager = serviceContainer.get(IInterpreterService); + // This must be completed before we can continue as language server needs the interpreter path. + interpreterManager.initialize(); + await interpreterManager.autoSetInterpreter(); + + const configuration = serviceManager.get(IConfigurationService); + const pythonSettings = configuration.getSettings(); + + const activator: IExtensionActivator = IS_ANALYSIS_ENGINE_TEST || !pythonSettings.jediEnabled + ? new AnalysisExtensionActivator(serviceManager, pythonSettings) + : new ClassicExtensionActivator(serviceManager, pythonSettings); + + await activator.activate(context); + + const standardOutputChannel = serviceManager.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); + sortImports.activate(context, standardOutputChannel, serviceManager); + + serviceManager.get(ICodeExecutionManager).registerCommands(); + // tslint:disable-next-line:no-floating-promises + sendStartupTelemetry(activated, serviceContainer); + + const pythonInstaller = new PythonInstaller(serviceContainer); + pythonInstaller.checkPythonInstallation(PythonSettings.getInstance()) + .catch(ex => console.error('Python Extension: pythonInstaller.checkPythonInstallation', ex)); + + interpreterManager.refresh() + .catch(ex => console.error('Python Extension: interpreterManager.refresh', ex)); + + const jupyterExtension = extensions.getExtension('donjayamanne.jupyter'); + const lintingEngine = serviceManager.get(ILintingEngine); + lintingEngine.linkJupiterExtension(jupyterExtension).ignoreErrors(); + + context.subscriptions.push(new LinterCommands(serviceManager)); + const linterProvider = new LinterProvider(context, serviceManager); + context.subscriptions.push(linterProvider); + + // Enable indentAction + // tslint:disable-next-line:no-non-null-assertion + languages.setLanguageConfiguration(PYTHON.language!, { + onEnterRules: [ + { + beforeText: /^\s*(?:def|class|for|if|elif|else|while|try|with|finally|except|async)\b.*/, + action: { indentAction: IndentAction.Indent } + }, + { + beforeText: /^\s*#.*/, + afterText: /.+$/, + action: { indentAction: IndentAction.None, appendText: '# ' } + }, + { + beforeText: /^\s+(continue|break|return)\b.*/, + afterText: /\s+$/, + action: { indentAction: IndentAction.Outdent } + } + ] + }); + + if (pythonSettings && pythonSettings.formatting && pythonSettings.formatting.provider !== 'none') { + const formatProvider = new PythonFormattingEditProvider(context, serviceContainer); + context.subscriptions.push(languages.registerDocumentFormattingEditProvider(PYTHON, formatProvider)); + context.subscriptions.push(languages.registerDocumentRangeFormattingEditProvider(PYTHON, formatProvider)); + } + + context.subscriptions.push(languages.registerOnTypeFormattingEditProvider(PYTHON, new BlockFormatProviders(), ':')); + context.subscriptions.push(languages.registerOnTypeFormattingEditProvider(PYTHON, new OnEnterFormatter(), '\n')); + + const persistentStateFactory = serviceManager.get(IPersistentStateFactory); + const deprecationMgr = new FeatureDeprecationManager(persistentStateFactory, !!jupyterExtension); + deprecationMgr.initialize(); + context.subscriptions.push(new FeatureDeprecationManager(persistentStateFactory, !!jupyterExtension)); + + context.subscriptions.push(serviceContainer.get(IInterpreterSelector)); + context.subscriptions.push(activateUpdateSparkLibraryProvider()); + + context.subscriptions.push(new ReplProvider(serviceContainer)); + context.subscriptions.push(new TerminalProvider(serviceContainer)); + context.subscriptions.push(new WorkspaceSymbols(serviceContainer)); + + serviceContainer.getAll(IDebugConfigurationProvider).forEach(debugConfig => { + context.subscriptions.push(debug.registerDebugConfigurationProvider(debugConfig.debugType, debugConfig)); + }); + activationDeferred.resolve(); +} + +function registerServices(context: ExtensionContext, serviceManager: ServiceManager, serviceContainer: ServiceContainer) { + serviceManager.addSingletonInstance(IServiceContainer, serviceContainer); + serviceManager.addSingletonInstance(IDisposableRegistry, context.subscriptions); + serviceManager.addSingletonInstance(IMemento, context.globalState, GLOBAL_MEMENTO); + serviceManager.addSingletonInstance(IMemento, context.workspaceState, WORKSPACE_MEMENTO); + + const standardOutputChannel = window.createOutputChannel('Python'); + const unitTestOutChannel = window.createOutputChannel('Python Test Log'); + serviceManager.addSingletonInstance(IOutputChannel, standardOutputChannel, STANDARD_OUTPUT_CHANNEL); + serviceManager.addSingletonInstance(IOutputChannel, unitTestOutChannel, TEST_OUTPUT_CHANNEL); + + commonRegisterTypes(serviceManager); + processRegisterTypes(serviceManager); + variableRegisterTypes(serviceManager); + unitTestsRegisterTypes(serviceManager); + lintersRegisterTypes(serviceManager); + interpretersRegisterTypes(serviceManager); + formattersRegisterTypes(serviceManager); + platformRegisterTypes(serviceManager); + installerRegisterTypes(serviceManager); + commonRegisterTerminalTypes(serviceManager); + debugConfigurationRegisterTypes(serviceManager); +} + +async function sendStartupTelemetry(activatedPromise: Promise, serviceContainer: IServiceContainer) { + const stopWatch = new StopWatch(); + const logger = serviceContainer.get(ILogger); + try { + await activatedPromise; + const duration = stopWatch.elapsedTime; + const condaLocator = serviceContainer.get(ICondaService); + const condaVersion = await condaLocator.getCondaVersion().catch(() => undefined); + const props = condaVersion ? { condaVersion } : undefined; + sendTelemetryEvent(EDITOR_LOAD, duration, props); + } catch (ex) { + logger.logError('sendStartupTelemetry failed.', ex); + } +} diff --git a/src/test/.vscode/settings.json b/src/test/.vscode/settings.json index cc64e708bea1..ba9c239e801d 100644 --- a/src/test/.vscode/settings.json +++ b/src/test/.vscode/settings.json @@ -5,11 +5,8 @@ "python.unitTest.nosetestArgs": [], "python.unitTest.pyTestArgs": [], "python.unitTest.unittestArgs": [ - "-v", - "-s", - ".", - "-p", - "*test*.py" + "-s=./tests", + "-p=test_*.py" ], "python.sortImports.args": [], "python.linting.lintOnSave": false, @@ -20,5 +17,6 @@ "python.linting.pylamaEnabled": false, "python.linting.mypyEnabled": false, "python.formatting.provider": "yapf", - "python.linting.pylintUseMinimalCheckers": false + "python.linting.pylintUseMinimalCheckers": false, + "python.pythonPath": "python" } diff --git a/src/test/analysisEngineTest.ts b/src/test/analysisEngineTest.ts new file mode 100644 index 000000000000..509cba41160c --- /dev/null +++ b/src/test/analysisEngineTest.ts @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// tslint:disable:no-console no-require-imports no-var-requires +import * as path from 'path'; + +process.env.CODE_TESTS_WORKSPACE = path.join(__dirname, '..', '..', 'src', 'test'); +process.env.IS_CI_SERVER_TEST_DEBUGGER = ''; +process.env.VSC_PYTHON_ANALYSIS = '1'; + +function start() { + console.log('*'.repeat(100)); + console.log('Start Analysis Engine tests'); + require('../../node_modules/vscode/bin/test'); +} +start(); diff --git a/src/test/autocomplete/base.test.ts b/src/test/autocomplete/base.test.ts index 4c4b8fd65992..5219e4ababd9 100644 --- a/src/test/autocomplete/base.test.ts +++ b/src/test/autocomplete/base.test.ts @@ -1,222 +1,248 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// tslint:disable:no-unused-variable -import * as assert from 'assert'; -import { EOL } from 'os'; -import * as path from 'path'; -import * as vscode from 'vscode'; -import { rootWorkspaceUri } from '../common'; -import { closeActiveWindows, initialize, initializeTest } from '../initialize'; -import { UnitTestIocContainer } from '../unittests/serviceRegistry'; - -const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); -const fileOne = path.join(autoCompPath, 'one.py'); -const fileImport = path.join(autoCompPath, 'imp.py'); -const fileDoc = path.join(autoCompPath, 'doc.py'); -const fileLambda = path.join(autoCompPath, 'lamb.py'); -const fileDecorator = path.join(autoCompPath, 'deco.py'); -const fileEncoding = path.join(autoCompPath, 'four.py'); -const fileEncodingUsed = path.join(autoCompPath, 'five.py'); -const fileSuppress = path.join(autoCompPath, 'suppress.py'); - -// tslint:disable-next-line:max-func-body-length -suite('Autocomplete', () => { - let isPython2: boolean; - let ioc: UnitTestIocContainer; - suiteSetup(async () => { - await initialize(); - initializeDI(); - isPython2 = await ioc.getPythonMajorVersion(rootWorkspaceUri) === 2; - }); - setup(initializeTest); - suiteTeardown(closeActiveWindows); - teardown(async () => { - await closeActiveWindows(); - ioc.dispose(); - }); - function initializeDI() { - ioc = new UnitTestIocContainer(); - ioc.registerCommonTypes(); - ioc.registerVariableTypes(); - ioc.registerProcessTypes(); - } - - test('For "sys."', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileOne).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(3, 10); - return vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - }).then(list => { - assert.equal(list!.items.filter(item => item.label === 'api_version').length, 1, 'api_version not found'); - }).then(done, done); - }); - - // https://github.com/DonJayamanne/pythonVSCode/issues/975 - test('For "import *"', async () => { - const textDocument = await vscode.workspace.openTextDocument(fileImport); - await vscode.window.showTextDocument(textDocument); - const position = new vscode.Position(1, 4); - const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - assert.equal(list!.items.filter(item => item.label === 'fstat').length, 1, 'fstat not found'); - }); - - // https://github.com/DonJayamanne/pythonVSCode/issues/898 - test('For "f.readlines()"', async () => { - const textDocument = await vscode.workspace.openTextDocument(fileDoc); - await vscode.window.showTextDocument(textDocument); - const position = new vscode.Position(5, 27); - const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - // These are not known to work, jedi issue - // assert.equal(list.items.filter(item => item.label === 'capitalize').length, 1, 'capitalize not found (known not to work, Jedi issue)'); - // assert.notEqual(list.items.filter(item => item.label === 'upper').length, 1, 'upper not found'); - // assert.notEqual(list.items.filter(item => item.label === 'lower').length, 1, 'lower not found'); - }); - - // https://github.com/DonJayamanne/pythonVSCode/issues/265 - test('For "lambda"', async function () { - if (isPython2) { - // tslint:disable-next-line:no-invalid-this - this.skip(); - return; - } - const textDocument = await vscode.workspace.openTextDocument(fileLambda); - await vscode.window.showTextDocument(textDocument); - const position = new vscode.Position(1, 19); - const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - assert.notEqual(list!.items.filter(item => item.label === 'append').length, 0, 'append not found'); - assert.notEqual(list!.items.filter(item => item.label === 'clear').length, 0, 'clear not found'); - assert.notEqual(list!.items.filter(item => item.label === 'count').length, 0, 'cound not found'); - }); - - // https://github.com/DonJayamanne/pythonVSCode/issues/630 - test('For "abc.decorators"', async () => { - const textDocument = await vscode.workspace.openTextDocument(fileDecorator); - await vscode.window.showTextDocument(textDocument); - let position = new vscode.Position(3, 9); - let list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - assert.notEqual(list!.items.filter(item => item.label === 'ABCMeta').length, 0, 'ABCMeta not found'); - assert.notEqual(list!.items.filter(item => item.label === 'abstractmethod').length, 0, 'abstractmethod not found'); - - position = new vscode.Position(4, 9); - list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - assert.notEqual(list!.items.filter(item => item.label === 'ABCMeta').length, 0, 'ABCMeta not found'); - assert.notEqual(list!.items.filter(item => item.label === 'abstractmethod').length, 0, 'abstractmethod not found'); - - position = new vscode.Position(2, 30); - list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - assert.notEqual(list!.items.filter(item => item.label === 'ABCMeta').length, 0, 'ABCMeta not found'); - assert.notEqual(list!.items.filter(item => item.label === 'abstractmethod').length, 0, 'abstractmethod not found'); - }); - - // https://github.com/DonJayamanne/pythonVSCode/issues/727 - // https://github.com/DonJayamanne/pythonVSCode/issues/746 - // https://github.com/davidhalter/jedi/issues/859 - test('For "time.slee"', async () => { - const textDocument = await vscode.workspace.openTextDocument(fileDoc); - await vscode.window.showTextDocument(textDocument); - const position = new vscode.Position(10, 9); - const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - - const items = list!.items.filter(item => item.label === 'sleep'); - assert.notEqual(items.length, 0, 'sleep not found'); - - checkDocumentation(items[0], 'Delay execution for a given number of seconds. The argument may be'); - }); - - test('For custom class', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileOne).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(30, 4); - return vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - }).then(list => { - assert.notEqual(list!.items.filter(item => item.label === 'method1').length, 0, 'method1 not found'); - assert.notEqual(list!.items.filter(item => item.label === 'method2').length, 0, 'method2 not found'); - }).then(done, done); - }); - - test('With Unicode Characters', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileEncoding).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(25, 4); - return vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - }).then(list => { - const items = list!.items.filter(item => item.label === 'bar'); - assert.equal(items.length, 1, 'bar not found'); - - const expected = `说明 - keep this line, it works${EOL}delete following line, it works${EOL}如果存在需要等待审批或正在执行的任务,将不刷新页面`; - checkDocumentation(items[0], expected); - }).then(done, done); - }); - - test('Across files With Unicode Characters', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileEncodingUsed).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(1, 5); - return vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - }).then(list => { - let items = list!.items.filter(item => item.label === 'Foo'); - assert.equal(items.length, 1, 'Foo not found'); - checkDocumentation(items[0], '说明'); - - items = list!.items.filter(item => item.label === 'showMessage'); - assert.equal(items.length, 1, 'showMessage not found'); - - const expected = `Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи. ${EOL}Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.`; - checkDocumentation(items[0], expected); - }).then(done, done); - }); - - // https://github.com/Microsoft/vscode-python/issues/110 - test('Suppress in strings/comments', async () => { - const positions = [ - new vscode.Position(0, 1), // false - new vscode.Position(0, 9), // true - new vscode.Position(0, 12), // false - new vscode.Position(1, 1), // false - new vscode.Position(1, 3), // false - new vscode.Position(2, 7), // false - new vscode.Position(3, 0), // false - new vscode.Position(4, 2), // false - new vscode.Position(4, 8), // false - new vscode.Position(5, 4), // false - new vscode.Position(5, 10) // false - ]; - const expected = [ - false, true, false, false, false, false, false, false, false, false, false - ]; - const textDocument = await vscode.workspace.openTextDocument(fileSuppress); - await vscode.window.showTextDocument(textDocument); - for (let i = 0; i < positions.length; i += 1) { - const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, positions[i]); - const result = list!.items.filter(item => item.label === 'abs').length; - assert.equal(result > 0, expected[i], - `Expected ${expected[i]} at position ${positions[i].line}:${positions[i].character} but got ${result}`); - } - }); -}); - -// tslint:disable-next-line:no-any -function checkDocumentation(item: vscode.CompletionItem, expectedContains: string): void { - const documentation = item.documentation as vscode.MarkdownString; - assert.notEqual(documentation, null, 'Documentation is not MarkdownString'); - - const inDoc = documentation.value.indexOf(expectedContains) >= 0; - assert.equal(inDoc, true, 'Documentation incorrect'); -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// tslint:disable:no-unused-variable +import * as assert from 'assert'; +import { EOL } from 'os'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { IConfigurationService } from '../../client/common/types'; +import { rootWorkspaceUri } from '../common'; +import { closeActiveWindows, initialize, initializeTest, IS_ANALYSIS_ENGINE_TEST } from '../initialize'; +import { UnitTestIocContainer } from '../unittests/serviceRegistry'; + +const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); +const fileOne = path.join(autoCompPath, 'one.py'); +const fileImport = path.join(autoCompPath, 'imp.py'); +const fileDoc = path.join(autoCompPath, 'doc.py'); +const fileLambda = path.join(autoCompPath, 'lamb.py'); +const fileDecorator = path.join(autoCompPath, 'deco.py'); +const fileEncoding = path.join(autoCompPath, 'four.py'); +const fileEncodingUsed = path.join(autoCompPath, 'five.py'); +const fileSuppress = path.join(autoCompPath, 'suppress.py'); + +// tslint:disable-next-line:max-func-body-length +suite('Autocomplete', () => { + let isPython2: boolean; + let ioc: UnitTestIocContainer; + + suiteSetup(async () => { + await initialize(); + initializeDI(); + isPython2 = await ioc.getPythonMajorVersion(rootWorkspaceUri) === 2; + }); + setup(initializeTest); + suiteTeardown(closeActiveWindows); + teardown(async () => { + await closeActiveWindows(); + ioc.dispose(); + }); + function initializeDI() { + ioc = new UnitTestIocContainer(); + ioc.registerCommonTypes(); + ioc.registerVariableTypes(); + ioc.registerProcessTypes(); + } + + test('For "sys."', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileOne).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(3, 10); + return vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + }).then(list => { + assert.equal(list!.items.filter(item => item.label === 'api_version').length, 1, 'api_version not found'); + }).then(done, done); + }); + + // https://github.com/DonJayamanne/pythonVSCode/issues/975 + test('For "import *"', async () => { + const textDocument = await vscode.workspace.openTextDocument(fileImport); + await vscode.window.showTextDocument(textDocument); + const position = new vscode.Position(1, 4); + const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + assert.equal(list!.items.filter(item => item.label === 'fstat').length, 1, 'fstat not found'); + }); + + // https://github.com/DonJayamanne/pythonVSCode/issues/898 + test('For "f.readlines()"', async () => { + const textDocument = await vscode.workspace.openTextDocument(fileDoc); + await vscode.window.showTextDocument(textDocument); + const position = new vscode.Position(5, 27); + const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + // These are not known to work, jedi issue + // assert.equal(list.items.filter(item => item.label === 'capitalize').length, 1, 'capitalize not found (known not to work, Jedi issue)'); + // assert.notEqual(list.items.filter(item => item.label === 'upper').length, 1, 'upper not found'); + // assert.notEqual(list.items.filter(item => item.label === 'lower').length, 1, 'lower not found'); + }); + + // https://github.com/DonJayamanne/pythonVSCode/issues/265 + test('For "lambda"', async function () { + if (isPython2) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + return; + } + const textDocument = await vscode.workspace.openTextDocument(fileLambda); + await vscode.window.showTextDocument(textDocument); + const position = new vscode.Position(1, 19); + const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + assert.notEqual(list!.items.filter(item => item.label === 'append').length, 0, 'append not found'); + assert.notEqual(list!.items.filter(item => item.label === 'clear').length, 0, 'clear not found'); + assert.notEqual(list!.items.filter(item => item.label === 'count').length, 0, 'cound not found'); + }); + + // https://github.com/DonJayamanne/pythonVSCode/issues/630 + test('For "abc.decorators"', async () => { + // Disabled for MS Python Code Analysis, see https://github.com/Microsoft/PTVS/issues/3857 + if (IS_ANALYSIS_ENGINE_TEST) { + return; + } + const textDocument = await vscode.workspace.openTextDocument(fileDecorator); + await vscode.window.showTextDocument(textDocument); + let position = new vscode.Position(3, 9); + let list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + assert.notEqual(list!.items.filter(item => item.label === 'ABCMeta').length, 0, 'ABCMeta not found'); + assert.notEqual(list!.items.filter(item => item.label === 'abstractmethod').length, 0, 'abstractmethod not found'); + + position = new vscode.Position(4, 9); + list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + assert.notEqual(list!.items.filter(item => item.label === 'ABCMeta').length, 0, 'ABCMeta not found'); + assert.notEqual(list!.items.filter(item => item.label === 'abstractmethod').length, 0, 'abstractmethod not found'); + + position = new vscode.Position(2, 30); + list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + assert.notEqual(list!.items.filter(item => item.label === 'ABCMeta').length, 0, 'ABCMeta not found'); + assert.notEqual(list!.items.filter(item => item.label === 'abstractmethod').length, 0, 'abstractmethod not found'); + }); + + // https://github.com/DonJayamanne/pythonVSCode/issues/727 + // https://github.com/DonJayamanne/pythonVSCode/issues/746 + // https://github.com/davidhalter/jedi/issues/859 + test('For "time.slee"', async () => { + const textDocument = await vscode.workspace.openTextDocument(fileDoc); + await vscode.window.showTextDocument(textDocument); + const position = new vscode.Position(10, 9); + const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + + const items = list!.items.filter(item => item.label === 'sleep'); + assert.notEqual(items.length, 0, 'sleep not found'); + + checkDocumentation(items[0], 'Delay execution for a given number of seconds. The argument may be'); + }); + + test('For custom class', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileOne).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(30, 4); + return vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + }).then(list => { + assert.notEqual(list!.items.filter(item => item.label === 'method1').length, 0, 'method1 not found'); + assert.notEqual(list!.items.filter(item => item.label === 'method2').length, 0, 'method2 not found'); + }).then(done, done); + }); + + test('With Unicode Characters', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileEncoding).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(25, 4); + return vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + }).then(list => { + const items = list!.items.filter(item => item.label === 'bar'); + assert.equal(items.length, 1, 'bar not found'); + + const expected1 = '说明 - keep this line, it works'; + checkDocumentation(items[0], expected1); + + const expected2 = '如果存在需要等待审批或正在执行的任务,将不刷新页面'; + checkDocumentation(items[0], expected2); + }).then(done, done); + }); + + test('Across files With Unicode Characters', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileEncodingUsed).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(1, 5); + return vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + }).then(list => { + let items = list!.items.filter(item => item.label === 'Foo'); + assert.equal(items.length, 1, 'Foo not found'); + checkDocumentation(items[0], '说明'); + + items = list!.items.filter(item => item.label === 'showMessage'); + assert.equal(items.length, 1, 'showMessage not found'); + + const expected1 = 'Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи.'; + checkDocumentation(items[0], expected1); + + const expected2 = 'Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.'; + checkDocumentation(items[0], expected2); + }).then(done, done); + }); + + // https://github.com/Microsoft/vscode-python/issues/110 + test('Suppress in strings/comments', async () => { + // Excluded from MS Python Code Analysis b/c skipping of strings and comments + // is not yet there. See https://github.com/Microsoft/PTVS/issues/3798 + if (IS_ANALYSIS_ENGINE_TEST) { + return; + } + const positions = [ + new vscode.Position(0, 1), // false + new vscode.Position(0, 9), // true + new vscode.Position(0, 12), // false + new vscode.Position(1, 1), // false + new vscode.Position(1, 3), // false + new vscode.Position(2, 7), // false + new vscode.Position(3, 0), // false + new vscode.Position(4, 2), // false + new vscode.Position(4, 8), // false + new vscode.Position(5, 4), // false + new vscode.Position(5, 10) // false + ]; + const expected = [ + false, true, false, false, false, false, false, false, false, false, false + ]; + const textDocument = await vscode.workspace.openTextDocument(fileSuppress); + await vscode.window.showTextDocument(textDocument); + for (let i = 0; i < positions.length; i += 1) { + const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, positions[i]); + const result = list!.items.filter(item => item.label === 'abs').length; + assert.equal(result > 0, expected[i], + `Expected ${expected[i]} at position ${positions[i].line}:${positions[i].character} but got ${result}`); + } + }); +}); + +// tslint:disable-next-line:no-any +function checkDocumentation(item: vscode.CompletionItem, expectedContains: string): void { + let isValidType = false; + let documentation: string; + + if (typeof item.documentation === 'string') { + isValidType = true; + documentation = item.documentation; + } else { + documentation = (item.documentation as vscode.MarkdownString).value; + isValidType = documentation !== undefined && documentation !== null; + } + assert.equal(isValidType, true, 'Documentation is neither string nor vscode.MarkdownString'); + + const inDoc = documentation.indexOf(expectedContains) >= 0; + assert.equal(inDoc, true, 'Documentation incorrect'); +} diff --git a/src/test/autocomplete/pep484.test.ts b/src/test/autocomplete/pep484.test.ts index 1eb13a792f1c..bac83c7afefa 100644 --- a/src/test/autocomplete/pep484.test.ts +++ b/src/test/autocomplete/pep484.test.ts @@ -2,6 +2,7 @@ import * as assert from 'assert'; import * as path from 'path'; import * as vscode from 'vscode'; import { rootWorkspaceUri } from '../common'; +import { IS_ANALYSIS_ENGINE_TEST } from '../constants'; import { closeActiveWindows, initialize, initializeTest } from '../initialize'; import { UnitTestIocContainer } from '../unittests/serviceRegistry'; @@ -11,10 +12,20 @@ const filePep484 = path.join(autoCompPath, 'pep484.py'); suite('Autocomplete PEP 484', () => { let isPython2: boolean; let ioc: UnitTestIocContainer; - suiteSetup(async () => { + suiteSetup(async function () { + // https://github.com/Microsoft/PTVS/issues/3917 + if (IS_ANALYSIS_ENGINE_TEST) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + } await initialize(); initializeDI(); isPython2 = await ioc.getPythonMajorVersion(rootWorkspaceUri) === 2; + if (isPython2) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + return; + } }); setup(initializeTest); suiteTeardown(closeActiveWindows); @@ -29,12 +40,7 @@ suite('Autocomplete PEP 484', () => { ioc.registerProcessTypes(); } - test('argument', async function () { - if (isPython2) { - // tslint:disable-next-line:no-invalid-this - this.skip(); - return; - } + test('argument', async () => { const textDocument = await vscode.workspace.openTextDocument(filePep484); await vscode.window.showTextDocument(textDocument); assert(vscode.window.activeTextEditor, 'No active editor'); @@ -46,9 +52,6 @@ suite('Autocomplete PEP 484', () => { }); test('return value', async () => { - if (isPython2) { - return; - } const textDocument = await vscode.workspace.openTextDocument(filePep484); await vscode.window.showTextDocument(textDocument); assert(vscode.window.activeTextEditor, 'No active editor'); diff --git a/src/test/autocomplete/pep526.test.ts b/src/test/autocomplete/pep526.test.ts index 099df5af10ab..24aadb2eb04d 100644 --- a/src/test/autocomplete/pep526.test.ts +++ b/src/test/autocomplete/pep526.test.ts @@ -2,19 +2,30 @@ import * as assert from 'assert'; import * as path from 'path'; import * as vscode from 'vscode'; import { rootWorkspaceUri } from '../common'; -import { closeActiveWindows, initialize, initializeTest } from '../initialize'; +import { closeActiveWindows, initialize, initializeTest, IS_ANALYSIS_ENGINE_TEST } from '../initialize'; import { UnitTestIocContainer } from '../unittests/serviceRegistry'; const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); const filePep526 = path.join(autoCompPath, 'pep526.py'); +// tslint:disable-next-line:max-func-body-length suite('Autocomplete PEP 526', () => { let isPython2: boolean; let ioc: UnitTestIocContainer; - suiteSetup(async () => { + suiteSetup(async function () { + // https://github.com/Microsoft/PTVS/issues/3917 + if (IS_ANALYSIS_ENGINE_TEST) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + } await initialize(); initializeDI(); isPython2 = await ioc.getPythonMajorVersion(rootWorkspaceUri) === 2; + if (isPython2) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + return; + } }); setup(initializeTest); suiteTeardown(closeActiveWindows); @@ -29,12 +40,7 @@ suite('Autocomplete PEP 526', () => { ioc.registerProcessTypes(); } - test('variable (abc:str)', async function () { - if (isPython2) { - // tslint:disable-next-line:no-invalid-this - this.skip(); - return; - } + test('variable (abc:str)', async () => { const textDocument = await vscode.workspace.openTextDocument(filePep526); await vscode.window.showTextDocument(textDocument); assert(vscode.window.activeTextEditor, 'No active editor'); @@ -45,11 +51,7 @@ suite('Autocomplete PEP 526', () => { assert.notEqual(list!.items.filter(item => item.label === 'lower').length, 0, 'lower not found'); }); - test('variable (abc: str = "")', async function () { - if (isPython2) { - // tslint:disable-next-line:no-invalid-this - this.skip(); - } + test('variable (abc: str = "")', async () => { const textDocument = await vscode.workspace.openTextDocument(filePep526); await vscode.window.showTextDocument(textDocument); assert(vscode.window.activeTextEditor, 'No active editor'); @@ -60,12 +62,7 @@ suite('Autocomplete PEP 526', () => { assert.notEqual(list!.items.filter(item => item.label === 'lower').length, 0, 'lower not found'); }); - test('variable (abc = UNKNOWN # type: str)', async function () { - if (isPython2) { - // tslint:disable-next-line:no-invalid-this - this.skip(); - return; - } + test('variable (abc = UNKNOWN # type: str)', async () => { const textDocument = await vscode.workspace.openTextDocument(filePep526); await vscode.window.showTextDocument(textDocument); assert(vscode.window.activeTextEditor, 'No active editor'); @@ -76,12 +73,7 @@ suite('Autocomplete PEP 526', () => { assert.notEqual(list!.items.filter(item => item.label === 'lower').length, 0, 'lower not found'); }); - test('class methods', async function () { - if (isPython2) { - // tslint:disable-next-line:no-invalid-this - this.skip(); - return; - } + test('class methods', async () => { const textDocument = await vscode.workspace.openTextDocument(filePep526); await vscode.window.showTextDocument(textDocument); assert(vscode.window.activeTextEditor, 'No active editor'); @@ -94,12 +86,7 @@ suite('Autocomplete PEP 526', () => { assert.notEqual(list!.items.filter(item => item.label === 'b').length, 0, 'method b not found'); }); - test('class method types', async function () { - if (isPython2) { - // tslint:disable-next-line:no-invalid-this - this.skip(); - return; - } + test('class method types', async () => { const textDocument = await vscode.workspace.openTextDocument(filePep526); await vscode.window.showTextDocument(textDocument); assert(vscode.window.activeTextEditor, 'No active editor'); diff --git a/src/test/common/moduleInstaller.test.ts b/src/test/common/moduleInstaller.test.ts index 72201771d5fc..698d424111a8 100644 --- a/src/test/common/moduleInstaller.test.ts +++ b/src/test/common/moduleInstaller.test.ts @@ -19,7 +19,7 @@ import { CurrentProcess } from '../../client/common/process/currentProcess'; import { IProcessService, IPythonExecutionFactory } from '../../client/common/process/types'; import { ITerminalService, ITerminalServiceFactory } from '../../client/common/terminal/types'; import { IConfigurationService, ICurrentProcess, IInstaller, ILogger, IPathUtils, IPersistentStateFactory, IPythonSettings, IsWindows } from '../../client/common/types'; -import { ICondaService, IInterpreterLocatorService, IInterpreterService, INTERPRETER_LOCATOR_SERVICE, InterpreterType, PIPENV_SERVICE } from '../../client/interpreter/contracts'; +import { ICondaService, IInterpreterLocatorService, IInterpreterService, INTERPRETER_LOCATOR_SERVICE, InterpreterType, PIPENV_SERVICE, PythonInterpreter } from '../../client/interpreter/contracts'; import { IServiceContainer } from '../../client/ioc/types'; import { rootWorkspaceUri } from '../common'; import { MockModuleInstaller } from '../mocks/moduleInstaller'; @@ -71,8 +71,10 @@ suite('Module Installer', () => { ioc.serviceManager.addSingleton(IModuleInstaller, PipEnvInstaller); condaService = TypeMoq.Mock.ofType(); ioc.serviceManager.addSingletonInstance(ICondaService, condaService.object); + interpreterService = TypeMoq.Mock.ofType(); ioc.serviceManager.addSingletonInstance(IInterpreterService, interpreterService.object); + ioc.serviceManager.addSingleton(IPathUtils, PathUtils); ioc.serviceManager.addSingleton(ICurrentProcess, CurrentProcess); ioc.serviceManager.addSingleton(IFileSystem, FileSystem); @@ -191,6 +193,12 @@ suite('Module Installer', () => { ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, mockInterpreterLocator.object, INTERPRETER_LOCATOR_SERVICE); ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, TypeMoq.Mock.ofType().object, PIPENV_SERVICE); + const interpreter: PythonInterpreter = { + type: InterpreterType.Unknown, + path: 'python' + }; + interpreterService.setup(x => x.getActiveInterpreter(TypeMoq.It.isAny())).returns(() => Promise.resolve(interpreter)); + const moduleName = 'xyz'; const moduleInstallers = ioc.serviceContainer.getAll(IModuleInstaller); diff --git a/src/test/common/process/pythonProc.simple.multiroot.test.ts b/src/test/common/process/pythonProc.simple.multiroot.test.ts index 2b9a3b513ad7..d8ff7c09cbb4 100644 --- a/src/test/common/process/pythonProc.simple.multiroot.test.ts +++ b/src/test/common/process/pythonProc.simple.multiroot.test.ts @@ -45,11 +45,9 @@ suite('PythonExecutableService', () => { this.skip(); } await clearPythonPathInWorkspaceFolder(workspace4Path); - - await (new ConfigurationService()).updateSettingAsync('envFile', undefined, workspace4PyFile, ConfigurationTarget.WorkspaceFolder); await initialize(); }); - setup(() => { + setup(async () => { cont = new Container(); serviceContainer = new ServiceContainer(cont); const serviceManager = new ServiceManager(cont); @@ -69,6 +67,7 @@ suite('PythonExecutableService', () => { configService = serviceManager.get(IConfigurationService); pythonExecFactory = serviceContainer.get(IPythonExecutionFactory); + await configService.updateSettingAsync('envFile', undefined, workspace4PyFile, ConfigurationTarget.WorkspaceFolder); return initializeTest(); }); suiteTeardown(closeActiveWindows); diff --git a/src/test/constants.ts b/src/test/constants.ts index 1b468a7d9d57..72f6dcf1b696 100644 --- a/src/test/constants.ts +++ b/src/test/constants.ts @@ -1,17 +1,22 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// tslint:disable:no-string-literal -import { workspace } from 'vscode'; - -export const IS_APPVEYOR = process.env['APPVEYOR'] === 'true'; -export const IS_CI_SERVER = process.env['TRAVIS'] === 'true' || IS_APPVEYOR; -export const TEST_TIMEOUT = 25000; -export const IS_MULTI_ROOT_TEST = isMultitrootTest(); -export const IS_CI_SERVER_TEST_DEBUGGER = process.env['IS_CI_SERVER_TEST_DEBUGGER'] === '1'; -// If running on CI server, then run debugger tests ONLY if the corresponding flag is enabled. -export const TEST_DEBUGGER = IS_CI_SERVER ? IS_CI_SERVER_TEST_DEBUGGER : true; - -function isMultitrootTest() { - return Array.isArray(workspace.workspaceFolders) && workspace.workspaceFolders.length > 1; -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// tslint:disable:no-string-literal +import { workspace } from 'vscode'; +import { PythonSettings } from '../client/common/configSettings'; + +export const IS_APPVEYOR = process.env['APPVEYOR'] === 'true'; +export const IS_TRAVIS = process.env['TRAVIS'] === 'true'; +export const IS_CI_SERVER = IS_TRAVIS || IS_APPVEYOR; +export const TEST_TIMEOUT = 25000; +export const IS_MULTI_ROOT_TEST = isMultitrootTest(); +export const IS_CI_SERVER_TEST_DEBUGGER = process.env['IS_CI_SERVER_TEST_DEBUGGER'] === '1'; +// If running on CI server, then run debugger tests ONLY if the corresponding flag is enabled. +export const TEST_DEBUGGER = IS_CI_SERVER ? IS_CI_SERVER_TEST_DEBUGGER : true; + +function isMultitrootTest() { + return Array.isArray(workspace.workspaceFolders) && workspace.workspaceFolders.length > 1; +} + +export const IS_ANALYSIS_ENGINE_TEST = + !IS_TRAVIS && (process.env['VSC_PYTHON_ANALYSIS'] === '1' || !PythonSettings.getInstance().jediEnabled); diff --git a/src/test/definitions/hover.test.ts b/src/test/definitions/hover.jedi.test.ts similarity index 96% rename from src/test/definitions/hover.test.ts rename to src/test/definitions/hover.jedi.test.ts index ba194a902446..5d1ea8b386cd 100644 --- a/src/test/definitions/hover.test.ts +++ b/src/test/definitions/hover.jedi.test.ts @@ -1,276 +1,283 @@ -import * as assert from 'assert'; -import { EOL } from 'os'; -import * as path from 'path'; -import * as vscode from 'vscode'; -import { closeActiveWindows, initialize, initializeTest } from '../initialize'; -import { normalizeMarkedString } from '../textUtils'; - -const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); -const hoverPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'hover'); -const fileOne = path.join(autoCompPath, 'one.py'); -const fileThree = path.join(autoCompPath, 'three.py'); -const fileEncoding = path.join(autoCompPath, 'four.py'); -const fileEncodingUsed = path.join(autoCompPath, 'five.py'); -const fileHover = path.join(autoCompPath, 'hoverTest.py'); -const fileStringFormat = path.join(hoverPath, 'stringFormat.py'); - -// tslint:disable-next-line:max-func-body-length -suite('Hover Definition', () => { - suiteSetup(initialize); - setup(initializeTest); - suiteTeardown(closeActiveWindows); - teardown(closeActiveWindows); - - test('Method', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileOne).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(30, 5); - return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); - }).then(result => { - const def = result!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '30,4', 'Start position is incorrect'); - assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '30,11', 'End position is incorrect'); - assert.equal(def[0].contents.length, 1, 'Invalid content items'); - // tslint:disable-next-line:prefer-template - const expectedContent = '```python' + EOL + 'def method1()' + EOL + '```' + EOL + 'This is method1'; - assert.equal(normalizeMarkedString(def[0].contents[0]), expectedContent, 'function signature incorrect'); - }).then(done, done); - }); - - test('Across files', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileThree).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(1, 12); - return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); - }).then(result => { - const def = result!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '1,9', 'Start position is incorrect'); - assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '1,12', 'End position is incorrect'); - // tslint:disable-next-line:prefer-template - assert.equal(normalizeMarkedString(def[0].contents[0]), '```python' + EOL + 'def fun()' + EOL + '```' + EOL + 'This is fun', 'Invalid conents'); - }).then(done, done); - }); - - test('With Unicode Characters', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileEncoding).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(25, 6); - return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); - }).then(result => { - const def = result!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '25,4', 'Start position is incorrect'); - assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '25,7', 'End position is incorrect'); - // tslint:disable-next-line:prefer-template - assert.equal(normalizeMarkedString(def[0].contents[0]), '```python' + EOL + 'def bar()' + EOL + '```' + EOL + - '说明 - keep this line, it works' + EOL + 'delete following line, it works' + - EOL + '如果存在需要等待审批或正在执行的任务,将不刷新页面', 'Invalid conents'); - }).then(done, done); - }); - - test('Across files with Unicode Characters', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileEncodingUsed).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(1, 11); - return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); - }).then(result => { - const def = result!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '1,5', 'Start position is incorrect'); - assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '1,16', 'End position is incorrect'); - // tslint:disable-next-line:prefer-template - assert.equal(normalizeMarkedString(def[0].contents[0]), '```python' + EOL + - 'def showMessage()' + EOL + - '```' + EOL + - 'Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи. ' + EOL + - 'Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.', 'Invalid conents'); - }).then(done, done); - }); - - test('Nothing for keywords (class)', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileOne).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(5, 1); - return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); - }).then(def => { - assert.equal(def!.length, 0, 'Definition length is incorrect'); - }).then(done, done); - }); - - test('Nothing for keywords (for)', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileHover).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(3, 1); - return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); - }).then(def => { - assert.equal(def!.length, 0, 'Definition length is incorrect'); - }).then(done, done); - }); - - test('Highlighting Class', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileHover).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(11, 15); - return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); - }).then(result => { - const def = result!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '11,12', 'Start position is incorrect'); - assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '11,18', 'End position is incorrect'); - // tslint:disable-next-line:prefer-template - const documentation = '```python' + EOL + - 'class Random(x=None)' + EOL + - '```' + EOL + - 'Random number generator base class used by bound module functions.' + EOL + - '' + EOL + - 'Used to instantiate instances of Random to get generators that don\'t' + EOL + - 'share state.' + EOL + - '' + EOL + - 'Class Random can also be subclassed if you want to use a different basic' + EOL + - 'generator of your own devising: in that case, override the following' + EOL + - 'methods: random(), seed(), getstate(), and setstate().' + EOL + - 'Optionally, implement a getrandbits() method so that randrange()' + EOL + - 'can cover arbitrarily large ranges.'; - - assert.equal(normalizeMarkedString(def[0].contents[0]), documentation, 'Invalid conents'); - }).then(done, done); - }); - - test('Highlight Method', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileHover).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(12, 10); - return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); - }).then(result => { - const def = result!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '12,5', 'Start position is incorrect'); - assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '12,12', 'End position is incorrect'); - // tslint:disable-next-line:prefer-template - assert.equal(normalizeMarkedString(def[0].contents[0]), '```python' + EOL + - 'def randint(a, b)' + EOL + - '```' + EOL + - 'Return random integer in range [a, b], including both end points.', 'Invalid conents'); - }).then(done, done); - }); - - test('Highlight Function', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileHover).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(8, 14); - return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); - }).then(result => { - const def = result!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '8,11', 'Start position is incorrect'); - assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '8,15', 'End position is incorrect'); - // tslint:disable-next-line:prefer-template - assert.equal(normalizeMarkedString(def[0].contents[0]), '```python' + EOL + - 'def acos(x)' + EOL + - '```' + EOL + - 'Return the arc cosine (measured in radians) of x.', 'Invalid conents'); - }).then(done, done); - }); - - test('Highlight Multiline Method Signature', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileHover).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(14, 14); - return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); - }).then(result => { - const def = result!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '14,9', 'Start position is incorrect'); - assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '14,15', 'End position is incorrect'); - // tslint:disable-next-line:prefer-template - assert.equal(normalizeMarkedString(def[0].contents[0]), '```python' + EOL + - 'class Thread(group=None, target=None, name=None, args=(), kwargs=None, verbose=None)' + EOL + - '```' + EOL + - 'A class that represents a thread of control.' + EOL + - '' + EOL + - 'This class can be safely subclassed in a limited fashion.', 'Invalid content items'); - }).then(done, done); - }); - - test('Variable', done => { - let textDocument: vscode.TextDocument; - vscode.workspace.openTextDocument(fileHover).then(document => { - textDocument = document; - return vscode.window.showTextDocument(textDocument); - }).then(editor => { - assert(vscode.window.activeTextEditor, 'No active editor'); - const position = new vscode.Position(6, 2); - return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); - }).then(result => { - const def = result!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(def[0].contents.length, 1, 'Only expected one result'); - const contents = normalizeMarkedString(def[0].contents[0]); - if (contents.indexOf('```python') === -1) { - assert.fail(contents, '', 'First line is incorrect', 'compare'); - } - if (contents.indexOf('rnd: Random') === -1) { - assert.fail(contents, '', 'Variable name or type are missing', 'compare'); - } - }).then(done, done); - }); - - test('format().capitalize()', async () => { - const textDocument = await vscode.workspace.openTextDocument(fileStringFormat); - await vscode.window.showTextDocument(textDocument); - const position = new vscode.Position(5, 41); - const def = (await vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position))!; - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(def[0].contents.length, 1, 'Only expected one result'); - const contents = normalizeMarkedString(def[0].contents[0]); - if (contents.indexOf('def capitalize') === -1) { - assert.fail(contents, '', '\'def capitalize\' is missing', 'compare'); - } - if (contents.indexOf('Return a capitalized version of S') === -1 && - contents.indexOf('Return a copy of the string S with only its first character') === -1) { - assert.fail(contents, '', '\'Return a capitalized version of S/Return a copy of the string S with only its first character\' message missing', 'compare'); - } - }); -}); +import * as assert from 'assert'; +import { EOL } from 'os'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { IS_ANALYSIS_ENGINE_TEST } from '../constants'; +import { closeActiveWindows, initialize, initializeTest } from '../initialize'; +import { normalizeMarkedString } from '../textUtils'; + +const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); +const hoverPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'hover'); +const fileOne = path.join(autoCompPath, 'one.py'); +const fileThree = path.join(autoCompPath, 'three.py'); +const fileEncoding = path.join(autoCompPath, 'four.py'); +const fileEncodingUsed = path.join(autoCompPath, 'five.py'); +const fileHover = path.join(autoCompPath, 'hoverTest.py'); +const fileStringFormat = path.join(hoverPath, 'stringFormat.py'); + +// tslint:disable-next-line:max-func-body-length +suite('Hover Definition (Jedi)', () => { + suiteSetup(async function () { + if (IS_ANALYSIS_ENGINE_TEST) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + } + await initialize(); + }); + setup(initializeTest); + suiteTeardown(closeActiveWindows); + teardown(closeActiveWindows); + + test('Method', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileOne).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(30, 5); + return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + }).then(result => { + const def = result!; + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '30,4', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '30,11', 'End position is incorrect'); + assert.equal(def[0].contents.length, 1, 'Invalid content items'); + // tslint:disable-next-line:prefer-template + const expectedContent = '```python' + EOL + 'def method1()' + EOL + '```' + EOL + 'This is method1'; + assert.equal(normalizeMarkedString(def[0].contents[0]), expectedContent, 'function signature incorrect'); + }).then(done, done); + }); + + test('Across files', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileThree).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(1, 12); + return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + }).then(result => { + const def = result!; + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '1,9', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '1,12', 'End position is incorrect'); + // tslint:disable-next-line:prefer-template + assert.equal(normalizeMarkedString(def[0].contents[0]), '```python' + EOL + 'def fun()' + EOL + '```' + EOL + 'This is fun', 'Invalid conents'); + }).then(done, done); + }); + + test('With Unicode Characters', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileEncoding).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(25, 6); + return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + }).then(result => { + const def = result!; + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '25,4', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '25,7', 'End position is incorrect'); + // tslint:disable-next-line:prefer-template + assert.equal(normalizeMarkedString(def[0].contents[0]), '```python' + EOL + 'def bar()' + EOL + '```' + EOL + + '说明 - keep this line, it works' + EOL + 'delete following line, it works' + + EOL + '如果存在需要等待审批或正在执行的任务,将不刷新页面', 'Invalid conents'); + }).then(done, done); + }); + + test('Across files with Unicode Characters', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileEncodingUsed).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(1, 11); + return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + }).then(result => { + const def = result!; + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '1,5', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '1,16', 'End position is incorrect'); + // tslint:disable-next-line:prefer-template + assert.equal(normalizeMarkedString(def[0].contents[0]), '```python' + EOL + + 'def showMessage()' + EOL + + '```' + EOL + + 'Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи. ' + EOL + + 'Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.', 'Invalid conents'); + }).then(done, done); + }); + + test('Nothing for keywords (class)', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileOne).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(5, 1); + return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + }).then(def => { + assert.equal(def!.length, 0, 'Definition length is incorrect'); + }).then(done, done); + }); + + test('Nothing for keywords (for)', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileHover).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(3, 1); + return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + }).then(def => { + assert.equal(def!.length, 0, 'Definition length is incorrect'); + }).then(done, done); + }); + + test('Highlighting Class', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileHover).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(11, 15); + return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + }).then(result => { + const def = result!; + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '11,12', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '11,18', 'End position is incorrect'); + // tslint:disable-next-line:prefer-template + const documentation = '```python' + EOL + + 'class Random(x=None)' + EOL + + '```' + EOL + + 'Random number generator base class used by bound module functions.' + EOL + + '' + EOL + + 'Used to instantiate instances of Random to get generators that don\'t' + EOL + + 'share state.' + EOL + + '' + EOL + + 'Class Random can also be subclassed if you want to use a different basic' + EOL + + 'generator of your own devising: in that case, override the following' + EOL + + 'methods: random(), seed(), getstate(), and setstate().' + EOL + + 'Optionally, implement a getrandbits() method so that randrange()' + EOL + + 'can cover arbitrarily large ranges.'; + + assert.equal(normalizeMarkedString(def[0].contents[0]), documentation, 'Invalid conents'); + }).then(done, done); + }); + + test('Highlight Method', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileHover).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(12, 10); + return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + }).then(result => { + const def = result!; + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '12,5', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '12,12', 'End position is incorrect'); + // tslint:disable-next-line:prefer-template + assert.equal(normalizeMarkedString(def[0].contents[0]), '```python' + EOL + + 'def randint(a, b)' + EOL + + '```' + EOL + + 'Return random integer in range [a, b], including both end points.', 'Invalid conents'); + }).then(done, done); + }); + + test('Highlight Function', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileHover).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(8, 14); + return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + }).then(result => { + const def = result!; + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '8,11', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '8,15', 'End position is incorrect'); + // tslint:disable-next-line:prefer-template + assert.equal(normalizeMarkedString(def[0].contents[0]), '```python' + EOL + + 'def acos(x)' + EOL + + '```' + EOL + + 'Return the arc cosine (measured in radians) of x.', 'Invalid conents'); + }).then(done, done); + }); + + test('Highlight Multiline Method Signature', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileHover).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(14, 14); + return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + }).then(result => { + const def = result!; + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '14,9', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '14,15', 'End position is incorrect'); + // tslint:disable-next-line:prefer-template + assert.equal(normalizeMarkedString(def[0].contents[0]), '```python' + EOL + + 'class Thread(group=None, target=None, name=None, args=(), kwargs=None, verbose=None)' + EOL + + '```' + EOL + + 'A class that represents a thread of control.' + EOL + + '' + EOL + + 'This class can be safely subclassed in a limited fashion.', 'Invalid content items'); + }).then(done, done); + }); + + test('Variable', done => { + let textDocument: vscode.TextDocument; + vscode.workspace.openTextDocument(fileHover).then(document => { + textDocument = document; + return vscode.window.showTextDocument(textDocument); + }).then(editor => { + assert(vscode.window.activeTextEditor, 'No active editor'); + const position = new vscode.Position(6, 2); + return vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + }).then(result => { + const def = result!; + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(def[0].contents.length, 1, 'Only expected one result'); + const contents = normalizeMarkedString(def[0].contents[0]); + if (contents.indexOf('```python') === -1) { + assert.fail(contents, '', 'First line is incorrect', 'compare'); + } + if (contents.indexOf('rnd: Random') === -1) { + assert.fail(contents, '', 'Variable name or type are missing', 'compare'); + } + }).then(done, done); + }); + + test('format().capitalize()', async () => { + const textDocument = await vscode.workspace.openTextDocument(fileStringFormat); + await vscode.window.showTextDocument(textDocument); + const position = new vscode.Position(5, 41); + const def = (await vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position))!; + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(def[0].contents.length, 1, 'Only expected one result'); + const contents = normalizeMarkedString(def[0].contents[0]); + if (contents.indexOf('def capitalize') === -1) { + assert.fail(contents, '', '\'def capitalize\' is missing', 'compare'); + } + if (contents.indexOf('Return a capitalized version of S') === -1 && + contents.indexOf('Return a copy of the string S with only its first character') === -1) { + assert.fail(contents, '', '\'Return a capitalized version of S/Return a copy of the string S with only its first character\' message missing', 'compare'); + } + }); +}); diff --git a/src/test/definitions/hover.ptvs.test.ts b/src/test/definitions/hover.ptvs.test.ts new file mode 100644 index 000000000000..8c3b981ca4bb --- /dev/null +++ b/src/test/definitions/hover.ptvs.test.ts @@ -0,0 +1,184 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as assert from 'assert'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import '../../client/common/extensions'; +import { IS_ANALYSIS_ENGINE_TEST } from '../constants'; +import { closeActiveWindows, initialize, initializeTest } from '../initialize'; +import { normalizeMarkedString } from '../textUtils'; + +const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); +const hoverPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'hover'); +const fileOne = path.join(autoCompPath, 'one.py'); +const fileThree = path.join(autoCompPath, 'three.py'); +const fileEncoding = path.join(autoCompPath, 'four.py'); +const fileEncodingUsed = path.join(autoCompPath, 'five.py'); +const fileHover = path.join(autoCompPath, 'hoverTest.py'); +const fileStringFormat = path.join(hoverPath, 'stringFormat.py'); + +let textDocument: vscode.TextDocument; + +// tslint:disable-next-line:max-func-body-length +suite('Hover Definition (Analysis Engine)', () => { + suiteSetup(async function () { + if (!IS_ANALYSIS_ENGINE_TEST) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + } + await initialize(); + }); + setup(initializeTest); + suiteTeardown(closeActiveWindows); + teardown(closeActiveWindows); + + async function openAndHover(file: string, line: number, character: number): Promise { + textDocument = await vscode.workspace.openTextDocument(file); + await vscode.window.showTextDocument(textDocument); + const position = new vscode.Position(line, character); + const result = await vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + return result ? result : []; + } + + test('Method', async () => { + const def = await openAndHover(fileOne, 30, 5); + assert.equal(def.length, 1, 'Definition length is incorrect'); + + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '30,0', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '30,11', 'End position is incorrect'); + assert.equal(def[0].contents.length, 1, 'Invalid content items'); + + const lines = normalizeMarkedString(def[0].contents[0]).splitLines(); + assert.equal(lines.length, 2, 'incorrect number of lines'); + assert.equal(lines[0].trim(), 'obj.method1: method method1 of one.Class1 objects', 'function signature line #1 is incorrect'); + assert.equal(lines[1].trim(), 'This is method1', 'function signature line #2 is incorrect'); + }); + + test('Across files', async () => { + const def = await openAndHover(fileThree, 1, 12); + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '1,0', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '1,12', 'End position is incorrect'); + + const lines = normalizeMarkedString(def[0].contents[0]).splitLines(); + assert.equal(lines.length, 2, 'incorrect number of lines'); + assert.equal(lines[0].trim(), 'two.ct().fun: method fun of two.ct objects', 'function signature line #1 is incorrect'); + assert.equal(lines[1].trim(), 'This is fun', 'function signature line #2 is incorrect'); + }); + + test('With Unicode Characters', async () => { + const def = await openAndHover(fileEncoding, 25, 6); + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '25,0', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '25,7', 'End position is incorrect'); + + const lines = normalizeMarkedString(def[0].contents[0]).splitLines(); + assert.equal(lines.length, 5, 'incorrect number of lines'); + assert.equal(lines[0].trim(), 'Foo.bar: def four.Foo.bar()', 'function signature line #1 is incorrect'); + assert.equal(lines[1].trim(), '说明 - keep this line, it works', 'function signature line #2 is incorrect'); + assert.equal(lines[2].trim(), 'delete following line, it works', 'function signature line #3 is incorrect'); + assert.equal(lines[3].trim(), '如果存在需要等待审批或正在执行的任务,将不刷新页面', 'function signature line #4 is incorrect'); + assert.equal(lines[4].trim(), 'declared in Foo', 'function signature line #5 is incorrect'); + }); + + test('Across files with Unicode Characters', async () => { + const def = await openAndHover(fileEncodingUsed, 1, 11); + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '1,0', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '1,16', 'End position is incorrect'); + + const lines = normalizeMarkedString(def[0].contents[0]).splitLines(); + assert.equal(lines.length, 3, 'incorrect number of lines'); + assert.equal(lines[0].trim(), 'four.showMessage: def four.showMessage()', 'function signature line #1 is incorrect'); + assert.equal(lines[1].trim(), 'Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи.', 'function signature line #2 is incorrect'); + assert.equal(lines[2].trim(), 'Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.', 'function signature line #3 is incorrect'); + }); + + test('Nothing for keywords (class)', async () => { + const def = await openAndHover(fileOne, 5, 1); + assert.equal(def.length, 0, 'Definition length is incorrect'); + }); + + test('Nothing for keywords (for)', async () => { + const def = await openAndHover(fileHover, 3, 1); + assert.equal(def!.length, 0, 'Definition length is incorrect'); + }); + + test('Highlighting Class', async () => { + const def = await openAndHover(fileHover, 11, 15); + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '11,7', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '11,18', 'End position is incorrect'); + + const lines = normalizeMarkedString(def[0].contents[0]).splitLines(); + assert.equal(lines.length, 9, 'incorrect number of lines'); + assert.equal(lines[0].trim(), 'misc.Random: class misc.Random(_random.Random)', 'function signature line #1 is incorrect'); + assert.equal(lines[1].trim(), 'Random number generator base class used by bound module functions.', 'function signature line #2 is incorrect'); + }); + + test('Highlight Method', async () => { + const def = await openAndHover(fileHover, 12, 10); + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '12,0', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '12,12', 'End position is incorrect'); + + const lines = normalizeMarkedString(def[0].contents[0]).splitLines(); + assert.equal(lines.length, 2, 'incorrect number of lines'); + assert.equal(lines[0].trim(), 'rnd2.randint: method randint of misc.Random objects -> int', 'function signature line #1 is incorrect'); + assert.equal(lines[1].trim(), 'Return random integer in range [a, b], including both end points.', 'function signature line #2 is incorrect'); + }); + + test('Highlight Function', async () => { + const def = await openAndHover(fileHover, 8, 14); + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '8,6', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '8,15', 'End position is incorrect'); + + const lines = normalizeMarkedString(def[0].contents[0]).splitLines(); + assert.equal(lines.length, 3, 'incorrect number of lines'); + assert.equal(lines[0].trim(), 'math.acos: built-in function acos(x)', 'function signature line #1 is incorrect'); + assert.equal(lines[1].trim(), 'acos(x)', 'function signature line #2 is incorrect'); + assert.equal(lines[2].trim(), 'Return the arc cosine (measured in radians) of x.', 'function signature line #3 is incorrect'); + }); + + test('Highlight Multiline Method Signature', async () => { + const def = await openAndHover(fileHover, 14, 14); + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '14,4', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '14,15', 'End position is incorrect'); + + const lines = normalizeMarkedString(def[0].contents[0]).splitLines(); + assert.equal(lines.length, 3, 'incorrect number of lines'); + assert.equal(lines[0].trim(), 'misc.Thread: class misc.Thread(_Verbose)', 'function signature line #1 is incorrect'); + assert.equal(lines[1].trim(), 'A class that represents a thread of control.', 'function signature line #2 is incorrect'); + + }); + + test('Variable', async () => { + const def = await openAndHover(fileHover, 6, 2); + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(def[0].contents.length, 1, 'Only expected one result'); + const contents = normalizeMarkedString(def[0].contents[0]); + if (contents.indexOf('Random') === -1) { + assert.fail(contents, '', 'Variable type is missing', 'compare'); + } + }); + + test('format().capitalize()', async function () { + // https://github.com/Microsoft/PTVS/issues/3868 + // tslint:disable-next-line:no-invalid-this + this.skip(); + const def = await openAndHover(fileStringFormat, 5, 41); + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(def[0].contents.length, 1, 'Only expected one result'); + const contents = normalizeMarkedString(def[0].contents[0]); + if (contents.indexOf('capitalize') === -1) { + assert.fail(contents, '', '\'capitalize\' is missing', 'compare'); + } + if (contents.indexOf('Return a capitalized version of S') === -1 && + contents.indexOf('Return a copy of the string S with only its first character') === -1) { + assert.fail(contents, '', '\'Return a capitalized version of S/Return a copy of the string S with only its first character\' message missing', 'compare'); + } + }); +}); diff --git a/src/test/definitions/parallel.test.ts b/src/test/definitions/parallel.jedi.test.ts similarity index 87% rename from src/test/definitions/parallel.test.ts rename to src/test/definitions/parallel.jedi.test.ts index 535587ba5e03..627352c60947 100644 --- a/src/test/definitions/parallel.test.ts +++ b/src/test/definitions/parallel.jedi.test.ts @@ -1,44 +1,50 @@ -import * as assert from 'assert'; -import { EOL } from 'os'; -import * as path from 'path'; -import * as vscode from 'vscode'; -import { Hover } from 'vscode'; -import { IS_WINDOWS } from '../../client/common/platform/constants'; -import { closeActiveWindows, initialize } from '../initialize'; -import { normalizeMarkedString } from '../textUtils'; - -const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); -const fileOne = path.join(autoCompPath, 'one.py'); - -suite('Code, Hover Definition and Intellisense', () => { - suiteSetup(initialize); - suiteTeardown(closeActiveWindows); - teardown(closeActiveWindows); - - test('All three together', async () => { - const textDocument = await vscode.workspace.openTextDocument(fileOne); - - let position = new vscode.Position(30, 5); - const hoverDef = await vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); - const codeDef = await vscode.commands.executeCommand('vscode.executeDefinitionProvider', textDocument.uri, position); - position = new vscode.Position(3, 10); - const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - - assert.equal(list!.items.filter(item => item.label === 'api_version').length, 1, 'api_version not found'); - - assert.equal(codeDef!.length, 1, 'Definition length is incorrect'); - const expectedPath = IS_WINDOWS ? fileOne.toUpperCase() : fileOne; - const actualPath = IS_WINDOWS ? codeDef![0].uri.fsPath.toUpperCase() : codeDef![0].uri.fsPath; - assert.equal(actualPath, expectedPath, 'Incorrect file'); - assert.equal(`${codeDef![0].range!.start.line},${codeDef![0].range!.start.character}`, '17,4', 'Start position is incorrect'); - assert.equal(`${codeDef![0].range!.end.line},${codeDef![0].range!.end.character}`, '21,11', 'End position is incorrect'); - - assert.equal(hoverDef!.length, 1, 'Definition length is incorrect'); - assert.equal(`${hoverDef![0].range!.start.line},${hoverDef![0].range!.start.character}`, '30,4', 'Start position is incorrect'); - assert.equal(`${hoverDef![0].range!.end.line},${hoverDef![0].range!.end.character}`, '30,11', 'End position is incorrect'); - assert.equal(hoverDef![0].contents.length, 1, 'Invalid content items'); - // tslint:disable-next-line:prefer-template - const expectedContent = '```python' + EOL + 'def method1()' + EOL + '```' + EOL + 'This is method1'; - assert.equal(normalizeMarkedString(hoverDef![0].contents[0]), expectedContent, 'function signature incorrect'); - }); -}); +import * as assert from 'assert'; +import { EOL } from 'os'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { IS_WINDOWS } from '../../client/common/platform/constants'; +import { IS_ANALYSIS_ENGINE_TEST } from '../constants'; +import { closeActiveWindows, initialize } from '../initialize'; +import { normalizeMarkedString } from '../textUtils'; + +const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); +const fileOne = path.join(autoCompPath, 'one.py'); + +suite('Code, Hover Definition and Intellisense (Jedi)', () => { + suiteSetup(async function () { + if (IS_ANALYSIS_ENGINE_TEST) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + } + await initialize(); + }); + suiteTeardown(closeActiveWindows); + teardown(closeActiveWindows); + + test('All three together', async () => { + const textDocument = await vscode.workspace.openTextDocument(fileOne); + + let position = new vscode.Position(30, 5); + const hoverDef = await vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + const codeDef = await vscode.commands.executeCommand('vscode.executeDefinitionProvider', textDocument.uri, position); + position = new vscode.Position(3, 10); + const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + + assert.equal(list!.items.filter(item => item.label === 'api_version').length, 1, 'api_version not found'); + + assert.equal(codeDef!.length, 1, 'Definition length is incorrect'); + const expectedPath = IS_WINDOWS ? fileOne.toUpperCase() : fileOne; + const actualPath = IS_WINDOWS ? codeDef![0].uri.fsPath.toUpperCase() : codeDef![0].uri.fsPath; + assert.equal(actualPath, expectedPath, 'Incorrect file'); + assert.equal(`${codeDef![0].range!.start.line},${codeDef![0].range!.start.character}`, '17,4', 'Start position is incorrect'); + assert.equal(`${codeDef![0].range!.end.line},${codeDef![0].range!.end.character}`, '21,11', 'End position is incorrect'); + + assert.equal(hoverDef!.length, 1, 'Definition length is incorrect'); + assert.equal(`${hoverDef![0].range!.start.line},${hoverDef![0].range!.start.character}`, '30,4', 'Start position is incorrect'); + assert.equal(`${hoverDef![0].range!.end.line},${hoverDef![0].range!.end.character}`, '30,11', 'End position is incorrect'); + assert.equal(hoverDef![0].contents.length, 1, 'Invalid content items'); + // tslint:disable-next-line:prefer-template + const expectedContent = '```python' + EOL + 'def method1()' + EOL + '```' + EOL + 'This is method1'; + assert.equal(normalizeMarkedString(hoverDef![0].contents[0]), expectedContent, 'function signature incorrect'); + }); +}); diff --git a/src/test/definitions/parallel.ptvs.test.ts b/src/test/definitions/parallel.ptvs.test.ts new file mode 100644 index 000000000000..6740350d374d --- /dev/null +++ b/src/test/definitions/parallel.ptvs.test.ts @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as assert from 'assert'; +import { EOL } from 'os'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { IS_WINDOWS } from '../../client/common/platform/constants'; +import { IS_ANALYSIS_ENGINE_TEST } from '../constants'; +import { closeActiveWindows, initialize } from '../initialize'; +import { normalizeMarkedString } from '../textUtils'; + +const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); +const fileOne = path.join(autoCompPath, 'one.py'); + +suite('Code, Hover Definition and Intellisense (MS Python Code Analysis)', () => { + suiteSetup(async function () { + // https://github.com/Microsoft/vscode-python/issues/1061 + // tslint:disable-next-line:no-invalid-this + this.skip(); + + if (!IS_ANALYSIS_ENGINE_TEST) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + } + await initialize(); + }); + suiteTeardown(closeActiveWindows); + teardown(closeActiveWindows); + + test('All three together', async () => { + const textDocument = await vscode.workspace.openTextDocument(fileOne); + + let position = new vscode.Position(30, 5); + const hoverDef = await vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + const codeDef = await vscode.commands.executeCommand('vscode.executeDefinitionProvider', textDocument.uri, position); + position = new vscode.Position(3, 10); + const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + + assert.equal(list!.items.filter(item => item.label === 'api_version').length, 1, 'api_version not found'); + + assert.equal(codeDef!.length, 1, 'Definition length is incorrect'); + const expectedPath = IS_WINDOWS ? fileOne.toUpperCase() : fileOne; + const actualPath = IS_WINDOWS ? codeDef![0].uri.fsPath.toUpperCase() : codeDef![0].uri.fsPath; + assert.equal(actualPath, expectedPath, 'Incorrect file'); + assert.equal(`${codeDef![0].range!.start.line},${codeDef![0].range!.start.character}`, '17,4', 'Start position is incorrect'); + assert.equal(`${codeDef![0].range!.end.line},${codeDef![0].range!.end.character}`, '21,11', 'End position is incorrect'); + + assert.equal(hoverDef!.length, 1, 'Definition length is incorrect'); + assert.equal(`${hoverDef![0].range!.start.line},${hoverDef![0].range!.start.character}`, '30,4', 'Start position is incorrect'); + assert.equal(`${hoverDef![0].range!.end.line},${hoverDef![0].range!.end.character}`, '30,11', 'End position is incorrect'); + assert.equal(hoverDef![0].contents.length, 1, 'Invalid content items'); + // tslint:disable-next-line:prefer-template + const expectedContent = '```python' + EOL + 'def method1()' + EOL + '```' + EOL + 'This is method1'; + assert.equal(normalizeMarkedString(hoverDef![0].contents[0]), expectedContent, 'function signature incorrect'); + }); +}); diff --git a/src/test/linters/lint.commands.test.ts b/src/test/linters/lint.commands.test.ts index d843c7128f4b..5cac8d6f997f 100644 --- a/src/test/linters/lint.commands.test.ts +++ b/src/test/linters/lint.commands.test.ts @@ -36,6 +36,7 @@ suite('Linting - Linter Selector', () => { const cont = new Container(); const serviceManager = new ServiceManager(cont); serviceContainer = new ServiceContainer(cont); + serviceManager.addSingletonInstance(IServiceContainer, serviceContainer); appShell = TypeMoq.Mock.ofType(); serviceManager.addSingleton(IConfigurationService, ConfigurationService); @@ -74,7 +75,7 @@ suite('Linting - Linter Selector', () => { }); test('Run linter command', async () => { - commands.runLinting(); + await commands.runLinting(); engine.verify(p => p.lintOpenPythonFiles(), TypeMoq.Times.once()); }); diff --git a/src/test/linters/lint.manager.test.ts b/src/test/linters/lint.manager.test.ts index 68f1e20e39c3..06bbd289d4ba 100644 --- a/src/test/linters/lint.manager.test.ts +++ b/src/test/linters/lint.manager.test.ts @@ -8,6 +8,7 @@ import { EnumEx } from '../../client/common/enumUtils'; import { IConfigurationService, ILintingSettings, IPythonSettings, Product } from '../../client/common/types'; import { ServiceContainer } from '../../client/ioc/container'; import { ServiceManager } from '../../client/ioc/serviceManager'; +import { IServiceContainer } from '../../client/ioc/types'; import { LinterManager } from '../../client/linters/linterManager'; import { ILinterManager, LinterId } from '../../client/linters/types'; import { initialize } from '../initialize'; @@ -23,6 +24,7 @@ suite('Linting - Manager', () => { const cont = new Container(); const serviceManager = new ServiceManager(cont); const serviceContainer = new ServiceContainer(cont); + serviceManager.addSingletonInstance(IServiceContainer, serviceContainer); serviceManager.addSingleton(IConfigurationService, ConfigurationService); configService = serviceManager.get(IConfigurationService); diff --git a/src/test/pythonFiles/autocomp/four.py b/src/test/pythonFiles/autocomp/four.py index f67f78af0856..470338f71157 100644 --- a/src/test/pythonFiles/autocomp/four.py +++ b/src/test/pythonFiles/autocomp/four.py @@ -1,4 +1,4 @@ -# -*- coding:utf-8 -*- +# -*- coding: utf-8 -*- # pylint: disable=E0401, W0512 import os diff --git a/src/test/pythonFiles/definition/four.py b/src/test/pythonFiles/definition/four.py index f67f78af0856..470338f71157 100644 --- a/src/test/pythonFiles/definition/four.py +++ b/src/test/pythonFiles/definition/four.py @@ -1,4 +1,4 @@ -# -*- coding:utf-8 -*- +# -*- coding: utf-8 -*- # pylint: disable=E0401, W0512 import os diff --git a/src/test/signature/signature.test.ts b/src/test/signature/signature.jedi.test.ts similarity index 95% rename from src/test/signature/signature.test.ts rename to src/test/signature/signature.jedi.test.ts index a4d002e4e750..1ab80cce964d 100644 --- a/src/test/signature/signature.test.ts +++ b/src/test/signature/signature.jedi.test.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import * as path from 'path'; import * as vscode from 'vscode'; import { rootWorkspaceUri } from '../common'; +import { IS_ANALYSIS_ENGINE_TEST } from '../constants'; import { closeActiveWindows, initialize, initializeTest } from '../initialize'; import { UnitTestIocContainer } from '../unittests/serviceRegistry'; @@ -20,10 +21,14 @@ class SignatureHelpResult { } // tslint:disable-next-line:max-func-body-length -suite('Signatures', () => { +suite('Signatures (Jedi)', () => { let isPython2: boolean; let ioc: UnitTestIocContainer; - suiteSetup(async () => { + suiteSetup(async function () { + if (IS_ANALYSIS_ENGINE_TEST) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + } await initialize(); initializeDI(); isPython2 = await ioc.getPythonMajorVersion(rootWorkspaceUri) === 2; diff --git a/src/test/signature/signature.ptvs.test.ts b/src/test/signature/signature.ptvs.test.ts new file mode 100644 index 000000000000..68720e33cde1 --- /dev/null +++ b/src/test/signature/signature.ptvs.test.ts @@ -0,0 +1,146 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as assert from 'assert'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { rootWorkspaceUri } from '../common'; +import { IS_ANALYSIS_ENGINE_TEST } from '../constants'; +import { closeActiveWindows, initialize, initializeTest } from '../initialize'; +import { UnitTestIocContainer } from '../unittests/serviceRegistry'; + +const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'signature'); + +class SignatureHelpResult { + constructor( + public line: number, + public index: number, + public signaturesCount: number, + public activeParameter: number, + public parameterName: string | null) { } +} + +// tslint:disable-next-line:max-func-body-length +suite('Signatures (Analysis Engine)', () => { + let isPython2: boolean; + let ioc: UnitTestIocContainer; + suiteSetup(async function () { + if (!IS_ANALYSIS_ENGINE_TEST) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + } + await initialize(); + initializeDI(); + isPython2 = await ioc.getPythonMajorVersion(rootWorkspaceUri) === 2; + }); + setup(initializeTest); + suiteTeardown(closeActiveWindows); + teardown(async () => { + await closeActiveWindows(); + ioc.dispose(); + }); + function initializeDI() { + ioc = new UnitTestIocContainer(); + ioc.registerCommonTypes(); + ioc.registerVariableTypes(); + ioc.registerProcessTypes(); + } + + test('For ctor', async () => { + const expected = [ + new SignatureHelpResult(5, 11, 1, -1, null), + new SignatureHelpResult(5, 12, 1, 0, 'name'), + new SignatureHelpResult(5, 13, 1, 0, 'name'), + new SignatureHelpResult(5, 14, 1, 0, 'name'), + new SignatureHelpResult(5, 15, 1, 0, 'name'), + new SignatureHelpResult(5, 16, 1, 0, 'name'), + new SignatureHelpResult(5, 17, 1, 0, 'name'), + new SignatureHelpResult(5, 18, 1, 1, 'age'), + new SignatureHelpResult(5, 19, 1, 1, 'age'), + new SignatureHelpResult(5, 20, 1, -1, null) + ]; + + const document = await openDocument(path.join(autoCompPath, 'classCtor.py')); + for (let i = 0; i < expected.length; i += 1) { + await checkSignature(expected[i], document!.uri, i); + } + }); + + test('For intrinsic', async () => { + const expected = [ + new SignatureHelpResult(0, 0, 1, -1, null), + new SignatureHelpResult(0, 1, 1, -1, null), + new SignatureHelpResult(0, 2, 1, -1, null), + new SignatureHelpResult(0, 3, 1, -1, null), + new SignatureHelpResult(0, 4, 1, -1, null), + new SignatureHelpResult(0, 5, 1, -1, null), + new SignatureHelpResult(0, 6, 1, 0, 'stop'), + new SignatureHelpResult(0, 7, 1, 0, 'stop') + // https://github.com/Microsoft/PTVS/issues/3869 + // new SignatureHelpResult(0, 8, 1, 1, 'stop'), + // new SignatureHelpResult(0, 9, 1, 1, 'stop'), + // new SignatureHelpResult(0, 10, 1, 1, 'stop'), + // new SignatureHelpResult(0, 11, 1, 2, 'step'), + // new SignatureHelpResult(1, 0, 1, 2, 'step') + ]; + + const document = await openDocument(path.join(autoCompPath, 'basicSig.py')); + for (let i = 0; i < expected.length; i += 1) { + await checkSignature(expected[i], document!.uri, i); + } + }); + + test('For ellipsis', async function () { + if (isPython2) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + return; + } + const expected = [ + new SignatureHelpResult(0, 5, 0, 0, null), + new SignatureHelpResult(0, 6, 1, 0, 'value'), + new SignatureHelpResult(0, 7, 1, 0, 'value'), + new SignatureHelpResult(0, 8, 1, 1, '...'), + new SignatureHelpResult(0, 9, 1, 1, '...'), + new SignatureHelpResult(0, 10, 1, 1, '...'), + new SignatureHelpResult(0, 11, 1, 2, 'sep'), + new SignatureHelpResult(0, 12, 1, 2, 'sep') + ]; + + const document = await openDocument(path.join(autoCompPath, 'ellipsis.py')); + for (let i = 0; i < expected.length; i += 1) { + await checkSignature(expected[i], document!.uri, i); + } + }); + + test('For pow', async () => { + let expected: SignatureHelpResult; + if (isPython2) { + expected = new SignatureHelpResult(0, 4, 1, 0, 'x'); + } else { + expected = new SignatureHelpResult(0, 4, 1, 0, null); + } + + const document = await openDocument(path.join(autoCompPath, 'noSigPy3.py')); + await checkSignature(expected, document!.uri, 0); + }); +}); + +async function openDocument(documentPath: string): Promise { + const document = await vscode.workspace.openTextDocument(documentPath); + await vscode.window.showTextDocument(document!); + return document; +} + +async function checkSignature(expected: SignatureHelpResult, uri: vscode.Uri, caseIndex: number) { + const position = new vscode.Position(expected.line, expected.index); + const actual = await vscode.commands.executeCommand('vscode.executeSignatureHelpProvider', uri, position); + assert.equal(actual!.signatures.length, expected.signaturesCount, `Signature count does not match, case ${caseIndex}`); + if (expected.signaturesCount > 0) { + assert.equal(actual!.activeParameter, expected.activeParameter, `Parameter index does not match, case ${caseIndex}`); + if (expected.parameterName) { + const parameter = actual!.signatures[0].parameters[expected.activeParameter]; + assert.equal(parameter.label, expected.parameterName, `Parameter name is incorrect, case ${caseIndex}`); + } + } +} diff --git a/vscode-python-signing.csproj b/vscode-python-signing.csproj new file mode 100644 index 000000000000..7fb333c4b277 --- /dev/null +++ b/vscode-python-signing.csproj @@ -0,0 +1,20 @@ + + + netcoreapp2.0 + + + + + + $(OutputPath)\python-$(Branch).vsix + $(UserProfile)\AppData\Roaming\npm\vsce + + + + + + + VsixSHA2 + + + From 9c8f437b8b39f6e9fdce1ed86d813ec35936b5bc Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 4 Apr 2018 09:55:17 -0700 Subject: [PATCH 091/433] Unit Tests to ensure capabilites in PTVSD match those in debug adapter (#1225) * split compilation for faster compilation on windows * :hammer: rename linting (else auto fix is not available) [skip ci] * :white_check_mark: tests * Fixes #1143 * Also updated debugger (npm) packages. --- package.json | 4 +- src/client/debugger/mainV2.ts | 4 + src/test/debugger/capabilities.test.ts | 103 +++++++++++++++++++++++++ yarn.lock | 23 ++++-- 4 files changed, 125 insertions(+), 9 deletions(-) create mode 100644 src/test/debugger/capabilities.test.ts diff --git a/package.json b/package.json index 81128aa16bb6..5de98e8a3bcb 100644 --- a/package.json +++ b/package.json @@ -1768,8 +1768,8 @@ "unicode": "^10.0.0", "untildify": "^3.0.2", "unzip": "^0.1.11", - "vscode-debugadapter": "^1.0.1", - "vscode-debugprotocol": "^1.0.1", + "vscode-debugadapter": "^1.28.0", + "vscode-debugprotocol": "^1.28.0", "vscode-extension-telemetry": "^0.0.14", "vscode-languageclient": "^3.1.0", "vscode-languageserver": "^3.1.0", diff --git a/src/client/debugger/mainV2.ts b/src/client/debugger/mainV2.ts index 53f4d60d17cd..bcb9a9d26e8c 100644 --- a/src/client/debugger/mainV2.ts +++ b/src/client/debugger/mainV2.ts @@ -69,6 +69,10 @@ export class PythonDebugger extends DebugSession { body.supportsConditionalBreakpoints = true; body.supportsSetVariable = true; body.supportsExceptionOptions = true; + body.supportsEvaluateForHovers = true; + body.supportsModulesRequest = true; + body.supportsValueFormattingOptions = true; + body.supportsSetExpression = true; body.exceptionBreakpointFilters = [ { filter: 'raised', diff --git a/src/test/debugger/capabilities.test.ts b/src/test/debugger/capabilities.test.ts new file mode 100644 index 000000000000..8052b5f6f2dc --- /dev/null +++ b/src/test/debugger/capabilities.test.ts @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:no-suspicious-comment max-func-body-length no-invalid-this no-var-requires no-require-imports no-any + +import { expect } from 'chai'; +import { ChildProcess, spawn } from 'child_process'; +import * as getFreePort from 'get-port'; +import { connect, Socket } from 'net'; +import * as path from 'path'; +import { PassThrough } from 'stream'; +import { Message } from 'vscode-debugadapter/lib/messages'; +import { DebugProtocol } from 'vscode-debugprotocol'; +import { EXTENSION_ROOT_DIR } from '../../client/common/constants'; +import { createDeferred } from '../../client/common/helpers'; +import { ProtocolParser } from '../../client/debugger/Common/protocolParser'; +import { ProtocolMessageWriter } from '../../client/debugger/Common/protocolWriter'; +import { PythonDebugger } from '../../client/debugger/mainV2'; +import { sleep } from '../common'; +import { IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; + +class Request extends Message implements DebugProtocol.InitializeRequest { + // tslint:disable-next-line:no-banned-terms + public arguments: any; + constructor(public command: string, args: any) { + super('request'); + this.arguments = args; + } +} + +suite('Debugging - Capabilities', () => { + let disposables: { dispose?: Function; destroy?: Function }[]; + let proc: ChildProcess; + setup(async function () { + if (!IS_MULTI_ROOT_TEST || !TEST_DEBUGGER) { + this.skip(); + } + disposables = []; + }); + teardown(() => { + disposables.forEach(disposable => { + try { + disposable.dispose!(); + // tslint:disable-next-line:no-empty + } catch { } + try { + disposable.destroy!(); + // tslint:disable-next-line:no-empty + } catch { } + }); + try { + proc.kill(); + // tslint:disable-next-line:no-empty + } catch { } + }); + test('Compare capabilities', async () => { + const protocolWriter = new ProtocolMessageWriter(); + const initializeRequest: DebugProtocol.InitializeRequest = new Request('initialize', { pathFormat: 'path' }); + + const debugClient = new PythonDebugger(undefined as any); + const inStream = new PassThrough(); + const outStream = new PassThrough(); + disposables.push(inStream); + disposables.push(outStream); + debugClient.start(inStream, outStream); + const debugClientProtocolParser = new ProtocolParser(); + debugClientProtocolParser.connect(outStream); + disposables.push(debugClientProtocolParser); + const expectedResponsePromise = new Promise(resolve => debugClientProtocolParser.once('response_initialize', resolve)); + protocolWriter.write(inStream, initializeRequest); + const expectedResponse = await expectedResponsePromise; + + const host = 'localhost'; + const port = await getFreePort({ host }); + const env = { ...process.env }; + env.PYTHONPATH = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'experimental', 'ptvsd'); + proc = spawn('python', ['-m', 'ptvsd', '--server', '--port', `${port}`, '--file', 'someFile.py'], { cwd: __dirname, env }); + // Wait for the socket server to start. + // Keep trying till we timeout. + let socket: Socket | undefined; + for (let index = 0; index < 1000; index += 1) { + try { + const connected = createDeferred(); + socket = connect({ port, host }, () => connected.resolve(socket)); + socket.on('error', connected.reject.bind(connected)); + await connected.promise; + break; + } catch { + await sleep(500); + } + } + const protocolParser = new ProtocolParser(); + protocolParser.connect(socket!); + disposables.push(protocolParser); + const actualResponsePromise = new Promise(resolve => protocolParser.once('response_initialize', resolve)); + protocolWriter.write(socket!, initializeRequest); + const actualResponse = await actualResponsePromise; + + expect(actualResponse.body).to.deep.equal(expectedResponse.body); + }); +}); diff --git a/yarn.lock b/yarn.lock index 19893afaaa94..7027a7bdcfbc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1826,7 +1826,7 @@ gulp-untar@^0.0.6: tar "^2.2.1" through2 "~2.0.3" -gulp-util@^3.0.0, gulp-util@^3.0.7, gulp-util@~3.0.0, gulp-util@~3.0.7, gulp-util@~3.0.8: +gulp-util@^3.0.0, gulp-util@^3.0.7, gulp-util@~3.0.8: version "3.0.8" resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f" dependencies: @@ -3904,7 +3904,7 @@ ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" -retyped-diff-match-patch-tsd-ambient@^1.0.0-1: +retyped-diff-match-patch-tsd-ambient@^1.0.0-0: version "1.0.0-1" resolved "https://registry.yarnpkg.com/retyped-diff-match-patch-tsd-ambient/-/retyped-diff-match-patch-tsd-ambient-1.0.0-1.tgz#26482bf4915c7ed9f8300bb5cbec48fd4ff5bc62" @@ -4835,16 +4835,21 @@ vscode-debugadapter-testsupport@^1.27.0: dependencies: vscode-debugprotocol "1.27.0" -vscode-debugadapter@^1.0.1: - version "1.27.0" - resolved "https://registry.yarnpkg.com/vscode-debugadapter/-/vscode-debugadapter-1.27.0.tgz#0688f7d03d7568efd653003ecdb402b7ba37231e" +vscode-debugadapter@^1.28.0: + version "1.28.0" + resolved "https://registry.yarnpkg.com/vscode-debugadapter/-/vscode-debugadapter-1.28.0.tgz#ebd6653e3f41db324d9547595375571a8732e966" dependencies: - vscode-debugprotocol "1.27.0" + vscode-debugprotocol "1.28.0" + vscode-uri "1.0.1" -vscode-debugprotocol@1.27.0, vscode-debugprotocol@^1.0.1: +vscode-debugprotocol@1.27.0: version "1.27.0" resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.27.0.tgz#735a43a3cc1235fe587c0ef93fe4e328def7b17c" +vscode-debugprotocol@1.28.0, vscode-debugprotocol@^1.28.0: + version "1.28.0" + resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.28.0.tgz#b9fb97c3fb2dadbec78e5c1619ff12bf741ce406" + vscode-extension-telemetry@^0.0.14: version "0.0.14" resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.14.tgz#17454705b6bb8757351b955d812923f02ee895bf" @@ -4879,6 +4884,10 @@ vscode-languageserver@^3.1.0: vscode-languageserver-protocol "3.5.1" vscode-uri "^1.0.1" +vscode-uri@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-1.0.1.tgz#11a86befeac3c4aa3ec08623651a3c81a6d0bbc8" + vscode-uri@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-1.0.3.tgz#631bdbf716dccab0e65291a8dc25c23232085a52" From 16cce8e72355d8dead14ebd40e5cb2e131a71fa3 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 4 Apr 2018 11:18:17 -0700 Subject: [PATCH 092/433] Parallelize unit tests on CI server (Travis) (#1251) Fixes #1247 Temporary work around for #1250 (didn't add a news entry for the work around) --- .travis.yml | 26 ++++++++- appveyor.yml | 33 +++++++++-- news/3 Code Health/1247.md | 1 + src/test/debugger/common/constants.ts | 2 +- src/test/debugger/misc.test.ts | 58 +++++++++++++++++-- src/test/pythonFiles/debugging/multiThread.py | 2 +- 6 files changed, 105 insertions(+), 17 deletions(-) create mode 100644 news/3 Code Health/1247.md diff --git a/.travis.yml b/.travis.yml index 8c3fb6e4924f..fee4079ea762 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,8 +5,22 @@ matrix: include: - os: linux python: "2.7" + env: DEBUGGER_TEST=true + - os: linux + python: "2.7" + env: SINGLE_WORKSPACE_TEST=true + - os: linux + python: "2.7" + env: MULTIROOT_WORKSPACE_TEST=true + - os: linux + python: "3.6-dev" + env: DEBUGGER_TEST=true - os: linux python: "3.6-dev" + env: SINGLE_WORKSPACE_TEST=true + - os: linux + python: "3.6-dev" + env: MULTIROOT_WORKSPACE_TEST=true before_install: | if [ $TRAVIS_OS_NAME == "linux" ]; then export CXX="g++-4.9" CC="gcc-4.9" DISPLAY=:99.0; @@ -30,7 +44,9 @@ script: - yarn run clean - yarn run vscode:prepublish - yarn run cover:enable - - yarn run testDebugger --silent + - if [ $DEBUGGER_TEST == "true" ]; then + yarn run testDebugger --silent; + fi - yarn run debugger-coverage - if [ $TRAVIS_UPLOAD_COVERAGE == "true" ]; then bash <(curl -s https://codecov.io/bash); @@ -44,14 +60,18 @@ script: - yarn run clean - yarn run vscode:prepublish - yarn run cover:enable - - yarn run testSingleWorkspace --silent + - if [ $SINGLE_WORKSPACE_TEST == "true" ]; then + yarn run testSingleWorkspace --silent; + fi - if [ $TRAVIS_UPLOAD_COVERAGE == "true" ]; then bash <(curl -s https://codecov.io/bash); fi - yarn run clean - yarn run vscode:prepublish - yarn run cover:enable - - yarn run testMultiWorkspace --silent + - if [ $MULTIROOT_WORKSPACE_TEST == "true" ]; then + yarn run testMultiWorkspace --silent; + fi - if [ $TRAVIS_UPLOAD_COVERAGE == "true" ]; then bash <(curl -s https://codecov.io/bash); fi diff --git a/appveyor.yml b/appveyor.yml index 3b80a7267a18..99a980c431cb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,6 +8,25 @@ environment: PYTHON_ARCH: "32" nodejs_version: "8.9.1" APPVEYOR: "true" + DEBUGGER_TEST: "true" + - PYTHON: "C:\\Python36" + PYTHON_VERSION: "3.6.3" + PYTHON_ARCH: "32" + nodejs_version: "8.9.1" + APPVEYOR: "true" + SINGLE_WORKSPACE_TEST: "true" + - PYTHON: "C:\\Python36" + PYTHON_VERSION: "3.6.3" + PYTHON_ARCH: "32" + nodejs_version: "8.9.1" + APPVEYOR: "true" + MULTIROOT_WORKSPACE_TEST: "true" + - PYTHON: "C:\\Python36" + PYTHON_VERSION: "3.6.3" + PYTHON_ARCH: "32" + nodejs_version: "8.9.1" + APPVEYOR: "true" + ANALYSIS_TEST: "true" init: - "ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH%" @@ -37,9 +56,11 @@ build: off test_script: - yarn run clean - yarn run vscode:prepublish - - yarn run testDebugger --silent - - yarn run testSingleWorkspace --silent - - yarn run testMultiWorkspace --silent - # - yarn run testAnalysisEngine --silent - - + - if [%DEBUGGER_TEST%]==[true] ( + yarn run testDebugger --silent) + - if [%SINGLE_WORKSPACE_TEST%]==[true] ( + yarn run testSingleWorkspace --silent) + - if [%MULTIROOT_WORKSPACE_TEST%]==[true] ( + yarn run testMultiWorkspace --silent) + # - if [%ANALYSIS_TEST%]==[true] ( + # yarn run testAnalysisEngine --silent) diff --git a/news/3 Code Health/1247.md b/news/3 Code Health/1247.md new file mode 100644 index 000000000000..458a70c5d1dc --- /dev/null +++ b/news/3 Code Health/1247.md @@ -0,0 +1 @@ +Parallelize jobs (unit tests) on CI server. diff --git a/src/test/debugger/common/constants.ts b/src/test/debugger/common/constants.ts index 9be293352e34..a9bcc64f1a24 100644 --- a/src/test/debugger/common/constants.ts +++ b/src/test/debugger/common/constants.ts @@ -4,4 +4,4 @@ 'use strict'; // Sometimes PTVSD can take a while for thread & other events to be reported. -export const DEBUGGER_TIMEOUT = 10000; +export const DEBUGGER_TIMEOUT = 20000; diff --git a/src/test/debugger/misc.test.ts b/src/test/debugger/misc.test.ts index 90fc5669447f..05b67f77898f 100644 --- a/src/test/debugger/misc.test.ts +++ b/src/test/debugger/misc.test.ts @@ -3,8 +3,7 @@ // tslint:disable:no-suspicious-comment max-func-body-length no-invalid-this no-var-requires no-require-imports no-any -import { expect, use } from 'chai'; -import * as chaiAsPromised from 'chai-as-promised'; +import { expect } from 'chai'; import * as path from 'path'; import { ThreadEvent } from 'vscode-debugadapter'; import { DebugClient } from 'vscode-debugadapter-testsupport'; @@ -22,8 +21,6 @@ import { DebugClientEx } from './debugClient'; const isProcessRunning = require('is-running') as (number) => boolean; -use(chaiAsPromised); - const debugFilesPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'debugging'); const DEBUG_ADAPTER = path.join(__dirname, '..', '..', 'client', 'debugger', 'Main.js'); @@ -445,12 +442,20 @@ let testCounter = 0; const pauseLocation = { path: path.join(debugFilesPath, 'sample3WithEx.py'), line: 5 }; await debugClient.assertStoppedLocation('exception', pauseLocation); }); - test('Test multi-threaded debugging', async () => { + test('Test multi-threaded debugging', async function () { + if (debuggerType !== 'python') { + // See GitHub issue #1250 + this.skip(); + return; + } await Promise.all([ debugClient.configurationSequence(), debugClient.launch(buildLauncArgs('multiThread.py', false)), debugClient.waitForEvent('initialized') ]); + + // Add a delay for debugger to start (sometimes it takes a long time for new debugger to break). + await sleep(3000); const pythonFile = path.join(debugFilesPath, 'multiThread.py'); const breakpointLocation = { path: pythonFile, column: 1, line: 11 }; await debugClient.setBreakpointsRequest({ @@ -459,8 +464,49 @@ let testCounter = 0; source: { path: breakpointLocation.path } }); - // hit breakpoint. await debugClient.assertStoppedLocation('breakpoint', breakpointLocation); + const threads = await debugClient.threadsRequest(); + expect(threads.body.threads).of.lengthOf(2, 'incorrect number of threads'); + for (const thread of threads.body.threads) { + expect(thread.id).to.be.lessThan(MAX_SIGNED_INT32 + 1, 'ThreadId is not an integer'); + } + }); + test('Test multi-threaded debugging', async function () { + this.timeout(30000); + await Promise.all([ + debugClient.launch(buildLauncArgs('multiThread.py', false)), + debugClient.waitForEvent('initialized') + ]); + + const pythonFile = path.join(debugFilesPath, 'multiThread.py'); + const breakpointLocation = { path: pythonFile, column: 1, line: 11 }; + const breakpointRequestArgs = { + lines: [breakpointLocation.line], + breakpoints: [{ line: breakpointLocation.line, column: breakpointLocation.column }], + source: { path: breakpointLocation.path } + }; + + function waitForStoppedEventFromTwoThreads() { + return new Promise((resolve, reject) => { + let numberOfStops = 0; + debugClient.addListener('stopped', (event: DebugProtocol.StoppedEvent) => { + numberOfStops += 1; + if (numberOfStops < 2) { + return; + } + resolve(event); + }); + setTimeout(() => reject(new Error('Timeout waiting for two threads to stop at breakpoint')), DEBUGGER_TIMEOUT); + }); + } + + await Promise.all([ + debugClient.setBreakpointsRequest(breakpointRequestArgs), + debugClient.setExceptionBreakpointsRequest({ filters: [] }), + debugClient.configurationDoneRequest(), + waitForStoppedEventFromTwoThreads(), + debugClient.assertStoppedLocation('breakpoint', breakpointLocation) + ]); const threads = await debugClient.threadsRequest(); expect(threads.body.threads).of.lengthOf(2, 'incorrect number of threads'); diff --git a/src/test/pythonFiles/debugging/multiThread.py b/src/test/pythonFiles/debugging/multiThread.py index 707f2568a2da..588971ffb502 100644 --- a/src/test/pythonFiles/debugging/multiThread.py +++ b/src/test/pythonFiles/debugging/multiThread.py @@ -4,7 +4,7 @@ def bar(): time.sleep(2) - print("abcdef") + print('bar') def foo(x): while True: From 22712a708abd10c08f76c00f505e00f21139cf4f Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 4 Apr 2018 12:29:40 -0700 Subject: [PATCH 093/433] Add support for remote debugging with experimental debugger (#1230) * Fixes #1229 * Fixes #1265 --- .vscode/settings.json | 2 +- news/1 Enhancements/1229.md | 2 + package.json | 69 ++++++ src/client/debugger/Common/Contracts.ts | 1 + .../DebugClients/RemoteDebugClient.ts | 14 +- .../DebugServers/RemoteDebugServerv2.ts | 51 +++++ .../debugger/configProviders/baseProvider.ts | 61 ++++-- .../configProviders/pythonV2Provider.ts | 16 +- src/client/debugger/mainV2.ts | 121 ++++++----- src/test/debugger/attach.ptvsd.test.ts | 115 ++++++++++ .../configProvider/provider.attach.test.ts | 197 ++++++++++++++++++ src/test/debugger/utils.ts | 92 ++++++++ .../remoteDebugger-start-with-ptvsd.py | 14 ++ 13 files changed, 677 insertions(+), 78 deletions(-) create mode 100644 news/1 Enhancements/1229.md create mode 100644 src/client/debugger/DebugServers/RemoteDebugServerv2.ts create mode 100644 src/test/debugger/attach.ptvsd.test.ts create mode 100644 src/test/debugger/configProvider/provider.attach.test.ts create mode 100644 src/test/debugger/utils.ts create mode 100644 src/testMultiRootWkspc/workspace5/remoteDebugger-start-with-ptvsd.py diff --git a/.vscode/settings.json b/.vscode/settings.json index e359168fd3a0..311167344456 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,7 +14,7 @@ "coverage": true }, "typescript.tsdk": "./node_modules/typescript/lib", // we want to use the TS server from our node_modules folder to control its version - "tslint.enable": true, + "tslint.enable": true, // We will run our own linting in gulp (& git commit hooks), else tslint extension just complains about unmodified files "python.linting.enabled": false, "python.unitTest.promptToConfigure": false, "python.workspaceSymbols.enabled": false, diff --git a/news/1 Enhancements/1229.md b/news/1 Enhancements/1229.md new file mode 100644 index 000000000000..1367edb3bbcc --- /dev/null +++ b/news/1 Enhancements/1229.md @@ -0,0 +1,2 @@ +Add prelimnary support for remote debugging using the experimental debugger. +Attach to a Python program started using the command `python -m ptvsd --server --port 9091 --file pythonFile.py` \ No newline at end of file diff --git a/package.json b/package.json index 5de98e8a3bcb..9a589d160676 100644 --- a/package.json +++ b/package.json @@ -874,6 +874,19 @@ "Pyramid" ] } + }, + { + "label": "Python Experimental: Attach", + "description": "%python.snippet.launch.attach.description%", + "body": { + "name": "Attach (Remote Debug)", + "type": "pythonExperimental", + "request": "attach", + "localRoot": "^\"\\${workspaceFolder}\"", + "remoteRoot": "^\"\\${workspaceFolder}\"", + "port": 3000, + "host": "localhost" + } } ], "configurationAttributes": { @@ -963,6 +976,53 @@ "default": false } } + }, + "attach": { + "required": [ + "port", + "remoteRoot" + ], + "properties": { + "localRoot": { + "type": "string", + "description": "Local source root that corrresponds to the 'remoteRoot'.", + "default": "${workspaceFolder}" + }, + "remoteRoot": { + "type": "string", + "description": "The source root of the remote host.", + "default": "" + }, + "port": { + "type": "number", + "description": "Debug port to attach", + "default": 0 + }, + "host": { + "type": "string", + "description": "IP Address of the of remote server (default is localhost or use 127.0.0.1).", + "default": "localhost" + }, + "debugOptions": { + "type": "array", + "description": "Advanced options, view read me for further details.", + "items": { + "type": "string", + "enum": [ + "RedirectOutput", + "DebugStdLib", + "Django", + "Jinja" + ] + }, + "default": [] + }, + "logToFile": { + "type": "boolean", + "description": "Enable logging of debugger events to a log file.", + "default": false + } + } } }, "initialConfigurations": [ @@ -973,6 +1033,15 @@ "program": "${file}", "console": "integratedTerminal" }, + { + "name": "Python Experimental: Attach", + "type": "pythonExperimental", + "request": "pythonExperimental", + "localRoot": "${workspaceFolder}", + "remoteRoot": "${workspaceFolder}", + "port": 3000, + "host": "localhost" + }, { "name": "Python Experimental: Django", "type": "pythonExperimental", diff --git a/src/client/debugger/Common/Contracts.ts b/src/client/debugger/Common/Contracts.ts index d2b0774845d8..30011ef88e6a 100644 --- a/src/client/debugger/Common/Contracts.ts +++ b/src/client/debugger/Common/Contracts.ts @@ -79,6 +79,7 @@ export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArgum } export interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments { + type?: DebuggerType; /** An absolute path to local directory with source. */ debugOptions?: string[]; localRoot?: string; diff --git a/src/client/debugger/DebugClients/RemoteDebugClient.ts b/src/client/debugger/DebugClients/RemoteDebugClient.ts index 45aec00388a4..a7bee8e9f578 100644 --- a/src/client/debugger/DebugClients/RemoteDebugClient.ts +++ b/src/client/debugger/DebugClients/RemoteDebugClient.ts @@ -2,19 +2,25 @@ import { DebugSession } from 'vscode-debugadapter'; import { AttachRequestArguments, IPythonProcess } from '../Common/Contracts'; import { BaseDebugServer } from '../DebugServers/BaseDebugServer'; import { RemoteDebugServer } from '../DebugServers/RemoteDebugServer'; +import { RemoteDebugServerV2 } from '../DebugServers/RemoteDebugServerv2'; import { DebugClient, DebugType } from './DebugClient'; export class RemoteDebugClient extends DebugClient { - private pythonProcess: IPythonProcess; + private pythonProcess?: IPythonProcess; private debugServer?: BaseDebugServer; // tslint:disable-next-line:no-any - constructor(args: any, debugSession: DebugSession) { + constructor(args: AttachRequestArguments, debugSession: DebugSession) { super(args, debugSession); } public CreateDebugServer(pythonProcess?: IPythonProcess): BaseDebugServer { - this.pythonProcess = pythonProcess!; - this.debugServer = new RemoteDebugServer(this.debugSession, this.pythonProcess!, this.args); + if (this.args.type === 'pythonExperimental') { + // tslint:disable-next-line:no-any + this.debugServer = new RemoteDebugServerV2(this.debugSession, undefined as any, this.args); + } else { + this.pythonProcess = pythonProcess!; + this.debugServer = new RemoteDebugServer(this.debugSession, this.pythonProcess!, this.args); + } return this.debugServer!; } public get DebugType(): DebugType { diff --git a/src/client/debugger/DebugServers/RemoteDebugServerv2.ts b/src/client/debugger/DebugServers/RemoteDebugServerv2.ts new file mode 100644 index 000000000000..be400c12c5ea --- /dev/null +++ b/src/client/debugger/DebugServers/RemoteDebugServerv2.ts @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { connect, Socket } from 'net'; +import { DebugSession } from 'vscode-debugadapter'; +import { AttachRequestArguments, IDebugServer, IPythonProcess } from '../Common/Contracts'; +import { BaseDebugServer } from './BaseDebugServer'; + +export class RemoteDebugServerV2 extends BaseDebugServer { + private args: AttachRequestArguments; + private socket?: Socket; + constructor(debugSession: DebugSession, pythonProcess: IPythonProcess, args: AttachRequestArguments) { + super(debugSession, pythonProcess); + this.args = args; + } + + public Stop() { + if (this.socket) { + this.socket.destroy(); + } + } + public Start(): Promise { + return new Promise((resolve, reject) => { + const port = this.args.port!; + const options = { port }; + if (typeof this.args.host === 'string' && this.args.host.length > 0) { + // tslint:disable-next-line:no-any + (options).host = this.args.host; + } + try { + let connected = false; + const socket = connect(options, () => { + connected = true; + this.socket = socket; + this.clientSocket.resolve(socket); + resolve(options); + }); + socket.on('error', ex => { + if (connected) { + return; + } + reject(ex); + }); + } catch (ex) { + reject(ex); + } + }); + } +} diff --git a/src/client/debugger/configProviders/baseProvider.ts b/src/client/debugger/configProviders/baseProvider.ts index f7786eca9b70..849994a735b1 100644 --- a/src/client/debugger/configProviders/baseProvider.ts +++ b/src/client/debugger/configProviders/baseProvider.ts @@ -11,34 +11,55 @@ import { PythonLanguage } from '../../common/constants'; import { IFileSystem, IPlatformService } from '../../common/platform/types'; import { IConfigurationService } from '../../common/types'; import { IServiceContainer } from '../../ioc/types'; -import { DebuggerType, DebugOptions, LaunchRequestArguments } from '../Common/Contracts'; +import { AttachRequestArguments, DebuggerType, DebugOptions, LaunchRequestArguments } from '../Common/Contracts'; // tslint:disable:no-invalid-template-strings -export type PythonDebugConfiguration = DebugConfiguration & LaunchRequestArguments; +export type PythonLaunchDebugConfiguration = DebugConfiguration & LaunchRequestArguments; +export type PythonAttachDebugConfiguration = DebugConfiguration & AttachRequestArguments; @injectable() export abstract class BaseConfigurationProvider implements DebugConfigurationProvider { constructor(@unmanaged() public debugType: DebuggerType, protected serviceContainer: IServiceContainer) { } public resolveDebugConfiguration(folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, token?: CancellationToken): ProviderResult { - const config = debugConfiguration as PythonDebugConfiguration; - const numberOfSettings = Object.keys(config); - const workspaceFolder = this.getWorkspaceFolder(folder, config); + const workspaceFolder = this.getWorkspaceFolder(folder); - if ((config.noDebug === true && numberOfSettings.length === 1) || numberOfSettings.length === 0) { - const defaultProgram = this.getProgram(config); + if (debugConfiguration.request === 'attach') { + this.provideAttachDefaults(workspaceFolder, debugConfiguration as PythonAttachDebugConfiguration); + } else { + const config = debugConfiguration as PythonLaunchDebugConfiguration; + const numberOfSettings = Object.keys(config); - config.name = 'Launch'; - config.type = this.debugType; - config.request = 'launch'; - config.program = defaultProgram ? defaultProgram : ''; - config.env = {}; - } + if ((config.noDebug === true && numberOfSettings.length === 1) || numberOfSettings.length === 0) { + const defaultProgram = this.getProgram(); + + config.name = 'Launch'; + config.type = this.debugType; + config.request = 'launch'; + config.program = defaultProgram ? defaultProgram : ''; + config.env = {}; + } - this.provideDefaults(workspaceFolder, config); - return config; + this.provideLaunchDefaults(workspaceFolder, config); + } + return debugConfiguration; + } + protected provideAttachDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonAttachDebugConfiguration): void { + if (!Array.isArray(debugConfiguration.debugOptions)) { + debugConfiguration.debugOptions = []; + } + // Always redirect output. + if (debugConfiguration.debugOptions.indexOf(DebugOptions.RedirectOutput) === -1) { + debugConfiguration.debugOptions.push(DebugOptions.RedirectOutput); + } + if (!debugConfiguration.host) { + debugConfiguration.host = 'localhost'; + } + if (!debugConfiguration.localRoot && workspaceFolder) { + debugConfiguration.localRoot = workspaceFolder.fsPath; + } } - protected provideDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonDebugConfiguration): void { + protected provideLaunchDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonLaunchDebugConfiguration): void { this.resolveAndUpdatePythonPath(workspaceFolder, debugConfiguration); if (typeof debugConfiguration.cwd !== 'string' && workspaceFolder) { debugConfiguration.cwd = workspaceFolder.fsPath; @@ -75,11 +96,11 @@ export abstract class BaseConfigurationProvider implements DebugConfigurationPro } } } - private getWorkspaceFolder(folder: WorkspaceFolder | undefined, config: PythonDebugConfiguration): Uri | undefined { + private getWorkspaceFolder(folder: WorkspaceFolder | undefined): Uri | undefined { if (folder) { return folder.uri; } - const program = this.getProgram(config); + const program = this.getProgram(); const workspaceService = this.serviceContainer.get(IWorkspaceService); if (!Array.isArray(workspaceService.workspaceFolders) || workspaceService.workspaceFolders.length === 0) { return program ? Uri.file(path.dirname(program)) : undefined; @@ -94,14 +115,14 @@ export abstract class BaseConfigurationProvider implements DebugConfigurationPro } } } - private getProgram(config: PythonDebugConfiguration): string | undefined { + private getProgram(): string | undefined { const documentManager = this.serviceContainer.get(IDocumentManager); const editor = documentManager.activeTextEditor; if (editor && editor.document.languageId === PythonLanguage.language) { return editor.document.fileName; } } - private resolveAndUpdatePythonPath(workspaceFolder: Uri | undefined, debugConfiguration: PythonDebugConfiguration): void { + private resolveAndUpdatePythonPath(workspaceFolder: Uri | undefined, debugConfiguration: PythonLaunchDebugConfiguration): void { if (!debugConfiguration) { return; } diff --git a/src/client/debugger/configProviders/pythonV2Provider.ts b/src/client/debugger/configProviders/pythonV2Provider.ts index 0d95f539e80b..d356901f45ab 100644 --- a/src/client/debugger/configProviders/pythonV2Provider.ts +++ b/src/client/debugger/configProviders/pythonV2Provider.ts @@ -8,15 +8,15 @@ import { Uri } from 'vscode'; import { IPlatformService } from '../../common/platform/types'; import { IServiceContainer } from '../../ioc/types'; import { DebugOptions } from '../Common/Contracts'; -import { BaseConfigurationProvider, PythonDebugConfiguration } from './baseProvider'; +import { BaseConfigurationProvider, PythonAttachDebugConfiguration, PythonLaunchDebugConfiguration } from './baseProvider'; @injectable() export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvider { constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { super('pythonExperimental', serviceContainer); } - protected provideDefaults(workspaceFolder: Uri, debugConfiguration: PythonDebugConfiguration): void { - super.provideDefaults(workspaceFolder, debugConfiguration); + protected provideLaunchDefaults(workspaceFolder: Uri, debugConfiguration: PythonLaunchDebugConfiguration): void { + super.provideLaunchDefaults(workspaceFolder, debugConfiguration); debugConfiguration.stopOnEntry = false; debugConfiguration.debugOptions = Array.isArray(debugConfiguration.debugOptions) ? debugConfiguration.debugOptions : []; @@ -30,4 +30,14 @@ export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvide debugConfiguration.debugOptions.push(DebugOptions.Jinja); } } + protected provideAttachDefaults(workspaceFolder: Uri, debugConfiguration: PythonAttachDebugConfiguration): void { + super.provideAttachDefaults(workspaceFolder, debugConfiguration); + + debugConfiguration.debugOptions = Array.isArray(debugConfiguration.debugOptions) ? debugConfiguration.debugOptions : []; + + // Add PTVSD specific flags. + if (this.serviceContainer.get(IPlatformService).isWindows) { + debugConfiguration.debugOptions.push(DebugOptions.FixFilePathCase); + } + } } diff --git a/src/client/debugger/mainV2.ts b/src/client/debugger/mainV2.ts index bcb9a9d26e8c..280e04b58d59 100644 --- a/src/client/debugger/mainV2.ts +++ b/src/client/debugger/mainV2.ts @@ -24,8 +24,7 @@ import { createDeferred, Deferred, isNotInstalledError } from '../common/helpers import { ICurrentProcess } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { AttachRequestArguments, LaunchRequestArguments } from './Common/Contracts'; -import { DebugClient } from './DebugClients/DebugClient'; -import { CreateLaunchDebugClient } from './DebugClients/DebugFactory'; +import { CreateAttachDebugClient, CreateLaunchDebugClient } from './DebugClients/DebugFactory'; import { BaseDebugServer } from './DebugServers/BaseDebugServer'; import { initializeIoc } from './serviceRegistry'; import { IDebugStreamProvider, IProtocolLogger, IProtocolMessageWriter, IProtocolParser } from './types'; @@ -44,7 +43,6 @@ const MIN_DEBUGGER_CONNECT_TIMEOUT = 5000; */ export class PythonDebugger extends DebugSession { public debugServer?: BaseDebugServer; - public debugClient?: DebugClient<{}>; public client = createDeferred(); private supportsRunInTerminalRequest: boolean = false; constructor(private readonly serviceContainer: IServiceContainer) { @@ -55,10 +53,6 @@ export class PythonDebugger extends DebugSession { this.debugServer.Stop(); this.debugServer = undefined; } - if (this.debugClient) { - this.debugClient.Stop(); - this.debugClient = undefined; - } super.shutdown(); } protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { @@ -91,14 +85,24 @@ export class PythonDebugger extends DebugSession { this.sendResponse(response); } protected attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArguments): void { - this.sendResponse(response); + const launcher = CreateAttachDebugClient(args, this); + this.debugServer = launcher.CreateDebugServer(undefined, this.serviceContainer); + this.debugServer!.Start() + .then(() => this.emit('debugger_attached')) + .catch(ex => { + logger.error('Attach failed'); + logger.error(`${ex}, ${ex.name}, ${ex.message}, ${ex.stack}`); + const message = this.getUserFriendlyAttachErrorMessage(ex) || 'Attach Failed'; + this.sendErrorResponse(response, { format: message, id: 1 }, undefined, undefined, ErrorDestination.User); + }); + } protected launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): void { this.launchPTVSD(args) .then(() => this.waitForPTVSDToConnect(args)) - .then(() => this.sendResponse(response)) + .then(() => this.emit('debugger_launched')) .catch(ex => { - const message = this.getErrorUserFriendlyMessage(args, ex) || 'Debug Error'; + const message = this.getUserFriendlyLaunchErrorMessage(args, ex) || 'Debug Error'; this.sendErrorResponse(response, { format: message, id: 1 }, undefined, undefined, ErrorDestination.User); }); } @@ -134,7 +138,7 @@ export class PythonDebugger extends DebugSession { const connectionTimeout = typeof (args as any).timeout === 'number' ? (args as any).timeout as number : DEBUGGER_CONNECT_TIMEOUT; return Math.max(connectionTimeout, MIN_DEBUGGER_CONNECT_TIMEOUT); } - private getErrorUserFriendlyMessage(launchArgs: LaunchRequestArguments, error: any): string | undefined { + private getUserFriendlyLaunchErrorMessage(launchArgs: LaunchRequestArguments, error: any): string | undefined { if (!error) { return; } @@ -145,6 +149,16 @@ export class PythonDebugger extends DebugSession { return errorMsg; } } + private getUserFriendlyAttachErrorMessage(error: any): string | undefined { + if (!error) { + return; + } + if (error.code === 'ECONNREFUSED' || error.errno === 'ECONNREFUSED') { + return `Failed to attach (${error.message})`; + } else { + return typeof error === 'string' ? error : ((error.message && error.message.length > 0) ? error.message : ''); + } + } } /** @@ -166,7 +180,7 @@ class DebugManager implements Disposable { private readonly debugSessionInputStream: PassThrough; // #endregion // #region Streams used to communicate with PTVSD. - private ptvsdSocket!: Socket; + private socket!: Socket; // #endregion private readonly inputProtocolParser: IProtocolParser; private readonly outputProtocolParser: IProtocolParser; @@ -177,7 +191,7 @@ class DebugManager implements Disposable { private hasShutdown: boolean = false; private debugSession?: PythonDebugger; private ptvsdProcessId?: number; - private killPTVSDProcess: boolean = false; + private launchOrAttach?: 'launch' | 'attach'; private terminatedEventSent: boolean = false; private readonly initializeRequestDeferred: Deferred; private get initializeRequest(): Promise { @@ -188,6 +202,11 @@ class DebugManager implements Disposable { return this.launchRequestDeferred.promise; } + private readonly attachRequestDeferred: Deferred; + private get attachRequest(): Promise { + return this.attachRequestDeferred.promise; + } + private set loggingEnabled(value: boolean) { if (value) { logger.setup(LogLevel.Verbose, true); @@ -215,6 +234,7 @@ class DebugManager implements Disposable { this.initializeRequestDeferred = createDeferred(); this.launchRequestDeferred = createDeferred(); + this.attachRequestDeferred = createDeferred(); } public dispose() { this.shutdown().ignoreErrors(); @@ -249,9 +269,9 @@ class DebugManager implements Disposable { this.hasShutdown = true; logger.verbose('shutdown'); - if (this.ptvsdSocket) { - this.throughInputStream.unpipe(this.ptvsdSocket); - this.ptvsdSocket.unpipe(this.throughOutputStream); + if (this.socket) { + this.throughInputStream.unpipe(this.socket); + this.socket.unpipe(this.throughOutputStream); } if (!this.terminatedEventSent) { @@ -267,7 +287,7 @@ class DebugManager implements Disposable { this.terminatedEventSent = true; } - if (this.killPTVSDProcess && this.ptvsdProcessId) { + if (this.launchOrAttach === 'launch' && this.ptvsdProcessId) { logger.verbose('killing process'); try { // 1. Wait for some time, its possible the program has run to completion. @@ -277,7 +297,6 @@ class DebugManager implements Disposable { await sleep(100); killProcessTree(this.ptvsdProcessId!); } catch { } - this.killPTVSDProcess = false; this.ptvsdProcessId = undefined; } @@ -299,6 +318,9 @@ class DebugManager implements Disposable { this.debugSession = new PythonDebugger(this.serviceContainer); this.debugSession.setRunAsServer(this.isServerMode); + this.debugSession.once('debugger_attached', this.connectVSCodeToPTVSD); + this.debugSession.once('debugger_launched', this.connectVSCodeToPTVSD); + this.debugSessionOutputStream.pipe(this.throughOutputStream); this.debugSessionOutputStream.pipe(this.outputStream); @@ -313,64 +335,63 @@ class DebugManager implements Disposable { // Keep track of the initialize and launch requests, we'll need to re-send these to ptvsd, for bootstrapping. this.inputProtocolParser.once('request_initialize', this.onRequestInitialize); this.inputProtocolParser.once('request_launch', this.onRequestLaunch); + this.inputProtocolParser.once('request_attach', this.onRequestAttach); this.outputProtocolParser.once('event_terminated', this.onEventTerminated); this.outputProtocolParser.once('response_disconnect', this.onResponseDisconnect); - this.outputProtocolParser.once('response_launch', this.connectVSCodeToPTVSD); } /** - * Once PTVSD process has been started (done by DebugSession), we need to connect PTVSD socket to VS Code. + * Connect PTVSD socket to VS Code. * This allows PTVSD to communicate directly with VS Code. * @private * @memberof DebugManager */ - private connectVSCodeToPTVSD = async () => { + private connectVSCodeToPTVSD = async (response: DebugProtocol.AttachResponse | DebugProtocol.LaunchResponse) => { + const attachOrLaunchRequest = await (this.launchOrAttach === 'attach' ? this.attachRequest : this.launchRequest); // By now we're connected to the client. - this.ptvsdSocket = await this.debugSession!.debugServer!.client; + this.socket = await this.debugSession!.debugServer!.client; // We need to handle both end and error, sometimes the socket will error out without ending (if debugee is killed). // Note, we need a handler for the error event, else nodejs complains when socket gets closed and there are no error handlers. - this.ptvsdSocket.on('end', this.shutdown); - this.ptvsdSocket.on('error', this.shutdown); - const debugSoketProtocolParser = this.serviceContainer.get(IProtocolParser); - debugSoketProtocolParser.connect(this.ptvsdSocket); - - // Send PTVSD the launch request (PTVSD needs to do its own initialization using launch arguments). - // E.g. redirectOutput & fixFilePathCase found in launch request are used to initialize the debugger. - this.sendMessage(await this.launchRequest, this.ptvsdSocket); - await new Promise(resolve => debugSoketProtocolParser.once('response_launch', resolve)); - - // The PTVSD process has launched, now send the initialize request to it (required by PTVSD). - this.sendMessage(await this.initializeRequest, this.ptvsdSocket); + this.socket.on('end', this.shutdown); + this.socket.on('error', this.shutdown); // Keep track of processid for killing it. - debugSoketProtocolParser.once('event_process', (proc: DebugProtocol.ProcessEvent) => { - this.ptvsdProcessId = proc.body.systemProcessId; - }); + if (this.launchOrAttach === 'launch') { + const debugSoketProtocolParser = this.serviceContainer.get(IProtocolParser); + debugSoketProtocolParser.connect(this.socket); + debugSoketProtocolParser.once('event_process', (proc: DebugProtocol.ProcessEvent) => { + this.ptvsdProcessId = proc.body.systemProcessId; + }); + } - // Wait for PTVSD to reply back with initialized event. - debugSoketProtocolParser.once('event_initialized', (initialized: DebugProtocol.InitializedEvent) => { - // Get ready for PTVSD to communicate directly with VS Code. - (this.inputStream as any as NodeJS.ReadStream).unpipe(this.debugSessionInputStream); - this.debugSessionOutputStream.unpipe(this.outputStream); + // Get ready for PTVSD to communicate directly with VS Code. + (this.inputStream as any as NodeJS.ReadStream).unpipe(this.debugSessionInputStream); + this.debugSessionOutputStream.unpipe(this.outputStream); - this.inputStream.pipe(this.ptvsdSocket!); - this.ptvsdSocket!.pipe(this.throughOutputStream); - this.ptvsdSocket!.pipe(this.outputStream); + this.inputStream.pipe(this.socket!); + this.socket!.pipe(this.throughOutputStream); + this.socket!.pipe(this.outputStream); - // Forward the initialized event sent by PTVSD onto VSCode. - // This is what will cause PTVSD to start the actualy work. - this.sendMessage(initialized, this.outputStream); - }); + // Send the launch/attach request to PTVSD and wait for it to reply back. + this.sendMessage(attachOrLaunchRequest, this.socket); + + // Send the initialize request and wait for it to reply back with the initialized event + this.sendMessage(await this.initializeRequest, this.socket); } private onRequestInitialize = (request: DebugProtocol.InitializeRequest) => { this.initializeRequestDeferred.resolve(request); } private onRequestLaunch = (request: DebugProtocol.LaunchRequest) => { - this.killPTVSDProcess = true; + this.launchOrAttach = 'launch'; this.loggingEnabled = (request.arguments as LaunchRequestArguments).logToFile === true; this.launchRequestDeferred.resolve(request); } + private onRequestAttach = (request: DebugProtocol.AttachRequest) => { + this.launchOrAttach = 'attach'; + this.loggingEnabled = (request.arguments as AttachRequestArguments).logToFile === true; + this.attachRequestDeferred.resolve(request); + } private onEventTerminated = async () => { logger.verbose('onEventTerminated'); this.terminatedEventSent = true; diff --git a/src/test/debugger/attach.ptvsd.test.ts b/src/test/debugger/attach.ptvsd.test.ts new file mode 100644 index 000000000000..8dbe6df09152 --- /dev/null +++ b/src/test/debugger/attach.ptvsd.test.ts @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:no-invalid-this max-func-body-length no-empty no-increment-decrement + +import { ChildProcess, spawn } from 'child_process'; +import * as getFreePort from 'get-port'; +import * as path from 'path'; +import { DebugClient } from 'vscode-debugadapter-testsupport'; +import { EXTENSION_ROOT_DIR } from '../../client/common/constants'; +import '../../client/common/extensions'; +import { DebugOptions } from '../../client/debugger/Common/Contracts'; +import { sleep } from '../common'; +import { initialize, IS_APPVEYOR, IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; +import { continueDebugging, createDebugAdapter } from './utils'; + +const fileToDebug = path.join(EXTENSION_ROOT_DIR, 'src', 'testMultiRootWkspc', 'workspace5', 'remoteDebugger-start-with-ptvsd.py'); +const ptvsdPath = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'experimental', 'ptvsd'); + +suite('Attach Debugger - Experimental', () => { + let debugClient: DebugClient; + let procToKill: ChildProcess; + suiteSetup(initialize); + + setup(async function () { + if (!IS_MULTI_ROOT_TEST || !TEST_DEBUGGER) { + this.skip(); + } + const coverageDirectory = path.join(EXTENSION_ROOT_DIR, 'debug_coverage_attach_ptvsd'); + debugClient = await createDebugAdapter(coverageDirectory); + }); + teardown(async () => { + // Wait for a second before starting another test (sometimes, sockets take a while to get closed). + await sleep(1000); + try { + await debugClient.stop().catch(() => { }); + } catch (ex) { } + if (procToKill) { + try { + procToKill.kill(); + } catch { } + } + }); + test('Confirm we are able to attach to a running program', async function () { + this.timeout(20000); + // Lets skip this test on AppVeyor (very flaky on AppVeyor). + if (IS_APPVEYOR) { + return; + } + + const port = await getFreePort({ host: 'localhost', port: 3000 }); + const customEnv = { ...process.env }; + + // Set the path for PTVSD to be picked up. + // tslint:disable-next-line:no-string-literal + customEnv['PYTHONPATH'] = ptvsdPath; + const pythonArgs = ['-m', 'ptvsd', '--server', '--port', `${port}`, '--file', fileToDebug.fileToCommandArgument()]; + procToKill = spawn('python', pythonArgs, { env: customEnv, cwd: path.dirname(fileToDebug) }); + + // Send initialize, attach + const initializePromise = debugClient.initializeRequest({ + adapterID: 'pythonExperimental', + linesStartAt1: true, + columnsStartAt1: true, + supportsRunInTerminalRequest: true, + pathFormat: 'path', + supportsVariableType: true, + supportsVariablePaging: true + }); + const attachPromise = debugClient.attachRequest({ + localRoot: path.dirname(fileToDebug), + remoteRoot: path.dirname(fileToDebug), + type: 'pythonExperimental', + port: port, + host: 'localhost', + logToFile: false, + debugOptions: [DebugOptions.RedirectOutput] + }); + + await Promise.all([ + initializePromise, + attachPromise, + debugClient.waitForEvent('initialized') + ]); + + await debugClient.configurationDoneRequest(); + + const stdOutPromise = debugClient.assertOutput('stdout', 'this is stdout'); + const stdErrPromise = debugClient.assertOutput('stderr', 'this is stderr'); + + const breakpointLocation = { path: fileToDebug, column: 1, line: 12 }; + const breakpointPromise = debugClient.setBreakpointsRequest({ + lines: [breakpointLocation.line], + breakpoints: [{ line: breakpointLocation.line, column: breakpointLocation.column }], + source: { path: breakpointLocation.path } + }); + const exceptionBreakpointPromise = debugClient.setExceptionBreakpointsRequest({ filters: [] }); + await Promise.all([ + breakpointPromise, + exceptionBreakpointPromise, + stdOutPromise, stdErrPromise + ]); + + await debugClient.assertStoppedLocation('breakpoint', breakpointLocation); + + await Promise.all([ + continueDebugging(debugClient), + debugClient.assertOutput('stdout', 'this is print'), + debugClient.waitForEvent('exited'), + debugClient.waitForEvent('terminated') + ]); + }); +}); diff --git a/src/test/debugger/configProvider/provider.attach.test.ts b/src/test/debugger/configProvider/provider.attach.test.ts new file mode 100644 index 000000000000..596cc907c7dc --- /dev/null +++ b/src/test/debugger/configProvider/provider.attach.test.ts @@ -0,0 +1,197 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:max-func-body-length no-invalid-template-strings no-any no-object-literal-type-assertion + +import { expect } from 'chai'; +import * as path from 'path'; +import * as TypeMoq from 'typemoq'; +import { DebugConfiguration, DebugConfigurationProvider, TextDocument, TextEditor, Uri, WorkspaceFolder } from 'vscode'; +import { IDocumentManager, IWorkspaceService } from '../../../client/common/application/types'; +import { PythonLanguage } from '../../../client/common/constants'; +import { EnumEx } from '../../../client/common/enumUtils'; +import { IFileSystem, IPlatformService } from '../../../client/common/platform/types'; +import { PythonDebugConfigurationProvider, PythonV2DebugConfigurationProvider } from '../../../client/debugger'; +import { DebugOptions } from '../../../client/debugger/Common/Contracts'; +import { IServiceContainer } from '../../../client/ioc/types'; + +enum OS { + Windows, + Mac, + Linux +} +[ + { debugType: 'pythonExperimental', class: PythonV2DebugConfigurationProvider }, + { debugType: 'python', class: PythonDebugConfigurationProvider } +].forEach(provider => { + EnumEx.getNamesAndValues(OS).forEach(os => { + suite(`Debugging - Config Provider attach, ${provider.debugType}, OS = ${os.name}`, () => { + let serviceContainer: TypeMoq.IMock; + let debugProvider: DebugConfigurationProvider; + let platformService: TypeMoq.IMock; + let fileSystem: TypeMoq.IMock; + const debugOptionsAvailable = [DebugOptions.RedirectOutput]; + if (os.value === OS.Windows && provider.debugType === 'pythonExperimental') { + debugOptionsAvailable.push(DebugOptions.FixFilePathCase); + } + setup(() => { + serviceContainer = TypeMoq.Mock.ofType(); + platformService = TypeMoq.Mock.ofType(); + fileSystem = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPlatformService))).returns(() => platformService.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IFileSystem))).returns(() => fileSystem.object); + platformService.setup(p => p.isWindows).returns(() => os.value === OS.Windows); + platformService.setup(p => p.isMac).returns(() => os.value === OS.Mac); + platformService.setup(p => p.isLinux).returns(() => os.value === OS.Linux); + debugProvider = new provider.class(serviceContainer.object); + }); + function createMoqWorkspaceFolder(folderPath: string) { + const folder = TypeMoq.Mock.ofType(); + folder.setup(f => f.uri).returns(() => Uri.file(folderPath)); + return folder.object; + } + function setupActiveEditor(fileName: string | undefined, languageId: string) { + const documentManager = TypeMoq.Mock.ofType(); + if (fileName) { + const textEditor = TypeMoq.Mock.ofType(); + const document = TypeMoq.Mock.ofType(); + document.setup(d => d.languageId).returns(() => languageId); + document.setup(d => d.fileName).returns(() => fileName); + textEditor.setup(t => t.document).returns(() => document.object); + documentManager.setup(d => d.activeTextEditor).returns(() => textEditor.object); + } else { + documentManager.setup(d => d.activeTextEditor).returns(() => undefined); + } + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IDocumentManager))).returns(() => documentManager.object); + } + function setupWorkspaces(folders: string[]) { + const workspaceService = TypeMoq.Mock.ofType(); + const workspaceFolders = folders.map(createMoqWorkspaceFolder); + workspaceService.setup(w => w.workspaceFolders).returns(() => workspaceFolders); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IWorkspaceService))).returns(() => workspaceService.object); + } + test('Defaults should be returned when an empty object is passed with a Workspace Folder and active file', async () => { + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + const pythonFile = 'xyz.py'; + + setupActiveEditor(pythonFile, PythonLanguage.language); + + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { request: 'attach' } as DebugConfiguration); + + expect(Object.keys(debugConfig!)).to.have.lengthOf.above(3); + expect(debugConfig).to.have.property('request', 'attach'); + expect(debugConfig).to.have.property('localRoot'); + expect(debugConfig!.localRoot!.toLowerCase()).to.be.equal(__dirname.toLowerCase()); + expect(debugConfig).to.have.property('debugOptions').deep.equal(debugOptionsAvailable); + }); + test('Defaults should be returned when an empty object is passed without Workspace Folder, no workspaces and active file', async () => { + const pythonFile = 'xyz.py'; + + setupActiveEditor(pythonFile, PythonLanguage.language); + setupWorkspaces([]); + + const debugConfig = await debugProvider.resolveDebugConfiguration!(undefined, { request: 'attach' } as DebugConfiguration); + const filePath = Uri.file(path.dirname('')).fsPath; + + expect(Object.keys(debugConfig!)).to.have.lengthOf.least(3); + expect(debugConfig).to.have.property('request', 'attach'); + expect(debugConfig).to.have.property('localRoot'); + expect(debugConfig).to.have.property('host', 'localhost'); + expect(debugConfig!.localRoot!.toLowerCase()).to.be.equal(filePath.toLowerCase()); + expect(debugConfig).to.have.property('debugOptions').deep.equal(debugOptionsAvailable); + }); + test('Defaults should be returned when an empty object is passed without Workspace Folder, no workspaces and no active file', async () => { + setupActiveEditor(undefined, PythonLanguage.language); + setupWorkspaces([]); + + const debugConfig = await debugProvider.resolveDebugConfiguration!(undefined, { request: 'attach' } as DebugConfiguration); + + expect(Object.keys(debugConfig!)).to.have.lengthOf.least(3); + expect(debugConfig).to.have.property('request', 'attach'); + expect(debugConfig).to.not.have.property('localRoot'); + expect(debugConfig).to.have.property('host', 'localhost'); + expect(debugConfig).to.have.property('debugOptions').deep.equal(debugOptionsAvailable); + }); + test('Defaults should be returned when an empty object is passed without Workspace Folder, no workspaces and non python file', async () => { + const activeFile = 'xyz.js'; + + setupActiveEditor(activeFile, 'javascript'); + setupWorkspaces([]); + + const debugConfig = await debugProvider.resolveDebugConfiguration!(undefined, { request: 'attach' } as DebugConfiguration); + + expect(Object.keys(debugConfig!)).to.have.lengthOf.least(3); + expect(debugConfig).to.have.property('request', 'attach'); + expect(debugConfig).to.not.have.property('localRoot'); + expect(debugConfig).to.have.property('host', 'localhost'); + expect(debugConfig).to.have.property('debugOptions').deep.equal(debugOptionsAvailable); + }); + test('Defaults should be returned when an empty object is passed without Workspace Folder, with a workspace and an active python file', async () => { + const activeFile = 'xyz.py'; + setupActiveEditor(activeFile, PythonLanguage.language); + const defaultWorkspace = path.join('usr', 'desktop'); + setupWorkspaces([defaultWorkspace]); + + const debugConfig = await debugProvider.resolveDebugConfiguration!(undefined, { request: 'attach' } as DebugConfiguration); + const filePath = Uri.file(defaultWorkspace).fsPath; + + expect(Object.keys(debugConfig!)).to.have.lengthOf.least(3); + expect(debugConfig).to.have.property('request', 'attach'); + expect(debugConfig).to.have.property('localRoot'); + expect(debugConfig).to.have.property('host', 'localhost'); + expect(debugConfig!.localRoot!.toLowerCase()).to.be.equal(filePath.toLowerCase()); + expect(debugConfig).to.have.property('debugOptions').deep.equal(debugOptionsAvailable); + }); + test('Ensure \'localRoot\' is left unaltered', async () => { + const activeFile = 'xyz.py'; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + setupActiveEditor(activeFile, PythonLanguage.language); + const defaultWorkspace = path.join('usr', 'desktop'); + setupWorkspaces([defaultWorkspace]); + + const localRoot = `Debug_PythonPath_${new Date().toString()}`; + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { localRoot, request: 'attach' } as any as DebugConfiguration); + + expect(debugConfig).to.have.property('localRoot', localRoot); + }); + test('Ensure \'remoteRoot\' is left unaltered', async () => { + const activeFile = 'xyz.py'; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + setupActiveEditor(activeFile, PythonLanguage.language); + const defaultWorkspace = path.join('usr', 'desktop'); + setupWorkspaces([defaultWorkspace]); + + const remoteRoot = `Debug_PythonPath_${new Date().toString()}`; + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { remoteRoot, request: 'attach' } as any as DebugConfiguration); + + expect(debugConfig).to.have.property('remoteRoot', remoteRoot); + }); + test('Ensure \'port\' is left unaltered', async () => { + const activeFile = 'xyz.py'; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + setupActiveEditor(activeFile, PythonLanguage.language); + const defaultWorkspace = path.join('usr', 'desktop'); + setupWorkspaces([defaultWorkspace]); + + const port = 12341234; + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { port, request: 'attach' } as any as DebugConfiguration); + + expect(debugConfig).to.have.property('port', port); + }); + test('Ensure \'debugOptions\' are left unaltered', async () => { + const activeFile = 'xyz.py'; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + setupActiveEditor(activeFile, PythonLanguage.language); + const defaultWorkspace = path.join('usr', 'desktop'); + setupWorkspaces([defaultWorkspace]); + + const debugOptions = debugOptionsAvailable.slice().concat(DebugOptions.Jinja, DebugOptions.Sudo); + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { debugOptions, request: 'attach' } as any as DebugConfiguration); + + expect(debugConfig).to.have.property('debugOptions').to.be.deep.equal(debugOptions); + }); + }); + }); +}); diff --git a/src/test/debugger/utils.ts b/src/test/debugger/utils.ts new file mode 100644 index 000000000000..ad59a2fd98e5 --- /dev/null +++ b/src/test/debugger/utils.ts @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:no-any no-http-string + +import { expect } from 'chai'; +import * as path from 'path'; +import * as request from 'request'; +import { DebugClient } from 'vscode-debugadapter-testsupport'; +import { DebugProtocol } from 'vscode-debugprotocol/lib/debugProtocol'; +import { EXTENSION_ROOT_DIR } from '../../client/common/constants'; +import { IS_WINDOWS } from '../../client/common/platform/constants'; +import { DEBUGGER_TIMEOUT } from './common/constants'; +import { DebugClientEx } from './debugClient'; + +const testAdapterFilePath = path.join(EXTENSION_ROOT_DIR, 'out', 'client', 'debugger', 'mainV2.js'); +const debuggerType = 'pythonExperimental'; + +/** + * Creates the debug adapter. + * We do not need to support code coverage on AppVeyor, lets use the standard test adapter. + * @returns {DebugClient} + */ +export async function createDebugAdapter(coverageDirectory: string): Promise { + await new Promise(resolve => setTimeout(resolve, 1000)); + let debugClient: DebugClient; + if (IS_WINDOWS) { + debugClient = new DebugClient('node', testAdapterFilePath, debuggerType); + } else { + debugClient = new DebugClientEx(testAdapterFilePath, debuggerType, coverageDirectory, { cwd: EXTENSION_ROOT_DIR }); + } + debugClient.defaultTimeout = DEBUGGER_TIMEOUT; + await debugClient.start(); + return debugClient; +} + +export async function continueDebugging(debugClient: DebugClient) { + const threads = await debugClient.threadsRequest(); + expect(threads).to.be.not.equal(undefined, 'no threads response'); + expect(threads.body.threads).to.be.lengthOf(1); + + await debugClient.continueRequest({ threadId: threads.body.threads[0].id }); +} + +export type ExpectedVariable = { type: string; name: string; value: string }; +export async function validateVariablesInFrame(debugClient: DebugClient, + stackTrace: DebugProtocol.StackTraceResponse, + expectedVariables: ExpectedVariable[], numberOfScopes?: number) { + + const frameId = stackTrace.body.stackFrames[0].id; + + const scopes = await debugClient.scopesRequest({ frameId }); + if (numberOfScopes) { + expect(scopes.body.scopes).of.length(1, 'Incorrect number of scopes'); + } + + const variablesReference = scopes.body.scopes[0].variablesReference; + const variables = await debugClient.variablesRequest({ variablesReference }); + + for (const expectedVariable of expectedVariables) { + const variable = variables.body.variables.find(item => item.name === expectedVariable.name)!; + expect(variable).to.be.not.equal('undefined', `variable '${expectedVariable.name}' is undefined`); + expect(variable.type).to.be.equal(expectedVariable.type); + expect(variable.value).to.be.equal(expectedVariable.value); + } +} +export function makeHttpRequest(uri: string): Promise { + return new Promise((resolve, reject) => { + request.get(uri, (error: any, response: request.Response, body: any) => { + if (response.statusCode !== 200) { + reject(new Error(`Status code = ${response.statusCode}`)); + } else { + resolve(body.toString()); + } + }); + }); +} +export async function hitHttpBreakpoint(debugClient: DebugClient, uri: string, file: string, line: number): Promise<[DebugProtocol.StackTraceResponse, Promise]> { + const breakpointLocation = { path: file, column: 1, line }; + await debugClient.setBreakpointsRequest({ + lines: [breakpointLocation.line], + breakpoints: [{ line: breakpointLocation.line, column: breakpointLocation.column }], + source: { path: breakpointLocation.path } + }); + + // Make the request, we want the breakpoint to be hit. + const breakpointPromise = debugClient.assertStoppedLocation('breakpoint', breakpointLocation); + const httpResult = makeHttpRequest(uri); + return [await breakpointPromise, httpResult]; +} diff --git a/src/testMultiRootWkspc/workspace5/remoteDebugger-start-with-ptvsd.py b/src/testMultiRootWkspc/workspace5/remoteDebugger-start-with-ptvsd.py new file mode 100644 index 000000000000..ad8d7003cb6d --- /dev/null +++ b/src/testMultiRootWkspc/workspace5/remoteDebugger-start-with-ptvsd.py @@ -0,0 +1,14 @@ +import sys +import time +time.sleep(2) +sys.stdout.write('this is stdout') +sys.stdout.flush() +sys.stderr.write('this is stderr') +sys.stderr.flush() +# Give the debugger some time to add a breakpoint. +time.sleep(5) +for i in range(1): + time.sleep(0.5) + pass + +print('this is print') From f3374ba77858c84c7d7216c638846c1ae25ded94 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 4 Apr 2018 18:48:29 -0700 Subject: [PATCH 094/433] Unit tests for debugging module (#1297) Fixes #1188 --- .vscode/settings.json | 4 +- package.json | 2 +- src/client/common/helpers.ts | 8 +-- src/client/debugger/Common/constants.ts | 9 +++ .../debugger/DebugClients/LocalDebugClient.ts | 5 +- src/test/debugger/attach.ptvsd.test.ts | 4 +- src/test/debugger/capabilities.test.ts | 5 +- src/test/debugger/misc.test.ts | 9 +-- src/test/debugger/module.test.ts | 70 +++++++++++++++++++ .../pythonFiles/debugging/stdErrOutput.py | 1 + .../pythonFiles/debugging/stdOutOutput.py | 1 + .../workspace5/mymod/__init__.py | 0 .../workspace5/mymod/__main__.py | 1 + 13 files changed, 98 insertions(+), 21 deletions(-) create mode 100644 src/client/debugger/Common/constants.ts create mode 100644 src/test/debugger/module.test.ts create mode 100644 src/testMultiRootWkspc/workspace5/mymod/__init__.py create mode 100644 src/testMultiRootWkspc/workspace5/mymod/__main__.py diff --git a/.vscode/settings.json b/.vscode/settings.json index 311167344456..be66f967c5c4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -14,9 +14,9 @@ "coverage": true }, "typescript.tsdk": "./node_modules/typescript/lib", // we want to use the TS server from our node_modules folder to control its version - "tslint.enable": true, // We will run our own linting in gulp (& git commit hooks), else tslint extension just complains about unmodified files + "tslint.enable": true, "python.linting.enabled": false, "python.unitTest.promptToConfigure": false, "python.workspaceSymbols.enabled": false, "python.formatting.provider": "none" -} +} \ No newline at end of file diff --git a/package.json b/package.json index 9a589d160676..7925bb4a33f9 100644 --- a/package.json +++ b/package.json @@ -1036,7 +1036,7 @@ { "name": "Python Experimental: Attach", "type": "pythonExperimental", - "request": "pythonExperimental", + "request": "attach", "localRoot": "${workspaceFolder}", "remoteRoot": "${workspaceFolder}", "port": 3000, diff --git a/src/client/common/helpers.ts b/src/client/common/helpers.ts index 82d59e9308c4..ce1824291bd7 100644 --- a/src/client/common/helpers.ts +++ b/src/client/common/helpers.ts @@ -29,9 +29,9 @@ export interface Deferred { } class DeferredImpl implements Deferred { - private _resolve: (value?: T | PromiseLike) => void; + private _resolve!: (value?: T | PromiseLike) => void; // tslint:disable-next-line:no-any - private _reject: (reason?: any) => void; + private _reject!: (reason?: any) => void; private _resolved: boolean = false; private _rejected: boolean = false; private _promise: Promise; @@ -70,14 +70,14 @@ export function createDeferred(scope: any = null): Deferred { return new DeferredImpl(scope); } -export function createTemporaryFile(extension: string, temporaryDirectory?: string): Promise<{ filePath: string, cleanupCallback: Function }> { +export function createTemporaryFile(extension: string, temporaryDirectory?: string): Promise<{ filePath: string; cleanupCallback: Function }> { // tslint:disable-next-line:no-any const options: any = { postfix: extension }; if (temporaryDirectory) { options.dir = temporaryDirectory; } - return new Promise<{ filePath: string, cleanupCallback: Function }>((resolve, reject) => { + return new Promise<{ filePath: string; cleanupCallback: Function }>((resolve, reject) => { tmp.file(options, (err, tmpFile, fd, cleanupCallback) => { if (err) { return reject(err); diff --git a/src/client/debugger/Common/constants.ts b/src/client/debugger/Common/constants.ts new file mode 100644 index 000000000000..e24fb1b790e5 --- /dev/null +++ b/src/client/debugger/Common/constants.ts @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as path from 'path'; +import { EXTENSION_ROOT_DIR } from '../../common/constants'; + +export const PTVSD_PATH = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'experimental', 'ptvsd'); diff --git a/src/client/debugger/DebugClients/LocalDebugClient.ts b/src/client/debugger/DebugClients/LocalDebugClient.ts index e74ce78a63f5..abcb14919bc8 100644 --- a/src/client/debugger/DebugClients/LocalDebugClient.ts +++ b/src/client/debugger/DebugClients/LocalDebugClient.ts @@ -2,12 +2,12 @@ import { ChildProcess, spawn } from 'child_process'; import * as path from 'path'; import { DebugSession, OutputEvent } from 'vscode-debugadapter'; import { DebugProtocol } from 'vscode-debugprotocol'; -import { EXTENSION_ROOT_DIR } from '../../common/constants'; import { open } from '../../common/open'; import { PathUtils } from '../../common/platform/pathUtils'; import { CurrentProcess } from '../../common/process/currentProcess'; import { EnvironmentVariablesService } from '../../common/variables/environment'; import { IServiceContainer } from '../../ioc/types'; +import { PTVSD_PATH } from '../Common/constants'; import { DebugOptions, IDebugServer, IPythonProcess, LaunchRequestArguments, VALID_DEBUG_OPTIONS } from '../Common/Contracts'; import { IS_WINDOWS } from '../Common/Utils'; import { BaseDebugServer } from '../DebugServers/BaseDebugServer'; @@ -81,8 +81,7 @@ export class LocalDebugClient extends DebugClient { const environmentVariables = await helper.getEnvironmentVariables(this.args); if (this.args.type === 'pythonExperimental') { // Import the PTVSD debugger, allowing users to use their own latest copies. - const experimentalPTVSDPath = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'experimental', 'ptvsd'); - environmentVariablesService.appendPythonPath(environmentVariables, experimentalPTVSDPath); + environmentVariablesService.appendPythonPath(environmentVariables, PTVSD_PATH); } // tslint:disable-next-line:max-func-body-length cyclomatic-complexity no-any return new Promise((resolve, reject) => { diff --git a/src/test/debugger/attach.ptvsd.test.ts b/src/test/debugger/attach.ptvsd.test.ts index 8dbe6df09152..76ccd4944034 100644 --- a/src/test/debugger/attach.ptvsd.test.ts +++ b/src/test/debugger/attach.ptvsd.test.ts @@ -11,13 +11,13 @@ import * as path from 'path'; import { DebugClient } from 'vscode-debugadapter-testsupport'; import { EXTENSION_ROOT_DIR } from '../../client/common/constants'; import '../../client/common/extensions'; +import { PTVSD_PATH } from '../../client/debugger/Common/constants'; import { DebugOptions } from '../../client/debugger/Common/Contracts'; import { sleep } from '../common'; import { initialize, IS_APPVEYOR, IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; import { continueDebugging, createDebugAdapter } from './utils'; const fileToDebug = path.join(EXTENSION_ROOT_DIR, 'src', 'testMultiRootWkspc', 'workspace5', 'remoteDebugger-start-with-ptvsd.py'); -const ptvsdPath = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'experimental', 'ptvsd'); suite('Attach Debugger - Experimental', () => { let debugClient: DebugClient; @@ -55,7 +55,7 @@ suite('Attach Debugger - Experimental', () => { // Set the path for PTVSD to be picked up. // tslint:disable-next-line:no-string-literal - customEnv['PYTHONPATH'] = ptvsdPath; + customEnv['PYTHONPATH'] = PTVSD_PATH; const pythonArgs = ['-m', 'ptvsd', '--server', '--port', `${port}`, '--file', fileToDebug.fileToCommandArgument()]; procToKill = spawn('python', pythonArgs, { env: customEnv, cwd: path.dirname(fileToDebug) }); diff --git a/src/test/debugger/capabilities.test.ts b/src/test/debugger/capabilities.test.ts index 8052b5f6f2dc..0bc4005ee512 100644 --- a/src/test/debugger/capabilities.test.ts +++ b/src/test/debugger/capabilities.test.ts @@ -9,12 +9,11 @@ import { expect } from 'chai'; import { ChildProcess, spawn } from 'child_process'; import * as getFreePort from 'get-port'; import { connect, Socket } from 'net'; -import * as path from 'path'; import { PassThrough } from 'stream'; import { Message } from 'vscode-debugadapter/lib/messages'; import { DebugProtocol } from 'vscode-debugprotocol'; -import { EXTENSION_ROOT_DIR } from '../../client/common/constants'; import { createDeferred } from '../../client/common/helpers'; +import { PTVSD_PATH } from '../../client/debugger/Common/constants'; import { ProtocolParser } from '../../client/debugger/Common/protocolParser'; import { ProtocolMessageWriter } from '../../client/debugger/Common/protocolWriter'; import { PythonDebugger } from '../../client/debugger/mainV2'; @@ -75,7 +74,7 @@ suite('Debugging - Capabilities', () => { const host = 'localhost'; const port = await getFreePort({ host }); const env = { ...process.env }; - env.PYTHONPATH = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'experimental', 'ptvsd'); + env.PYTHONPATH = PTVSD_PATH; proc = spawn('python', ['-m', 'ptvsd', '--server', '--port', `${port}`, '--file', 'someFile.py'], { cwd: __dirname, env }); // Wait for the socket server to start. // Keep trying till we timeout. diff --git a/src/test/debugger/misc.test.ts b/src/test/debugger/misc.test.ts index 05b67f77898f..0e17bc9a05c9 100644 --- a/src/test/debugger/misc.test.ts +++ b/src/test/debugger/misc.test.ts @@ -13,6 +13,7 @@ import { noop } from '../../client/common/core.utils'; import { IS_WINDOWS } from '../../client/common/platform/constants'; import { FileSystem } from '../../client/common/platform/fileSystem'; import { PlatformService } from '../../client/common/platform/platformService'; +import { PTVSD_PATH } from '../../client/debugger/Common/constants'; import { DebugOptions, LaunchRequestArguments } from '../../client/debugger/Common/Contracts'; import { sleep } from '../common'; import { IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; @@ -69,8 +70,9 @@ let testCounter = 0; const env = {}; if (debuggerType === 'pythonExperimental') { // tslint:disable-next-line:no-string-literal - env['PYTHONPATH'] = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'experimental', 'ptvsd'); + env['PYTHONPATH'] = PTVSD_PATH; } + // tslint:disable-next-line:no-unnecessary-local-variable const options: LaunchRequestArguments = { program: path.join(debugFilesPath, pythonFile), cwd: debugFilesPath, @@ -84,11 +86,6 @@ let testCounter = 0; type: debuggerType }; - // Custom experimental debugger options (filled in by DebugConfigurationProvider). - if (debuggerType === 'pythonExperimental') { - (options as any).redirectOutput = true; - } - return options; } diff --git a/src/test/debugger/module.test.ts b/src/test/debugger/module.test.ts new file mode 100644 index 000000000000..cdf88f27a68a --- /dev/null +++ b/src/test/debugger/module.test.ts @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:no-suspicious-comment max-func-body-length no-invalid-this no-var-requires no-require-imports no-any + +import * as path from 'path'; +import { DebugClient } from 'vscode-debugadapter-testsupport'; +import { EXTENSION_ROOT_DIR } from '../../client/common/constants'; +import { noop } from '../../client/common/core.utils'; +import { PTVSD_PATH } from '../../client/debugger/Common/constants'; +import { DebugOptions, LaunchRequestArguments } from '../../client/debugger/Common/Contracts'; +import { sleep } from '../common'; +import { IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; +import { createDebugAdapter } from './utils'; + +const workspaceDirectory = path.join(EXTENSION_ROOT_DIR, 'src', 'testMultiRootWkspc', 'workspace5'); +const debuggerType = 'pythonExperimental'; +suite(`Module Debugging - Misc tests: ${debuggerType}`, () => { + let debugClient: DebugClient; + setup(async function () { + if (!IS_MULTI_ROOT_TEST || !TEST_DEBUGGER) { + this.skip(); + } + const coverageDirectory = path.join(EXTENSION_ROOT_DIR, 'debug_coverage_module'); + debugClient = await createDebugAdapter(coverageDirectory); + }); + teardown(async () => { + // Wait for a second before starting another test (sometimes, sockets take a while to get closed). + await sleep(1000); + try { + await debugClient.stop().catch(noop); + // tslint:disable-next-line:no-empty + } catch (ex) { } + await sleep(1000); + }); + function buildLauncArgs(): LaunchRequestArguments { + const env = {}; + // tslint:disable-next-line:no-string-literal + env['PYTHONPATH'] = `.${path.delimiter}${PTVSD_PATH}`; + + // tslint:disable-next-line:no-unnecessary-local-variable + const options: LaunchRequestArguments = { + module: 'mymod', + program: '', + cwd: workspaceDirectory, + debugOptions: [DebugOptions.RedirectOutput], + pythonPath: 'python', + args: [], + env, + envFile: '', + logToFile: false, + type: debuggerType + }; + + return options; + } + + test('Test stdout output', async () => { + await Promise.all([ + debugClient.configurationSequence(), + debugClient.launch(buildLauncArgs()), + debugClient.waitForEvent('initialized'), + debugClient.assertOutput('stdout', 'Hello world!'), + debugClient.waitForEvent('exited'), + debugClient.waitForEvent('terminated') + ]); + }); +}); diff --git a/src/test/pythonFiles/debugging/stdErrOutput.py b/src/test/pythonFiles/debugging/stdErrOutput.py index 1e55c2ffd4ba..ef576d80d8a8 100644 --- a/src/test/pythonFiles/debugging/stdErrOutput.py +++ b/src/test/pythonFiles/debugging/stdErrOutput.py @@ -1,3 +1,4 @@ import sys +import time sys.stderr.write('error output') sys.stderr.flush() diff --git a/src/test/pythonFiles/debugging/stdOutOutput.py b/src/test/pythonFiles/debugging/stdOutOutput.py index 9d1994322597..e750f3c1fcbe 100644 --- a/src/test/pythonFiles/debugging/stdOutOutput.py +++ b/src/test/pythonFiles/debugging/stdOutOutput.py @@ -1,3 +1,4 @@ import sys +import time sys.stdout.write('normal output') sys.stdout.flush() diff --git a/src/testMultiRootWkspc/workspace5/mymod/__init__.py b/src/testMultiRootWkspc/workspace5/mymod/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/testMultiRootWkspc/workspace5/mymod/__main__.py b/src/testMultiRootWkspc/workspace5/mymod/__main__.py new file mode 100644 index 000000000000..f1a18139c84a --- /dev/null +++ b/src/testMultiRootWkspc/workspace5/mymod/__main__.py @@ -0,0 +1 @@ +print("Hello world!") From d5e53d2b3e7fdcf56330bfbc039625a8358b4a6a Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Thu, 5 Apr 2018 09:51:43 -0700 Subject: [PATCH 095/433] Language server startup time improvement (#1299) * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * Undo changes * Test fixes * .NET Core check * Better find dotnet * Fix pip test * Linting tests * Undo accidental changes * Add clone and build PTVS * Appveyor PTVS build * Fix slashes * Enable build * Try absolute path * Fix xcopy switch * Activate Analysis Engine test on Appveyor * Temporary only run new tests * Disable PEP hint tests * Test fix * Disable appveyor build and tests for PTVS for now * Remove analysis engine test from the set * Remove VS image for now * Build/sign VSXI project * Run vsce from cmd * Rename * Abs path vsce * Path * Move project * Ignore publishing project * Try csproj * Add framework * Ignore build output folder * Package before build * Try batch instead of PS * Fix path quotes * #1096 The if statement is automatically formatted incorrectly * Merge fix * Add more tests * More tests * Typo * Test * Also better handle multiline arguments * Changes lost on squash * More lost changes * Restore Jedi/PTVS setting * Update tests to new PTVS * Signature tests * Add PTVS tests task * Analysis Engine contribution * Add Mac/Linux info * Disable csproj build * Add unzip to dependencies * Minor fixes to doc * Change setting type to bool * Report progress on status bar * Simplify * CR feedback * Fix launching fx-independent code on Mac/Linux * Add title * PTVS startup time * PTVS startup time * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Undo changes * Test fixes * Merge master * Remove unused code * Add clone and build PTVS * Fix slashes * Disable PEP hint tests * Test fix * Remove analysis engine test from the set * Build/sign VSXI project * Run vsce from cmd * Rename * Abs path vsce * Path * Move project * Ignore publishing project * Try csproj * Add framework * Ignore build output folder * Package before build * More lost changes * Change setting type to bool * Merge issues * Merge issues * Merge issues * Check search paths only if using cache * Undo change * PR feedback * Add async startup to PTVS --- package.json | 2 +- src/client/activation/analysis.ts | 182 +++++------------- .../activation/interpreterDataService.ts | 146 ++++++++++++++ src/client/common/configuration/service.ts | 30 +-- src/client/common/types.ts | 1 - 5 files changed, 201 insertions(+), 160 deletions(-) create mode 100644 src/client/activation/interpreterDataService.ts diff --git a/package.json b/package.json index 7925bb4a33f9..13d8ab4506b0 100644 --- a/package.json +++ b/package.json @@ -1907,4 +1907,4 @@ "publisherDisplayName": "Microsoft", "publisherId": "998b010b-e2af-44a5-a6cd-0b5fd3b9b6f8" } -} +} \ No newline at end of file diff --git a/src/client/activation/analysis.ts b/src/client/activation/analysis.ts index 3590d52b951c..554daace8d96 100644 --- a/src/client/activation/analysis.ts +++ b/src/client/activation/analysis.ts @@ -6,14 +6,13 @@ import { ExtensionContext, OutputChannel } from 'vscode'; import { Disposable, LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient'; import { IApplicationShell } from '../common/application/types'; import { isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; -import '../common/extensions'; import { IFileSystem, IPlatformService } from '../common/platform/types'; -import { IProcessService, IPythonExecutionFactory } from '../common/process/types'; +import { IProcessService } from '../common/process/types'; import { StopWatch } from '../common/stopWatch'; import { IConfigurationService, IOutputChannel, IPythonSettings } from '../common/types'; -import { IInterpreterService } from '../interpreter/contracts'; import { IServiceContainer } from '../ioc/types'; import { AnalysisEngineDownloader } from './downloader'; +import { InterpreterDataService } from './interpreterDataService'; import { PlatformData } from './platformData'; import { IExtensionActivator } from './types'; @@ -22,12 +21,7 @@ const dotNetCommand = 'dotnet'; const languageClientName = 'Python Tools'; const analysisEngineFolder = 'analysis'; -class InterpreterData { - constructor(public readonly version: string, public readonly prefix: string) { } -} - export class AnalysisExtensionActivator implements IExtensionActivator { - private readonly executionFactory: IPythonExecutionFactory; private readonly configuration: IConfigurationService; private readonly appShell: IApplicationShell; private readonly output: OutputChannel; @@ -37,7 +31,6 @@ export class AnalysisExtensionActivator implements IExtensionActivator { private languageClient: LanguageClient | undefined; constructor(private readonly services: IServiceContainer, pythonSettings: IPythonSettings) { - this.executionFactory = this.services.get(IPythonExecutionFactory); this.configuration = this.services.get(IConfigurationService); this.appShell = this.services.get(IApplicationShell); this.output = this.services.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); @@ -50,7 +43,6 @@ export class AnalysisExtensionActivator implements IExtensionActivator { if (!clientOptions) { return false; } - this.output.appendLine(`Options determined: ${this.sw.elapsedTime} ms`); return this.startLanguageServer(context, clientOptions); } @@ -68,16 +60,17 @@ export class AnalysisExtensionActivator implements IExtensionActivator { if (!await this.fs.fileExistsAsync(mscorlib)) { // Depends on .NET Runtime or SDK this.languageClient = this.createSimpleLanguageClient(context, clientOptions); - const e = await this.tryStartLanguageClient(context, this.languageClient); - if (!e) { + try { + await this.tryStartLanguageClient(context, this.languageClient); return true; + } catch (ex) { + if (await this.isDotNetInstalled()) { + this.appShell.showErrorMessage(`.NET Runtime appears to be installed but the language server did not start. Error ${ex}`); + return false; + } + // No .NET Runtime, no mscorlib - need to download self-contained package. + downloadPackage = true; } - if (await this.isDotNetInstalled()) { - this.appShell.showErrorMessage(`.NET Runtime appears to be installed but the language server did not start. Error ${e}`); - return false; - } - // No .NET Runtime, no mscorlib - need to download self-contained package. - downloadPackage = true; } if (downloadPackage) { @@ -88,15 +81,16 @@ export class AnalysisExtensionActivator implements IExtensionActivator { const serverModule = path.join(context.extensionPath, analysisEngineFolder, this.platformData.getEngineExecutableName()); // Now try to start self-contained app this.languageClient = this.createSelfContainedLanguageClient(context, serverModule, clientOptions); - const error = await this.tryStartLanguageClient(context, this.languageClient); - if (!error) { + try { + await this.tryStartLanguageClient(context, this.languageClient); return true; + } catch (ex) { + this.appShell.showErrorMessage(`Language server failed to start. Error ${ex}`); + return false; } - this.appShell.showErrorMessage(`Language server failed to start. Error ${error}`); - return false; } - private async tryStartLanguageClient(context: ExtensionContext, lc: LanguageClient): Promise { + private async tryStartLanguageClient(context: ExtensionContext, lc: LanguageClient): Promise { let disposable: Disposable | undefined; try { disposable = lc.start(); @@ -106,7 +100,7 @@ export class AnalysisExtensionActivator implements IExtensionActivator { } catch (ex) { if (disposable) { disposable.dispose(); - return ex; + throw ex; } } } @@ -135,45 +129,37 @@ export class AnalysisExtensionActivator implements IExtensionActivator { const properties = new Map(); // Microsoft Python code analysis engine needs full path to the interpreter - const interpreterService = this.services.get(IInterpreterService); - const interpreter = await interpreterService.getActiveInterpreter(); + const interpreterDataService = new InterpreterDataService(context, this.services); + const interpreterData = await interpreterDataService.getInterpreterData(); + if (!interpreterData) { + const appShell = this.services.get(IApplicationShell); + appShell.showErrorMessage('Unable to determine path to Python interpreter.'); + return; + } - if (interpreter) { - // tslint:disable-next-line:no-string-literal - properties['InterpreterPath'] = interpreter.path; - if (interpreter.displayName) { - // tslint:disable-next-line:no-string-literal - properties['Description'] = interpreter.displayName; + // tslint:disable-next-line:no-string-literal + properties['InterpreterPath'] = interpreterData.path; + // tslint:disable-next-line:no-string-literal + properties['Version'] = interpreterData.version; + // tslint:disable-next-line:no-string-literal + properties['PrefixPath'] = interpreterData.prefix; + // tslint:disable-next-line:no-string-literal + properties['DatabasePath'] = path.join(context.extensionPath, analysisEngineFolder); + + let searchPaths = interpreterData.searchPaths; + const settings = this.configuration.getSettings(); + if (settings.autoComplete) { + const extraPaths = settings.autoComplete.extraPaths; + if (extraPaths && extraPaths.length > 0) { + searchPaths = `${searchPaths};${extraPaths.join(';')}`; } - const interpreterData = await this.getInterpreterData(); - - // tslint:disable-next-line:no-string-literal - properties['Version'] = interpreterData.version; - // tslint:disable-next-line:no-string-literal - properties['PrefixPath'] = interpreterData.prefix; - // tslint:disable-next-line:no-string-literal - properties['DatabasePath'] = path.join(context.extensionPath, analysisEngineFolder); + } + // tslint:disable-next-line:no-string-literal + properties['SearchPaths'] = searchPaths; - let searchPaths = await this.getSearchPaths(); - const settings = this.configuration.getSettings(); - if (settings.autoComplete) { - const extraPaths = settings.autoComplete.extraPaths; - if (extraPaths && extraPaths.length > 0) { - searchPaths = `${searchPaths};${extraPaths.join(';')}`; - } - } + if (isTestExecution()) { // tslint:disable-next-line:no-string-literal - properties['SearchPaths'] = searchPaths; - - if (isTestExecution()) { - // tslint:disable-next-line:no-string-literal - properties['TestEnvironment'] = true; - } - } else { - const appShell = this.services.get(IApplicationShell); - const pythonPath = this.configuration.getSettings().pythonPath; - appShell.showErrorMessage(`Interpreter ${pythonPath} does not exist.`); - return; + properties['TestEnvironment'] = true; } const selector: string[] = [PYTHON]; @@ -188,80 +174,18 @@ export class AnalysisExtensionActivator implements IExtensionActivator { initializationOptions: { interpreter: { properties - } + }, + displayOptions: { + trimDocumentationLines: false, + maxDocumentationLineLength: 0, + trimDocumentationText: false, + maxDocumentationTextLength: 0 + }, + asyncStartup: true } }; } - private async getInterpreterData(): Promise { - // Not appropriate for multiroot workspaces. - // See https://github.com/Microsoft/vscode-python/issues/1149 - const execService = await this.executionFactory.create(); - const result = await execService.exec(['-c', 'import sys; print(sys.version_info); print(sys.prefix)'], {}); - // 2.7.14 (v2.7.14:84471935ed, Sep 16 2017, 20:19:30) <> - // [MSC v.1500 32 bit (Intel)] - // C:\Python27 - if (!result.stdout) { - throw Error('Unable to determine Python interpreter version and system prefix.'); - } - const output = result.stdout.splitLines({ removeEmptyEntries: true, trim: true }); - if (!output || output.length < 2) { - throw Error('Unable to parse version and and system prefix from the Python interpreter output.'); - } - const majorMatches = output[0].match(/major=(\d*?),/); - const minorMatches = output[0].match(/minor=(\d*?),/); - if (!majorMatches || majorMatches.length < 2 || !minorMatches || minorMatches.length < 2) { - throw Error('Unable to parse interpreter version.'); - } - const prefix = output[output.length - 1]; - return new InterpreterData(`${majorMatches[1]}.${minorMatches[1]}`, prefix); - } - - private async getSearchPaths(): Promise { - // Not appropriate for multiroot workspaces. - // See https://github.com/Microsoft/vscode-python/issues/1149 - const execService = await this.executionFactory.create(); - const result = await execService.exec(['-c', 'import sys; print(sys.path);'], {}); - if (!result.stdout) { - throw Error('Unable to determine Python interpreter search paths.'); - } - // tslint:disable-next-line:no-unnecessary-local-variable - const paths = result.stdout.split(',') - .filter(p => this.isValidPath(p)) - .map(p => this.pathCleanup(p)); - return paths.join(';'); - } - - private pathCleanup(s: string): string { - s = s.trim(); - if (s[0] === '\'') { - s = s.substr(1); - } - if (s[s.length - 1] === ']') { - s = s.substr(0, s.length - 1); - } - if (s[s.length - 1] === '\'') { - s = s.substr(0, s.length - 1); - } - return s; - } - - private isValidPath(s: string): boolean { - return s.length > 0 && s[0] !== '['; - } - - // private async checkNetCoreRuntime(): Promise { - // if (!await this.isDotNetInstalled()) { - // const appShell = this.services.get(IApplicationShell); - // if (await appShell.showErrorMessage('Python Tools require .NET Core Runtime. Would you like to install it now?', 'Yes', 'No') === 'Yes') { - // appShell.openUrl('https://www.microsoft.com/net/download/core#/runtime'); - // appShell.showWarningMessage('Please restart VS Code after .NET Runtime installation is complete.'); - // } - // return false; - // } - // return true; - // } - private async isDotNetInstalled(): Promise { const ps = this.services.get(IProcessService); const result = await ps.exec('dotnet', ['--version']).catch(() => { return { stdout: '' }; }); diff --git a/src/client/activation/interpreterDataService.ts b/src/client/activation/interpreterDataService.ts new file mode 100644 index 000000000000..45cf9749e6cf --- /dev/null +++ b/src/client/activation/interpreterDataService.ts @@ -0,0 +1,146 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { createHash } from 'crypto'; +import * as fs from 'fs'; +import * as path from 'path'; +import { ExtensionContext, Uri } from 'vscode'; +import { IApplicationShell } from '../common/application/types'; +import '../common/extensions'; +import { createDeferred } from '../common/helpers'; +import { IPlatformService } from '../common/platform/types'; +import { IPythonExecutionFactory, IPythonExecutionService } from '../common/process/types'; +import { IServiceContainer } from '../ioc/types'; + +const DataVersion = 1; + +export class InterpreterData { + constructor( + public readonly dataVersion: number, + // tslint:disable-next-line:no-shadowed-variable + public readonly path: string, + public readonly version: string, + public readonly prefix: string, + public readonly searchPaths: string, + public readonly hash: string + ) { } +} + +export class InterpreterDataService { + constructor( + private readonly context: ExtensionContext, + private readonly serviceContainer: IServiceContainer) { } + + public async getInterpreterData(resource?: Uri): Promise { + const executionFactory = this.serviceContainer.get(IPythonExecutionFactory); + const execService = await executionFactory.create(resource); + + const interpreterPath = await execService.getExecutablePath(); + if (interpreterPath.length === 0) { + return; + } + + const cacheKey = `InterpreterData-${interpreterPath}`; + let interpreterData = this.context.globalState.get(cacheKey) as InterpreterData; + let interpreterChanged = false; + if (interpreterData) { + // Check if interpreter executable changed + if (interpreterData.dataVersion !== DataVersion) { + interpreterChanged = true; + } else { + const currentHash = await this.getInterpreterHash(interpreterPath); + interpreterChanged = currentHash !== interpreterData.hash; + } + } + + if (interpreterChanged || !interpreterData) { + interpreterData = await this.getInterpreterDataFromPython(execService, interpreterPath); + this.context.globalState.update(interpreterPath, interpreterData); + } else { + // Make sure we verify that search paths did not change. This must be done + // completely async so we don't delay Python language server startup. + this.verifySearchPaths(interpreterData.searchPaths, interpreterPath, execService); + } + return interpreterData; + } + + private async getInterpreterDataFromPython(execService: IPythonExecutionService, interpreterPath: string): Promise { + const result = await execService.exec(['-c', 'import sys; print(sys.version_info); print(sys.prefix)'], {}); + // 2.7.14 (v2.7.14:84471935ed, Sep 16 2017, 20:19:30) <> + // [MSC v.1500 32 bit (Intel)] + // C:\Python27 + if (!result.stdout) { + throw Error('Unable to determine Python interpreter version and system prefix.'); + } + const output = result.stdout.splitLines({ removeEmptyEntries: true, trim: true }); + if (!output || output.length < 2) { + throw Error('Unable to parse version and and system prefix from the Python interpreter output.'); + } + const majorMatches = output[0].match(/major=(\d*?),/); + const minorMatches = output[0].match(/minor=(\d*?),/); + if (!majorMatches || majorMatches.length < 2 || !minorMatches || minorMatches.length < 2) { + throw Error('Unable to parse interpreter version.'); + } + const prefix = output[output.length - 1]; + const hash = await this.getInterpreterHash(interpreterPath); + const searchPaths = await this.getSearchPaths(execService); + return new InterpreterData(DataVersion, interpreterPath, `${majorMatches[1]}.${minorMatches[1]}`, prefix, searchPaths, hash); + } + + private getInterpreterHash(interpreterPath: string): Promise { + const platform = this.serviceContainer.get(IPlatformService); + const pythonExecutable = path.join(path.dirname(interpreterPath), platform.isWindows ? 'python.exe' : 'python'); + // Hash mod time and creation time + const deferred = createDeferred(); + fs.lstat(pythonExecutable, (err, stats) => { + if (err) { + deferred.resolve(''); + } else { + const actual = createHash('sha512').update(`${stats.ctimeMs}-${stats.mtimeMs}`).digest('hex'); + deferred.resolve(actual); + } + }); + return deferred.promise; + } + + private async getSearchPaths(execService: IPythonExecutionService): Promise { + const result = await execService.exec(['-c', 'import sys; print(sys.path);'], {}); + if (!result.stdout) { + throw Error('Unable to determine Python interpreter search paths.'); + } + // tslint:disable-next-line:no-unnecessary-local-variable + const paths = result.stdout.split(',') + .filter(p => this.isValidPath(p)) + .map(p => this.pathCleanup(p)); + return paths.join(';'); // PTVS uses ; on all platforms + } + + private pathCleanup(s: string): string { + s = s.trim(); + if (s[0] === '\'') { + s = s.substr(1); + } + if (s[s.length - 1] === ']') { + s = s.substr(0, s.length - 1); + } + if (s[s.length - 1] === '\'') { + s = s.substr(0, s.length - 1); + } + return s; + } + + private isValidPath(s: string): boolean { + return s.length > 0 && s[0] !== '['; + } + + private verifySearchPaths(currentPaths: string, interpreterPath: string, execService: IPythonExecutionService): void { + this.getSearchPaths(execService) + .then(async paths => { + if (paths !== currentPaths) { + this.context.globalState.update(interpreterPath, undefined); + const appShell = this.serviceContainer.get(IApplicationShell); + await appShell.showWarningMessage('Search paths have changed for this Python interpreter. Please reload the extension to ensure that the IntelliSense works correctly.'); + } + }).ignoreErrors(); + } +} diff --git a/src/client/common/configuration/service.ts b/src/client/common/configuration/service.ts index e5d862930ca3..fe84a3de9d5c 100644 --- a/src/client/common/configuration/service.ts +++ b/src/client/common/configuration/service.ts @@ -1,19 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { inject, injectable } from 'inversify'; +import { injectable } from 'inversify'; import { ConfigurationTarget, Uri, workspace, WorkspaceConfiguration } from 'vscode'; -import { IServiceContainer } from '../../ioc/types'; -import { IApplicationShell } from '../application/types'; import { PythonSettings } from '../configSettings'; -import { IProcessService } from '../process/types'; import { IConfigurationService, IPythonSettings } from '../types'; @injectable() export class ConfigurationService implements IConfigurationService { - constructor(@inject(IServiceContainer) private services: IServiceContainer) { - } - public getSettings(resource?: Uri): IPythonSettings { return PythonSettings.getInstance(resource); } @@ -39,10 +33,6 @@ export class ConfigurationService implements IConfigurationService { return process.env.VSC_PYTHON_CI_TEST === '1'; } - public async checkDependencies(): Promise { - return this.checkDotNet(); - } - private async verifySetting(pythonConfig: WorkspaceConfiguration, target: ConfigurationTarget, settingName: string, value?: {}): Promise { if (this.isTestExecution()) { let retries = 0; @@ -66,22 +56,4 @@ export class ConfigurationService implements IConfigurationService { } while (retries < 20); } } - - private async checkDotNet(): Promise { - if (!await this.isDotNetInstalled()) { - const appShell = this.services.get(IApplicationShell); - if (await appShell.showErrorMessage('Python Tools require .NET Core Runtime. Would you like to install it now?', 'Yes', 'No') === 'Yes') { - appShell.openUrl('https://www.microsoft.com/net/download/core#/runtime'); - appShell.showWarningMessage('Please restart VS Code after .NET Runtime installation is complete.'); - } - return false; - } - return true; - } - - private async isDotNetInstalled(): Promise { - const ps = this.services.get(IProcessService); - const result = await ps.exec('dotnet', ['--version']); - return result.stdout.trim().startsWith('2.'); - } } diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 266d1a812cb0..f64617178288 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -219,7 +219,6 @@ export interface IConfigurationService { getSettings(resource?: Uri): IPythonSettings; isTestExecution(): boolean; updateSettingAsync(setting: string, value?: {}, resource?: Uri, configTarget?: ConfigurationTarget): Promise; - checkDependencies(): Promise; } export const ISocketServer = Symbol('ISocketServer'); From f4b1457041e74cd4ef9b0c82b92459e977edabcb Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 5 Apr 2018 10:45:02 -0700 Subject: [PATCH 096/433] Require a 'feature' label --- CONTRIBUTING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9e066b582c40..6d7fb4afeda3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,7 @@ [![Build Status (Travis)](https://travis-ci.org/Microsoft/vscode-python.svg?branch=master)](https://travis-ci.org/Microsoft/vscode-python) [![Build status (AppVeyor)](https://ci.appveyor.com/api/projects/status/s0pt8d79gqw222j7?svg=true)](https://ci.appveyor.com/project/DonJayamanne/vscode-python-v3vd6) [![codecov](https://codecov.io/gh/Microsoft/vscode-python/branch/master/graph/badge.svg)](https://codecov.io/gh/Microsoft/vscode-python) # Contributing to Microsoft Python Analysis Engine -[![Contributing to Python Analysis Engine](https://github.com/Microsoft/vscode-python/blob/master/CONTRIBUTING - PYTHON_ANALYSIS.md)] +[Contributing to Python Analysis Engine](https://github.com/Microsoft/vscode-python/blob/master/CONTRIBUTING - PYTHON_ANALYSIS.md) ## Contributing a pull request @@ -118,11 +118,11 @@ When an it is triaged to contain at least two types of labels: 1. `needs` +1. `feature` 1. `type` -These labels cover what is blocking the issue from closing and what kind of -issue it is. We also add a `feature` label when appropriate for what the issue -relates to. +These labels cover what is blocking the issue from closing, what is affected by +the issue, and what kind of issue it is. #### Closed issues From f97cf029cff8e56a4b0aedcb44bc34a80f061e8e Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 5 Apr 2018 10:46:20 -0700 Subject: [PATCH 097/433] Fix a link --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6d7fb4afeda3..67aa7cdce4fe 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,7 @@ [![Build Status (Travis)](https://travis-ci.org/Microsoft/vscode-python.svg?branch=master)](https://travis-ci.org/Microsoft/vscode-python) [![Build status (AppVeyor)](https://ci.appveyor.com/api/projects/status/s0pt8d79gqw222j7?svg=true)](https://ci.appveyor.com/project/DonJayamanne/vscode-python-v3vd6) [![codecov](https://codecov.io/gh/Microsoft/vscode-python/branch/master/graph/badge.svg)](https://codecov.io/gh/Microsoft/vscode-python) # Contributing to Microsoft Python Analysis Engine -[Contributing to Python Analysis Engine](https://github.com/Microsoft/vscode-python/blob/master/CONTRIBUTING - PYTHON_ANALYSIS.md) +[Contributing to Python Analysis Engine](https://github.com/Microsoft/vscode-python/blob/master/CONTRIBUTING%20-%20PYTHON_ANALYSIS.md) ## Contributing a pull request From 3cbfca9d67d4488ec9106eb95f50dac227b4edf7 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 5 Apr 2018 12:35:01 -0700 Subject: [PATCH 098/433] Exclude appveyor config (#1287) --- appveyor.yml => .appveyor.yml | 0 .vscodeignore | 72 +++++++++++++++++------------------ 2 files changed, 36 insertions(+), 36 deletions(-) rename appveyor.yml => .appveyor.yml (100%) diff --git a/appveyor.yml b/.appveyor.yml similarity index 100% rename from appveyor.yml rename to .appveyor.yml diff --git a/.vscodeignore b/.vscodeignore index b762bf75dfc7..ecff38de7902 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -1,47 +1,47 @@ -.vscode/** -.vscode-test/** -.github/** -.nvm/** -typings/** -out/test/** -out/src/** -out/pythonFiles/** -out/testMultiRootWkspc/** -out/coverconfig.json -test/** -src/** -scripts/** **/*.map -.gitignore -.gitmodules +.appveyor.yml .editorconfig .eslintrc .gitattributes -images/**/*.gif -images/**/*.png -tsconfig.json -typings.json -coverconfig.json -tslint.json -tsfmt.json -gulpfile.js -pythonFiles/**/*.pyc -requirements.txt -vsc-extension-quickstart.md +.gitignore +.gitmodules .travis.yml -webpack.config.js -yarn.lock -coverage/** CODE_OF_CONDUCT.md CODING_STANDARDS.md CONTRIBUTING.md -news/** -debug_coverage*/** -analysis/publish*.* -vscode-python-signing.* +coverconfig.json +gulpfile.js packageExtension.cmd +tsconfig.json +tsfmt.json +tslint.json +typings.json +vscode-python-signing.* +webpack.config.js +yarn.lock + +.github/** +.nvm/** +.vscode/** +.vscode-test/** +analysis/publish*.* bin/** -obj/** BuildOutput/** - - +coverage/** +debug_coverage*/** +images/**/*.gif +images/**/*.png +news/** +obj/** +out/coverconfig.json +out/pythonFiles/** +out/src/** +out/test/** +out/testMultiRootWkspc/** +pythonFiles/**/*.pyc +requirements.txt +scripts/** +src/** +test/** +typings/** +vsc-extension-quickstart.md From 816f3ea96ba14f3971779098d4a3cb51fd84b693 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Thu, 5 Apr 2018 16:00:29 -0700 Subject: [PATCH 099/433] Fix indentation on single-liners and f-strings in type formatting (#1312) * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * #1096 The if statement is automatically formatted incorrectly * Merge fix * Add more tests * More tests * Typo * Test * Also better handle multiline arguments * Add a couple missing periods [skip ci] * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * Merge fix * #1257 On type formatting errors for args and kwargs * Handle f-strings * Stop importing from test code * #1308 Single line statements leading to an indentation on the next line * #726 editing python after inline if statement invalid indent * Undo change * Move constant --- src/client/common/constants.ts | 3 ++ src/client/extension.ts | 11 ++++-- src/client/formatters/lineFormatter.ts | 13 +++++-- src/client/language/tokenizer.ts | 12 ++++-- .../format/extension.lineFormatter.test.ts | 6 ++- src/test/language/tokenizer.test.ts | 37 +++++++++++++++++++ 6 files changed, 71 insertions(+), 11 deletions(-) diff --git a/src/client/common/constants.ts b/src/client/common/constants.ts index 2995452cb2f7..de0c7d260a9f 100644 --- a/src/client/common/constants.ts +++ b/src/client/common/constants.ts @@ -69,5 +69,8 @@ export function isTestExecution(): boolean { // tslint:disable-next-line:interface-name no-string-literal return process.env['VSC_PYTHON_CI_TEST'] === '1'; } +export function isPythonAnalysisEngineTest(): boolean { + return process.env.VSC_PYTHON_ANALYSIS === '1'; +} export const EXTENSION_ROOT_DIR = path.join(__dirname, '..', '..', '..'); diff --git a/src/client/extension.ts b/src/client/extension.ts index e86959d62255..04457bb99a15 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -11,12 +11,11 @@ import { extensions, IndentAction, languages, Memento, OutputChannel, window } from 'vscode'; -import { IS_ANALYSIS_ENGINE_TEST } from '../test/constants'; import { AnalysisExtensionActivator } from './activation/analysis'; import { ClassicExtensionActivator } from './activation/classic'; import { IExtensionActivator } from './activation/types'; import { PythonSettings } from './common/configSettings'; -import { STANDARD_OUTPUT_CHANNEL } from './common/constants'; +import { isPythonAnalysisEngineTest, STANDARD_OUTPUT_CHANNEL } from './common/constants'; import { FeatureDeprecationManager } from './common/featureDeprecationManager'; import { createDeferred } from './common/helpers'; import { PythonInstaller } from './common/installer/pythonInstallation'; @@ -75,7 +74,7 @@ export async function activate(context: ExtensionContext) { const configuration = serviceManager.get(IConfigurationService); const pythonSettings = configuration.getSettings(); - const activator: IExtensionActivator = IS_ANALYSIS_ENGINE_TEST || !pythonSettings.jediEnabled + const activator: IExtensionActivator = isPythonAnalysisEngineTest() || !pythonSettings.jediEnabled ? new AnalysisExtensionActivator(serviceManager, pythonSettings) : new ClassicExtensionActivator(serviceManager, pythonSettings); @@ -108,7 +107,11 @@ export async function activate(context: ExtensionContext) { languages.setLanguageConfiguration(PYTHON.language!, { onEnterRules: [ { - beforeText: /^\s*(?:def|class|for|if|elif|else|while|try|with|finally|except|async)\b.*/, + beforeText: /^\s*(?:def|class|for|if|elif|else|while|try|with|finally|except)\b.*:\s*\S+/, + action: { indentAction: IndentAction.None } + }, + { + beforeText: /^\s*(?:def|class|for|if|elif|else|while|try|with|finally|except|async)\b.*:\s*/, action: { indentAction: IndentAction.Indent } }, { diff --git a/src/client/formatters/lineFormatter.ts b/src/client/formatters/lineFormatter.ts index fc347235a525..046533952464 100644 --- a/src/client/formatters/lineFormatter.ts +++ b/src/client/formatters/lineFormatter.ts @@ -95,15 +95,22 @@ export class LineFormatter { } break; case Char.Period: - this.builder.append('.'); - return; case Char.At: - this.builder.append('@'); + case Char.ExclamationMark: + this.builder.append(this.text[t.start]); return; default: break; } } + // Do not append space if operator is preceded by '(' or ',' as in foo(**kwarg) + if (index > 0) { + const prev = this.tokens.getItemAt(index - 1); + if (this.isOpenBraceType(prev.type) || prev.type === TokenType.Comma) { + this.builder.append(this.text.substring(t.start, t.end)); + return; + } + } this.builder.softAppendSpace(); this.builder.append(this.text.substring(t.start, t.end)); this.builder.softAppendSpace(); diff --git a/src/client/language/tokenizer.ts b/src/client/language/tokenizer.ts index c481c4201ac0..fcb29ed8b9a3 100644 --- a/src/client/language/tokenizer.ts +++ b/src/client/language/tokenizer.ts @@ -85,10 +85,16 @@ export class Tokenizer implements ITokenizer { } } + // tslint:disable-next-line:cyclomatic-complexity private handleCharacter(): boolean { + // f-strings + const fString = this.cs.currentChar === Char.f && (this.cs.nextChar === Char.SingleQuote || this.cs.nextChar === Char.DoubleQuote); + if (fString) { + this.cs.moveNext(); + } const quoteType = this.getQuoteType(); if (quoteType !== QuoteType.None) { - this.handleString(quoteType); + this.handleString(quoteType, fString); return true; } if (this.cs.currentChar === Char.Hash) { @@ -342,8 +348,8 @@ export class Tokenizer implements ITokenizer { return QuoteType.None; } - private handleString(quoteType: QuoteType): void { - const start = this.cs.position; + private handleString(quoteType: QuoteType, fString: boolean): void { + const start = fString ? this.cs.position - 1 : this.cs.position; if (quoteType === QuoteType.Single || quoteType === QuoteType.Double) { this.cs.moveNext(); this.skipToSingleEndQuote(quoteType === QuoteType.Single diff --git a/src/test/format/extension.lineFormatter.test.ts b/src/test/format/extension.lineFormatter.test.ts index 79de72c5774a..3325c19382a2 100644 --- a/src/test/format/extension.lineFormatter.test.ts +++ b/src/test/format/extension.lineFormatter.test.ts @@ -73,7 +73,7 @@ suite('Formatting - line formatter', () => { const actual = formatter.formatLine('foo(x,y= \"a\",'); assert.equal(actual, 'foo(x, y=\"a\",'); }); - test('Equals in multiline arguments', () => { + test('Equals in multiline arguments', () => { const actual = formatter.formatLine('x = 1,y =-2)'); assert.equal(actual, 'x=1, y=-2)'); }); @@ -81,4 +81,8 @@ suite('Formatting - line formatter', () => { const actual = formatter.formatLine(',x = 1,y =m)'); assert.equal(actual, ', x=1, y=m)'); }); + test('Operators without following space', () => { + const actual = formatter.formatLine('foo( *a, ** b, ! c)'); + assert.equal(actual, 'foo(*a, **b, !c)'); + }); }); diff --git a/src/test/language/tokenizer.test.ts b/src/test/language/tokenizer.test.ts index 1d2bf15d2b7b..8d37f49dd791 100644 --- a/src/test/language/tokenizer.test.ts +++ b/src/test/language/tokenizer.test.ts @@ -79,6 +79,43 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(0).type, TokenType.String); assert.equal(tokens.getItemAt(0).length, 12); }); + test('Strings: single quoted f-string ', async () => { + const t = new Tokenizer(); + // tslint:disable-next-line:quotemark + const tokens = t.tokenize("a+f'quoted'"); + assert.equal(tokens.count, 3); + assert.equal(tokens.getItemAt(0).type, TokenType.Identifier); + assert.equal(tokens.getItemAt(1).type, TokenType.Operator); + assert.equal(tokens.getItemAt(2).type, TokenType.String); + assert.equal(tokens.getItemAt(2).length, 9); + }); + test('Strings: double quoted f-string ', async () => { + const t = new Tokenizer(); + const tokens = t.tokenize('x(1,f"quoted")'); + assert.equal(tokens.count, 6); + assert.equal(tokens.getItemAt(0).type, TokenType.Identifier); + assert.equal(tokens.getItemAt(1).type, TokenType.OpenBrace); + assert.equal(tokens.getItemAt(2).type, TokenType.Number); + assert.equal(tokens.getItemAt(3).type, TokenType.Comma); + assert.equal(tokens.getItemAt(4).type, TokenType.String); + assert.equal(tokens.getItemAt(4).length, 9); + assert.equal(tokens.getItemAt(5).type, TokenType.CloseBrace); + }); + test('Strings: single quoted multiline f-string ', async () => { + const t = new Tokenizer(); + // tslint:disable-next-line:quotemark + const tokens = t.tokenize("f'''quoted'''"); + assert.equal(tokens.count, 1); + assert.equal(tokens.getItemAt(0).type, TokenType.String); + assert.equal(tokens.getItemAt(0).length, 13); + }); + test('Strings: double quoted multiline f-string ', async () => { + const t = new Tokenizer(); + const tokens = t.tokenize('f"""quoted """'); + assert.equal(tokens.count, 1); + assert.equal(tokens.getItemAt(0).type, TokenType.String); + assert.equal(tokens.getItemAt(0).length, 14); + }); test('Comments', async () => { const t = new Tokenizer(); const tokens = t.tokenize(' #co"""mment1\n\t\n#comm\'ent2 '); From b3776eb68ee1671bab93de7ebe2cf960b2c9d571 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 5 Apr 2018 19:11:26 -0700 Subject: [PATCH 100/433] Add support for mapping of local and remote paths in remote debugging (#1300) Fixes #1289 --- package.json | 25 +++++++ src/client/common/net/socket/socketServer.ts | 10 +-- src/client/debugger/Common/Contracts.ts | 2 + .../DebugServers/RemoteDebugServerv2.ts | 15 +++-- .../configProviders/pythonV2Provider.ts | 14 +++- src/test/autocomplete/base.test.ts | 10 ++- src/test/debugger/attach.ptvsd.test.ts | 66 +++++++++++++------ src/test/debugger/capabilities.test.ts | 34 +++++----- src/test/initialize.ts | 14 ++-- 9 files changed, 131 insertions(+), 59 deletions(-) diff --git a/package.json b/package.json index 13d8ab4506b0..26d728dd09ba 100644 --- a/package.json +++ b/package.json @@ -1017,6 +1017,31 @@ }, "default": [] }, + "pathMappings": { + "type": "array", + "label": "Additional path mappings.", + "items": { + "type": "object", + "label": "Path mapping", + "required": [ + "localRoot", + "remoteRoot" + ], + "properties": { + "localRoot": { + "type": "string", + "label": "Local source root.", + "default": "" + }, + "remoteRoot": { + "type": "string", + "label": "Remote source root.", + "default": "" + } + } + }, + "default": [] + }, "logToFile": { "type": "boolean", "description": "Enable logging of debugger events to a log file.", diff --git a/src/client/common/net/socket/socketServer.ts b/src/client/common/net/socket/socketServer.ts index 24e7b2713740..74099fd7cffe 100644 --- a/src/client/common/net/socket/socketServer.ts +++ b/src/client/common/net/socket/socketServer.ts @@ -28,22 +28,22 @@ export class SocketServer extends EventEmitter implements ISocketServer { this.socketServer = undefined; } - public Start(options: { port?: number, host?: string } = {}): Promise { + public Start(options: { port?: number; host?: string } = {}): Promise { const def = createDeferred(); this.socketServer = net.createServer(this.connectionListener.bind(this)); const port = typeof options.port === 'number' ? options.port! : 0; const host = typeof options.host === 'string' ? options.host! : 'localhost'; - this.socketServer!.listen({ port, host }, () => { - def.resolve(this.socketServer!.address().port); - }); - this.socketServer!.on('error', ex => { console.error('Error in Socket Server', ex); const msg = `Failed to start the socket server. (Error: ${ex.message})`; def.reject(msg); }); + this.socketServer!.listen({ port, host }, () => { + def.resolve(this.socketServer!.address().port); + }); + return def.promise; } diff --git a/src/client/debugger/Common/Contracts.ts b/src/client/debugger/Common/Contracts.ts index 30011ef88e6a..85bc8d7f4a2c 100644 --- a/src/client/debugger/Common/Contracts.ts +++ b/src/client/debugger/Common/Contracts.ts @@ -88,6 +88,8 @@ export interface AttachRequestArguments extends DebugProtocol.AttachRequestArgum host?: string; secret?: string; logToFile?: boolean; + pathMappings?: { localRoot: string; remoteRoot: string }[]; + debugOptions?: DebugOptions[]; } export interface IDebugServer { diff --git a/src/client/debugger/DebugServers/RemoteDebugServerv2.ts b/src/client/debugger/DebugServers/RemoteDebugServerv2.ts index be400c12c5ea..ff6ff06f9a40 100644 --- a/src/client/debugger/DebugServers/RemoteDebugServerv2.ts +++ b/src/client/debugger/DebugServers/RemoteDebugServerv2.ts @@ -3,7 +3,7 @@ 'use strict'; -import { connect, Socket } from 'net'; +import { Socket } from 'net'; import { DebugSession } from 'vscode-debugadapter'; import { AttachRequestArguments, IDebugServer, IPythonProcess } from '../Common/Contracts'; import { BaseDebugServer } from './BaseDebugServer'; @@ -31,18 +31,19 @@ export class RemoteDebugServerV2 extends BaseDebugServer { } try { let connected = false; - const socket = connect(options, () => { - connected = true; - this.socket = socket; - this.clientSocket.resolve(socket); - resolve(options); - }); + const socket = new Socket(); socket.on('error', ex => { if (connected) { return; } reject(ex); }); + socket.connect(options, () => { + connected = true; + this.socket = socket; + this.clientSocket.resolve(socket); + resolve(options); + }); } catch (ex) { reject(ex); } diff --git a/src/client/debugger/configProviders/pythonV2Provider.ts b/src/client/debugger/configProviders/pythonV2Provider.ts index d356901f45ab..4c6b74d7faac 100644 --- a/src/client/debugger/configProviders/pythonV2Provider.ts +++ b/src/client/debugger/configProviders/pythonV2Provider.ts @@ -35,9 +35,19 @@ export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvide debugConfiguration.debugOptions = Array.isArray(debugConfiguration.debugOptions) ? debugConfiguration.debugOptions : []; - // Add PTVSD specific flags. - if (this.serviceContainer.get(IPlatformService).isWindows) { + // We'll need paths to be fixed only in the case where local and remote hosts are the same + // I.e. only if hostName === 'localhost' or '127.0.0.1' or '' + const isLocalHost = !debugConfiguration.host || debugConfiguration.host === 'localhost' || debugConfiguration.host === '127.0.0.1'; + if (this.serviceContainer.get(IPlatformService).isWindows && isLocalHost) { debugConfiguration.debugOptions.push(DebugOptions.FixFilePathCase); } + + if (!debugConfiguration.pathMappings) { + debugConfiguration.pathMappings = []; + } + debugConfiguration.pathMappings!.push({ + localRoot: debugConfiguration.localRoot, + remoteRoot: debugConfiguration.remoteRoot + }); } } diff --git a/src/test/autocomplete/base.test.ts b/src/test/autocomplete/base.test.ts index 5219e4ababd9..f3289e359dfd 100644 --- a/src/test/autocomplete/base.test.ts +++ b/src/test/autocomplete/base.test.ts @@ -22,11 +22,17 @@ const fileEncodingUsed = path.join(autoCompPath, 'five.py'); const fileSuppress = path.join(autoCompPath, 'suppress.py'); // tslint:disable-next-line:max-func-body-length -suite('Autocomplete', () => { +suite('Autocomplete', function () { + // Attempt to fix #1301 + // tslint:disable-next-line:no-invalid-this + this.timeout(60000); let isPython2: boolean; let ioc: UnitTestIocContainer; - suiteSetup(async () => { + suiteSetup(async function () { + // Attempt to fix #1301 + // tslint:disable-next-line:no-invalid-this + this.timeout(60000); await initialize(); initializeDI(); isPython2 = await ioc.getPythonMajorVersion(rootWorkspaceUri) === 2; diff --git a/src/test/debugger/attach.ptvsd.test.ts b/src/test/debugger/attach.ptvsd.test.ts index 76ccd4944034..87d7e77de17d 100644 --- a/src/test/debugger/attach.ptvsd.test.ts +++ b/src/test/debugger/attach.ptvsd.test.ts @@ -8,26 +8,33 @@ import { ChildProcess, spawn } from 'child_process'; import * as getFreePort from 'get-port'; import * as path from 'path'; +import * as TypeMoq from 'typemoq'; +import { DebugConfiguration, Uri } from 'vscode'; import { DebugClient } from 'vscode-debugadapter-testsupport'; import { EXTENSION_ROOT_DIR } from '../../client/common/constants'; import '../../client/common/extensions'; +import { IS_WINDOWS } from '../../client/common/platform/constants'; +import { IPlatformService } from '../../client/common/platform/types'; +import { PythonV2DebugConfigurationProvider } from '../../client/debugger'; import { PTVSD_PATH } from '../../client/debugger/Common/constants'; -import { DebugOptions } from '../../client/debugger/Common/Contracts'; +import { AttachRequestArguments, DebugOptions } from '../../client/debugger/Common/Contracts'; +import { IServiceContainer } from '../../client/ioc/types'; import { sleep } from '../common'; -import { initialize, IS_APPVEYOR, IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; +import { initialize, IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; import { continueDebugging, createDebugAdapter } from './utils'; const fileToDebug = path.join(EXTENSION_ROOT_DIR, 'src', 'testMultiRootWkspc', 'workspace5', 'remoteDebugger-start-with-ptvsd.py'); suite('Attach Debugger - Experimental', () => { let debugClient: DebugClient; - let procToKill: ChildProcess; + let proc: ChildProcess; suiteSetup(initialize); setup(async function () { if (!IS_MULTI_ROOT_TEST || !TEST_DEBUGGER) { this.skip(); } + this.timeout(30000); const coverageDirectory = path.join(EXTENSION_ROOT_DIR, 'debug_coverage_attach_ptvsd'); debugClient = await createDebugAdapter(coverageDirectory); }); @@ -37,27 +44,23 @@ suite('Attach Debugger - Experimental', () => { try { await debugClient.stop().catch(() => { }); } catch (ex) { } - if (procToKill) { + if (proc) { try { - procToKill.kill(); + proc.kill(); } catch { } } }); - test('Confirm we are able to attach to a running program', async function () { - this.timeout(20000); - // Lets skip this test on AppVeyor (very flaky on AppVeyor). - if (IS_APPVEYOR) { - return; - } - + async function testAttachingToRemoteProcess(localRoot: string, remoteRoot: string, isLocalHostWindows: boolean) { + const localHostPathSeparator = isLocalHostWindows ? '\\' : '/'; const port = await getFreePort({ host: 'localhost', port: 3000 }); - const customEnv = { ...process.env }; + const env = { ...process.env }; // Set the path for PTVSD to be picked up. // tslint:disable-next-line:no-string-literal - customEnv['PYTHONPATH'] = PTVSD_PATH; + env['PYTHONPATH'] = PTVSD_PATH; const pythonArgs = ['-m', 'ptvsd', '--server', '--port', `${port}`, '--file', fileToDebug.fileToCommandArgument()]; - procToKill = spawn('python', pythonArgs, { env: customEnv, cwd: path.dirname(fileToDebug) }); + proc = spawn('python', pythonArgs, { env: env, cwd: path.dirname(fileToDebug) }); + await sleep(3000); // Send initialize, attach const initializePromise = debugClient.initializeRequest({ @@ -69,15 +72,25 @@ suite('Attach Debugger - Experimental', () => { supportsVariableType: true, supportsVariablePaging: true }); - const attachPromise = debugClient.attachRequest({ - localRoot: path.dirname(fileToDebug), - remoteRoot: path.dirname(fileToDebug), + const options: AttachRequestArguments & DebugConfiguration = { + name: 'attach', + request: 'attach', + localRoot, + remoteRoot, type: 'pythonExperimental', port: port, host: 'localhost', - logToFile: false, + logToFile: true, debugOptions: [DebugOptions.RedirectOutput] - }); + }; + const platformService = TypeMoq.Mock.ofType(); + platformService.setup(p => p.isWindows).returns(() => isLocalHostWindows); + const serviceContainer = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(IPlatformService, TypeMoq.It.isAny())).returns(() => platformService.object); + const configProvider = new PythonV2DebugConfigurationProvider(serviceContainer.object); + + await configProvider.resolveDebugConfiguration({ index: 0, name: 'root', uri: Uri.file(localRoot) }, options); + const attachPromise = debugClient.attachRequest(options); await Promise.all([ initializePromise, @@ -90,7 +103,9 @@ suite('Attach Debugger - Experimental', () => { const stdOutPromise = debugClient.assertOutput('stdout', 'this is stdout'); const stdErrPromise = debugClient.assertOutput('stderr', 'this is stderr'); - const breakpointLocation = { path: fileToDebug, column: 1, line: 12 }; + // Don't use path utils, as we're building the paths manually (mimic windows paths on unix test servers and vice versa). + const localFileName = `${localRoot}${localHostPathSeparator}${path.basename(fileToDebug)}`; + const breakpointLocation = { path: localFileName, column: 1, line: 12 }; const breakpointPromise = debugClient.setBreakpointsRequest({ lines: [breakpointLocation.line], breakpoints: [{ line: breakpointLocation.line, column: breakpointLocation.column }], @@ -111,5 +126,14 @@ suite('Attach Debugger - Experimental', () => { debugClient.waitForEvent('exited'), debugClient.waitForEvent('terminated') ]); + } + test('Confirm we are able to attach to a running program', async () => { + await testAttachingToRemoteProcess(path.dirname(fileToDebug), path.dirname(fileToDebug), IS_WINDOWS); + }); + test('Confirm local and remote paths are translated', async () => { + // If tests are running on windows, then treat debug client as a unix client and remote process as current OS. + const isLocalHostWindows = !IS_WINDOWS; + const localWorkspace = isLocalHostWindows ? 'C:\\Project\\src' : '/home/user/Desktop/project/src'; + await testAttachingToRemoteProcess(localWorkspace, path.dirname(fileToDebug), isLocalHostWindows); }); }); diff --git a/src/test/debugger/capabilities.test.ts b/src/test/debugger/capabilities.test.ts index 0bc4005ee512..5f7f6f53c159 100644 --- a/src/test/debugger/capabilities.test.ts +++ b/src/test/debugger/capabilities.test.ts @@ -8,16 +8,18 @@ import { expect } from 'chai'; import { ChildProcess, spawn } from 'child_process'; import * as getFreePort from 'get-port'; -import { connect, Socket } from 'net'; +import { Socket } from 'net'; +import * as path from 'path'; import { PassThrough } from 'stream'; import { Message } from 'vscode-debugadapter/lib/messages'; import { DebugProtocol } from 'vscode-debugprotocol'; +import { EXTENSION_ROOT_DIR } from '../../client/common/constants'; +import { sleep } from '../../client/common/core.utils'; import { createDeferred } from '../../client/common/helpers'; import { PTVSD_PATH } from '../../client/debugger/Common/constants'; import { ProtocolParser } from '../../client/debugger/Common/protocolParser'; import { ProtocolMessageWriter } from '../../client/debugger/Common/protocolWriter'; import { PythonDebugger } from '../../client/debugger/mainV2'; -import { sleep } from '../common'; import { IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; class Request extends Message implements DebugProtocol.InitializeRequest { @@ -29,6 +31,8 @@ class Request extends Message implements DebugProtocol.InitializeRequest { } } +const fileToDebug = path.join(EXTENSION_ROOT_DIR, 'src', 'testMultiRootWkspc', 'workspace5', 'remoteDebugger-start-with-ptvsd.py'); + suite('Debugging - Capabilities', () => { let disposables: { dispose?: Function; destroy?: Function }[]; let proc: ChildProcess; @@ -36,6 +40,7 @@ suite('Debugging - Capabilities', () => { if (!IS_MULTI_ROOT_TEST || !TEST_DEBUGGER) { this.skip(); } + this.timeout(30000); disposables = []; }); teardown(() => { @@ -72,24 +77,17 @@ suite('Debugging - Capabilities', () => { const expectedResponse = await expectedResponsePromise; const host = 'localhost'; - const port = await getFreePort({ host }); + const port = await getFreePort({ host, port: 3000 }); const env = { ...process.env }; env.PYTHONPATH = PTVSD_PATH; - proc = spawn('python', ['-m', 'ptvsd', '--server', '--port', `${port}`, '--file', 'someFile.py'], { cwd: __dirname, env }); - // Wait for the socket server to start. - // Keep trying till we timeout. - let socket: Socket | undefined; - for (let index = 0; index < 1000; index += 1) { - try { - const connected = createDeferred(); - socket = connect({ port, host }, () => connected.resolve(socket)); - socket.on('error', connected.reject.bind(connected)); - await connected.promise; - break; - } catch { - await sleep(500); - } - } + proc = spawn('python', ['-m', 'ptvsd', '--server', '--port', `${port}`, '--file', fileToDebug], { cwd: path.dirname(fileToDebug), env }); + await sleep(3000); + + const connected = createDeferred(); + const socket = new Socket(); + socket.on('error', connected.reject.bind(connected)); + socket.connect({ port, host }, () => connected.resolve(socket)); + await connected.promise; const protocolParser = new ProtocolParser(); protocolParser.connect(socket!); disposables.push(protocolParser); diff --git a/src/test/initialize.ts b/src/test/initialize.ts index 5914d38185e9..0a428faa4dbd 100644 --- a/src/test/initialize.ts +++ b/src/test/initialize.ts @@ -42,11 +42,17 @@ export async function initializeTest(): Promise { // Dispose any cached python settings (used only in test env). PythonSettings.dispose(); } - export async function closeActiveWindows(): Promise { - return new Promise((resolve, reject) => vscode.commands.executeCommand('workbench.action.closeAllEditors') - // tslint:disable-next-line:no-unnecessary-callback-wrapper - .then(() => resolve(), reject)); + return new Promise((resolve, reject) => { + vscode.commands.executeCommand('workbench.action.closeAllEditors') + // tslint:disable-next-line:no-unnecessary-callback-wrapper + .then(() => resolve(), reject); + // Attempt to fix #1301. + // Lets not waste too much time. + setTimeout(() => { + reject(new Error('Command \'workbench.action.closeAllEditors\' timedout')); + }, 15000); + }); } function getPythonPath(): string { From 7bb2712f8137d1e8bc73b94c0a348fd31110bb5d Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 6 Apr 2018 09:59:43 -0700 Subject: [PATCH 101/433] Django and Flask template debugging tests (#1317) Fixes #1172 Fixes #1173 --- .travis.yml | 4 +- requirements.txt | 2 + src/test/common.ts | 11 ++ src/test/debugger/utils.ts | 3 + src/test/debugger/web.framework.test.ts | 148 ++++++++++++++++++ src/test/initialize.ts | 13 +- .../workspace5/djangoApp/home/__init__.py | 0 .../djangoApp/home/templates/index.html | 9 ++ .../workspace5/djangoApp/home/urls.py | 7 + .../workspace5/djangoApp/home/views.py | 10 ++ .../workspace5/djangoApp/manage.py | 22 +++ .../workspace5/djangoApp/mysite/__init__.py | 0 .../workspace5/djangoApp/mysite/settings.py | 93 +++++++++++ .../workspace5/djangoApp/mysite/urls.py | 23 +++ .../workspace5/djangoApp/mysite/wsgi.py | 16 ++ .../workspace5/flaskApp/run.py | 13 ++ .../workspace5/flaskApp/templates/index.html | 9 ++ 17 files changed, 369 insertions(+), 14 deletions(-) create mode 100644 src/test/debugger/web.framework.test.ts create mode 100644 src/testMultiRootWkspc/workspace5/djangoApp/home/__init__.py create mode 100644 src/testMultiRootWkspc/workspace5/djangoApp/home/templates/index.html create mode 100644 src/testMultiRootWkspc/workspace5/djangoApp/home/urls.py create mode 100644 src/testMultiRootWkspc/workspace5/djangoApp/home/views.py create mode 100644 src/testMultiRootWkspc/workspace5/djangoApp/manage.py create mode 100644 src/testMultiRootWkspc/workspace5/djangoApp/mysite/__init__.py create mode 100644 src/testMultiRootWkspc/workspace5/djangoApp/mysite/settings.py create mode 100644 src/testMultiRootWkspc/workspace5/djangoApp/mysite/urls.py create mode 100644 src/testMultiRootWkspc/workspace5/djangoApp/mysite/wsgi.py create mode 100644 src/testMultiRootWkspc/workspace5/flaskApp/run.py create mode 100644 src/testMultiRootWkspc/workspace5/flaskApp/templates/index.html diff --git a/.travis.yml b/.travis.yml index fee4079ea762..915c33dd9cad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,8 +36,8 @@ before_install: | yarn global add azure-cli export TRAVIS_PYTHON_PATH=`which python` install: - - pip install --upgrade -r requirements.txt - - pip install -t ./pythonFiles/experimental/ptvsd git+https://github.com/Microsoft/ptvsd/ + - python -m pip install --upgrade -r requirements.txt + - python -m pip install -t ./pythonFiles/experimental/ptvsd git+https://github.com/Microsoft/ptvsd/ - yarn script: diff --git a/requirements.txt b/requirements.txt index e7f764089ecf..439a8d6999b7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,3 +10,5 @@ pytest fabric numba rope +flask +django diff --git a/src/test/common.ts b/src/test/common.ts index e399012adf2c..5f6d6640e709 100644 --- a/src/test/common.ts +++ b/src/test/common.ts @@ -7,6 +7,8 @@ import { IS_MULTI_ROOT_TEST } from './initialize'; const fileInNonRootWorkspace = path.join(__dirname, '..', '..', 'src', 'test', 'pythonFiles', 'dummy.py'); export const rootWorkspaceUri = getWorkspaceRoot(); +export const PYTHON_PATH = getPythonPath(); + export type PythonSettingKeys = 'workspaceSymbols.enabled' | 'pythonPath' | 'linting.lintOnSave' | 'linting.enabled' | 'linting.pylintEnabled' | @@ -118,3 +120,12 @@ const globalPythonPathSetting = workspace.getConfiguration('python').inspect('py export const clearPythonPathInWorkspaceFolder = async (resource: string | Uri) => retryAsync(setPythonPathInWorkspace)(resource, ConfigurationTarget.WorkspaceFolder); export const setPythonPathInWorkspaceRoot = async (pythonPath: string) => retryAsync(setPythonPathInWorkspace)(undefined, ConfigurationTarget.Workspace, pythonPath); export const resetGlobalPythonPathSetting = async () => retryAsync(restoreGlobalPythonPathSetting)(); + +function getPythonPath(): string { + // tslint:disable-next-line:no-unsafe-any + if (process.env.TRAVIS_PYTHON_PATH && fs.existsSync(process.env.TRAVIS_PYTHON_PATH)) { + // tslint:disable-next-line:no-unsafe-any + return process.env.TRAVIS_PYTHON_PATH; + } + return 'python'; +} diff --git a/src/test/debugger/utils.ts b/src/test/debugger/utils.ts index ad59a2fd98e5..adc4a532221a 100644 --- a/src/test/debugger/utils.ts +++ b/src/test/debugger/utils.ts @@ -69,6 +69,9 @@ export async function validateVariablesInFrame(debugClient: DebugClient, export function makeHttpRequest(uri: string): Promise { return new Promise((resolve, reject) => { request.get(uri, (error: any, response: request.Response, body: any) => { + if (error) { + return reject(error); + } if (response.statusCode !== 200) { reject(new Error(`Status code = ${response.statusCode}`)); } else { diff --git a/src/test/debugger/web.framework.test.ts b/src/test/debugger/web.framework.test.ts new file mode 100644 index 000000000000..ca3ca6c6a19f --- /dev/null +++ b/src/test/debugger/web.framework.test.ts @@ -0,0 +1,148 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:no-suspicious-comment max-func-body-length no-invalid-this no-var-requires no-require-imports no-any no-http-string no-string-literal no-console + +import { expect } from 'chai'; +import * as getFreePort from 'get-port'; +import * as path from 'path'; +import { DebugClient } from 'vscode-debugadapter-testsupport'; +import { EXTENSION_ROOT_DIR } from '../../client/common/constants'; +import { noop } from '../../client/common/core.utils'; +import { DebugOptions, LaunchRequestArguments } from '../../client/debugger/Common/Contracts'; +import { PYTHON_PATH, sleep } from '../common'; +import { IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; +import { DEBUGGER_TIMEOUT } from './common/constants'; +import { continueDebugging, createDebugAdapter, ExpectedVariable, hitHttpBreakpoint, makeHttpRequest, validateVariablesInFrame } from './utils'; + +let testCounter = 0; +const debuggerType = 'pythonExperimental'; +suite(`Django and Flask Debugging: ${debuggerType}`, () => { + let debugClient: DebugClient; + setup(async function () { + if (!IS_MULTI_ROOT_TEST || !TEST_DEBUGGER) { + this.skip(); + } + this.timeout(5 * DEBUGGER_TIMEOUT); + const coverageDirectory = path.join(EXTENSION_ROOT_DIR, `debug_coverage_django_flask${testCounter += 1}`); + debugClient = await createDebugAdapter(coverageDirectory); + }); + teardown(async () => { + // Wait for a second before starting another test (sometimes, sockets take a while to get closed). + await sleep(1000); + try { + await debugClient.stop().catch(noop); + // tslint:disable-next-line:no-empty + } catch (ex) { } + await sleep(1000); + }); + function buildLaunchArgs(workspaceDirectory: string): LaunchRequestArguments { + const env = {}; + // tslint:disable-next-line:no-string-literal + env['PYTHONPATH'] = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'experimental', 'ptvsd'); + + // tslint:disable-next-line:no-unnecessary-local-variable + const options: LaunchRequestArguments = { + cwd: workspaceDirectory, + program: '', + debugOptions: [DebugOptions.RedirectOutput], + pythonPath: PYTHON_PATH, + args: [], + env, + envFile: '', + logToFile: true, + type: debuggerType + }; + + return options; + } + async function buildFlaskLaunchArgs(workspaceDirectory: string) { + const port = await getFreePort({ host: 'localhost' }); + const options = buildLaunchArgs(workspaceDirectory); + + options.env!['FLASK_APP'] = path.join(workspaceDirectory, 'run.py'); + options.module = 'flask'; + options.debugOptions = [DebugOptions.RedirectOutput, DebugOptions.Jinja]; + options.args = [ + 'run', + '--no-debugger', + '--no-reload', + '--port', + `${port}` + ]; + + return { options, port }; + } + async function buildDjangoLaunchArgs(workspaceDirectory: string) { + const port = await getFreePort({ host: 'localhost' }); + const options = buildLaunchArgs(workspaceDirectory); + + options.program = path.join(workspaceDirectory, 'manage.py'); + options.debugOptions = [DebugOptions.RedirectOutput, DebugOptions.Django]; + options.args = [ + 'runserver', + '--noreload', + '--nothreading', + `${port}` + ]; + + return { options, port }; + } + + async function testTemplateDebugging(launchArgs: LaunchRequestArguments, port: number, viewFile: string, viewLine: number, templateFile: string, templateLine: number) { + await Promise.all([ + debugClient.configurationSequence(), + debugClient.launch(launchArgs), + debugClient.waitForEvent('initialized'), + debugClient.waitForEvent('process'), + debugClient.waitForEvent('thread') + ]); + + const httpResult = await makeHttpRequest(`http://localhost:${port}`); + + expect(httpResult).to.contain('Hello this_is_a_value_from_server'); + expect(httpResult).to.contain('Hello this_is_another_value_from_server'); + + await hitHttpBreakpoint(debugClient, `http://localhost:${port}`, viewFile, viewLine); + + await continueDebugging(debugClient); + await debugClient.setBreakpointsRequest({ breakpoints: [], lines: [], source: { path: viewFile } }); + + // Template debugging. + const [stackTrace, htmlResultPromise] = await hitHttpBreakpoint(debugClient, `http://localhost:${port}`, templateFile, templateLine); + + // Wait for breakpoint to hit + const expectedVariables: ExpectedVariable[] = [ + { name: 'value_from_server', type: 'str', value: '\'this_is_a_value_from_server\'' }, + { name: 'another_value_from_server', type: 'str', value: '\'this_is_another_value_from_server\'' } + ]; + await validateVariablesInFrame(debugClient, stackTrace, expectedVariables, 1); + + await debugClient.setBreakpointsRequest({ breakpoints: [], lines: [], source: { path: templateFile } }); + await continueDebugging(debugClient); + + const htmlResult = await htmlResultPromise; + expect(htmlResult).to.contain('Hello this_is_a_value_from_server'); + expect(htmlResult).to.contain('Hello this_is_another_value_from_server'); + } + + test('Test Flask Route and Template debugging', async () => { + const workspaceDirectory = path.join(EXTENSION_ROOT_DIR, 'src', 'testMultiRootWkspc', 'workspace5', 'flaskApp'); + const { options, port } = await buildFlaskLaunchArgs(workspaceDirectory); + + await testTemplateDebugging(options, port, + path.join(workspaceDirectory, 'run.py'), 7, + path.join(workspaceDirectory, 'templates', 'index.html'), 6); + }); + + test('Test Django Route and Template debugging', async () => { + const workspaceDirectory = path.join(EXTENSION_ROOT_DIR, 'src', 'testMultiRootWkspc', 'workspace5', 'djangoApp'); + const { options, port } = await buildDjangoLaunchArgs(workspaceDirectory); + + await testTemplateDebugging(options, port, + path.join(workspaceDirectory, 'home', 'views.py'), 10, + path.join(workspaceDirectory, 'home', 'templates', 'index.html'), 6); + }); +}); diff --git a/src/test/initialize.ts b/src/test/initialize.ts index 0a428faa4dbd..edaa7324f6ce 100644 --- a/src/test/initialize.ts +++ b/src/test/initialize.ts @@ -5,7 +5,7 @@ import * as path from 'path'; import * as vscode from 'vscode'; import { PythonSettings } from '../client/common/configSettings'; import { activated } from '../client/extension'; -import { clearPythonPathInWorkspaceFolder, resetGlobalPythonPathSetting, setPythonPathInWorkspaceRoot } from './common'; +import { clearPythonPathInWorkspaceFolder, PYTHON_PATH, resetGlobalPythonPathSetting, setPythonPathInWorkspaceRoot } from './common'; export * from './constants'; @@ -16,8 +16,6 @@ const workspace3Uri = vscode.Uri.file(path.join(multirootPath, 'workspace3')); //First thing to be executed. process.env['VSC_PYTHON_CI_TEST'] = '1'; -const PYTHON_PATH = getPythonPath(); - // Ability to use custom python environments for testing export async function initializePython() { await resetGlobalPythonPathSetting(); @@ -54,12 +52,3 @@ export async function closeActiveWindows(): Promise { }, 15000); }); } - -function getPythonPath(): string { - // tslint:disable-next-line:no-unsafe-any - if (process.env.TRAVIS_PYTHON_PATH && fs.existsSync(process.env.TRAVIS_PYTHON_PATH)) { - // tslint:disable-next-line:no-unsafe-any - return process.env.TRAVIS_PYTHON_PATH; - } - return 'python'; -} diff --git a/src/testMultiRootWkspc/workspace5/djangoApp/home/__init__.py b/src/testMultiRootWkspc/workspace5/djangoApp/home/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/testMultiRootWkspc/workspace5/djangoApp/home/templates/index.html b/src/testMultiRootWkspc/workspace5/djangoApp/home/templates/index.html new file mode 100644 index 000000000000..6ca5107d23d6 --- /dev/null +++ b/src/testMultiRootWkspc/workspace5/djangoApp/home/templates/index.html @@ -0,0 +1,9 @@ + + + + +

Hello {{ value_from_server }}!

+

Hello {{ another_value_from_server }}!

+ + + diff --git a/src/testMultiRootWkspc/workspace5/djangoApp/home/urls.py b/src/testMultiRootWkspc/workspace5/djangoApp/home/urls.py new file mode 100644 index 000000000000..70a9606e88e6 --- /dev/null +++ b/src/testMultiRootWkspc/workspace5/djangoApp/home/urls.py @@ -0,0 +1,7 @@ +from django.conf.urls import url + +from . import views + +urlpatterns = [ + url('', views.index, name='index'), +] diff --git a/src/testMultiRootWkspc/workspace5/djangoApp/home/views.py b/src/testMultiRootWkspc/workspace5/djangoApp/home/views.py new file mode 100644 index 000000000000..0494f868dc6f --- /dev/null +++ b/src/testMultiRootWkspc/workspace5/djangoApp/home/views.py @@ -0,0 +1,10 @@ +from django.shortcuts import render +from django.template import loader + + +def index(request): + context = { + 'value_from_server':'this_is_a_value_from_server', + 'another_value_from_server':'this_is_another_value_from_server' + } + return render(request, 'index.html', context) diff --git a/src/testMultiRootWkspc/workspace5/djangoApp/manage.py b/src/testMultiRootWkspc/workspace5/djangoApp/manage.py new file mode 100644 index 000000000000..afbc784aafd8 --- /dev/null +++ b/src/testMultiRootWkspc/workspace5/djangoApp/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") + try: + from django.core.management import execute_from_command_line + except ImportError: + # The above import may fail for some other reason. Ensure that the + # issue is really that Django is missing to avoid masking other + # exceptions on Python 2. + try: + import django + except ImportError: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) + raise + execute_from_command_line(sys.argv) diff --git a/src/testMultiRootWkspc/workspace5/djangoApp/mysite/__init__.py b/src/testMultiRootWkspc/workspace5/djangoApp/mysite/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/testMultiRootWkspc/workspace5/djangoApp/mysite/settings.py b/src/testMultiRootWkspc/workspace5/djangoApp/mysite/settings.py new file mode 100644 index 000000000000..4e182517ca2a --- /dev/null +++ b/src/testMultiRootWkspc/workspace5/djangoApp/mysite/settings.py @@ -0,0 +1,93 @@ +""" +Django settings for mysite project. + +Generated by 'django-admin startproject' using Django 1.11.2. + +For more information on this file, see +https://docs.djangoproject.com/en/1.11/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.11/ref/settings/ +""" + +import os + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = '5u06*)07dvd+=kn)zqp8#b0^qt@*$8=nnjc&&0lzfc28(wns&l' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = ['localhost', '127.0.0.1'] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.contenttypes', + 'django.contrib.messages', + 'django.contrib.staticfiles', +] + +MIDDLEWARE = [ +] + +ROOT_URLCONF = 'mysite.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': ['home/templates'], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'mysite.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/1.11/ref/settings/#databases + +DATABASES = { +} + + +# Password validation +# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ +] + + +# Internationalization +# https://docs.djangoproject.com/en/1.11/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.11/howto/static-files/ + +STATIC_URL = '/static/' diff --git a/src/testMultiRootWkspc/workspace5/djangoApp/mysite/urls.py b/src/testMultiRootWkspc/workspace5/djangoApp/mysite/urls.py new file mode 100644 index 000000000000..9db383365e3e --- /dev/null +++ b/src/testMultiRootWkspc/workspace5/djangoApp/mysite/urls.py @@ -0,0 +1,23 @@ +"""mysite URL Configuration + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/1.11/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.conf.urls import url, include + 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) +""" +from django.conf.urls import url, include +from django.contrib import admin +from django.views.generic import RedirectView + +urlpatterns = [ + url(r'^home/', include('home.urls')), + url('', RedirectView.as_view(url='/home/')), +] diff --git a/src/testMultiRootWkspc/workspace5/djangoApp/mysite/wsgi.py b/src/testMultiRootWkspc/workspace5/djangoApp/mysite/wsgi.py new file mode 100644 index 000000000000..74e7daeefe76 --- /dev/null +++ b/src/testMultiRootWkspc/workspace5/djangoApp/mysite/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for mysite project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") + +application = get_wsgi_application() diff --git a/src/testMultiRootWkspc/workspace5/flaskApp/run.py b/src/testMultiRootWkspc/workspace5/flaskApp/run.py new file mode 100644 index 000000000000..9c3172c3e918 --- /dev/null +++ b/src/testMultiRootWkspc/workspace5/flaskApp/run.py @@ -0,0 +1,13 @@ +from flask import Flask, render_template +app = Flask(__name__) + + +@app.route('/') +def hello(): + return render_template('index.html', + value_from_server='this_is_a_value_from_server', + another_value_from_server='this_is_another_value_from_server') + + +if __name__ == '__main__': + app.run() diff --git a/src/testMultiRootWkspc/workspace5/flaskApp/templates/index.html b/src/testMultiRootWkspc/workspace5/flaskApp/templates/index.html new file mode 100644 index 000000000000..6ca5107d23d6 --- /dev/null +++ b/src/testMultiRootWkspc/workspace5/flaskApp/templates/index.html @@ -0,0 +1,9 @@ + + + + +

Hello {{ value_from_server }}!

+

Hello {{ another_value_from_server }}!

+ + + From 6154555f0a00374025a8120ec311cc745a6b2003 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 6 Apr 2018 10:00:01 -0700 Subject: [PATCH 102/433] Use the right version of Python interpreter on Travis when running unit tests (#1319) Fixes #1318 --- .vscode/settings.json | 4 +- news/3 Code Health/1318.md | 1 + .../codeExecution/djangoShellCodeExecution.ts | 9 ++-- src/test/common/moduleInstaller.test.ts | 4 +- src/test/debugger/attach.ptvsd.test.ts | 4 +- src/test/debugger/attach.test.ts | 4 +- src/test/debugger/capabilities.test.ts | 3 +- src/test/debugger/core/capabilities.test.ts | 6 --- src/test/debugger/misc.test.ts | 4 +- src/test/debugger/module.test.ts | 4 +- src/test/debugger/portAndHost.test.ts | 3 +- .../interpreters/interpreterVersion.test.ts | 9 ++-- .../interpreters/virtualEnvManager.test.ts | 13 ++--- .../shebangCodeLenseProvider.test.ts | 9 ++-- .../djangoShellCodeExect.test.ts | 5 +- .../codeExecution/terminalCodeExec.test.ts | 47 ++++++++++--------- 16 files changed, 65 insertions(+), 64 deletions(-) create mode 100644 news/3 Code Health/1318.md delete mode 100644 src/test/debugger/core/capabilities.test.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index be66f967c5c4..7ce29c7c0813 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,8 @@ "files.exclude": { "out": true, // set this to true to hide the "out" folder with the compiled JS files "**/*.pyc": true, + "obj": true, + "bin": true, "**/__pycache__": true, "node_modules": true, ".vscode-test": true, @@ -19,4 +21,4 @@ "python.unitTest.promptToConfigure": false, "python.workspaceSymbols.enabled": false, "python.formatting.provider": "none" -} \ No newline at end of file +} diff --git a/news/3 Code Health/1318.md b/news/3 Code Health/1318.md new file mode 100644 index 000000000000..d0f965f5a448 --- /dev/null +++ b/news/3 Code Health/1318.md @@ -0,0 +1 @@ +Ensure all unit tests run on Travis use the right Python interpreter. diff --git a/src/client/terminals/codeExecution/djangoShellCodeExecution.ts b/src/client/terminals/codeExecution/djangoShellCodeExecution.ts index 5fbe2ef2d19f..4fc230b6e21a 100644 --- a/src/client/terminals/codeExecution/djangoShellCodeExecution.ts +++ b/src/client/terminals/codeExecution/djangoShellCodeExecution.ts @@ -10,14 +10,13 @@ import { ICommandManager, IDocumentManager, IWorkspaceService } from '../../comm import '../../common/extensions'; import { IFileSystem, IPlatformService } from '../../common/platform/types'; import { ITerminalServiceFactory } from '../../common/terminal/types'; -import { IConfigurationService } from '../../common/types'; -import { IDisposableRegistry } from '../../common/types'; +import { IConfigurationService, IDisposableRegistry } from '../../common/types'; import { DjangoContextInitializer } from './djangoContext'; import { TerminalCodeExecutionProvider } from './terminalCodeExecution'; @injectable() export class DjangoShellCodeExecutionProvider extends TerminalCodeExecutionProvider { - constructor( @inject(ITerminalServiceFactory) terminalServiceFactory: ITerminalServiceFactory, + constructor(@inject(ITerminalServiceFactory) terminalServiceFactory: ITerminalServiceFactory, @inject(IConfigurationService) configurationService: IConfigurationService, @inject(IWorkspaceService) workspace: IWorkspaceService, @inject(IDocumentManager) documentManager: IDocumentManager, @@ -30,10 +29,10 @@ export class DjangoShellCodeExecutionProvider extends TerminalCodeExecutionProvi this.terminalTitle = 'Django Shell'; disposableRegistry.push(new DjangoContextInitializer(documentManager, workspace, fileSystem, commandManager)); } - public getReplCommandArgs(resource?: Uri): { command: string, args: string[] } { + public getReplCommandArgs(resource?: Uri): { command: string; args: string[] } { const pythonSettings = this.configurationService.getSettings(resource); const command = this.platformService.isWindows ? pythonSettings.pythonPath.replace(/\\/g, '/') : pythonSettings.pythonPath; - const args = pythonSettings.terminal.launchArgs.slice(); + const args = pythonSettings.terminal!.launchArgs.slice(); const workspaceUri = resource ? this.workspace.getWorkspaceFolder(resource) : undefined; const defaultWorkspace = Array.isArray(this.workspace.workspaceFolders) && this.workspace.workspaceFolders.length > 0 ? this.workspace.workspaceFolders[0].uri.fsPath : ''; diff --git a/src/test/common/moduleInstaller.test.ts b/src/test/common/moduleInstaller.test.ts index 698d424111a8..31ea81de250a 100644 --- a/src/test/common/moduleInstaller.test.ts +++ b/src/test/common/moduleInstaller.test.ts @@ -21,7 +21,7 @@ import { ITerminalService, ITerminalServiceFactory } from '../../client/common/t import { IConfigurationService, ICurrentProcess, IInstaller, ILogger, IPathUtils, IPersistentStateFactory, IPythonSettings, IsWindows } from '../../client/common/types'; import { ICondaService, IInterpreterLocatorService, IInterpreterService, INTERPRETER_LOCATOR_SERVICE, InterpreterType, PIPENV_SERVICE, PythonInterpreter } from '../../client/interpreter/contracts'; import { IServiceContainer } from '../../client/ioc/types'; -import { rootWorkspaceUri } from '../common'; +import { PYTHON_PATH, rootWorkspaceUri } from '../common'; import { MockModuleInstaller } from '../mocks/moduleInstaller'; import { MockProcessService } from '../mocks/proc'; import { UnitTestIocContainer } from '../unittests/serviceRegistry'; @@ -195,7 +195,7 @@ suite('Module Installer', () => { const interpreter: PythonInterpreter = { type: InterpreterType.Unknown, - path: 'python' + path: PYTHON_PATH }; interpreterService.setup(x => x.getActiveInterpreter(TypeMoq.It.isAny())).returns(() => Promise.resolve(interpreter)); diff --git a/src/test/debugger/attach.ptvsd.test.ts b/src/test/debugger/attach.ptvsd.test.ts index 87d7e77de17d..b5f0f9839e1b 100644 --- a/src/test/debugger/attach.ptvsd.test.ts +++ b/src/test/debugger/attach.ptvsd.test.ts @@ -19,7 +19,7 @@ import { PythonV2DebugConfigurationProvider } from '../../client/debugger'; import { PTVSD_PATH } from '../../client/debugger/Common/constants'; import { AttachRequestArguments, DebugOptions } from '../../client/debugger/Common/Contracts'; import { IServiceContainer } from '../../client/ioc/types'; -import { sleep } from '../common'; +import { PYTHON_PATH, sleep } from '../common'; import { initialize, IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; import { continueDebugging, createDebugAdapter } from './utils'; @@ -59,7 +59,7 @@ suite('Attach Debugger - Experimental', () => { // tslint:disable-next-line:no-string-literal env['PYTHONPATH'] = PTVSD_PATH; const pythonArgs = ['-m', 'ptvsd', '--server', '--port', `${port}`, '--file', fileToDebug.fileToCommandArgument()]; - proc = spawn('python', pythonArgs, { env: env, cwd: path.dirname(fileToDebug) }); + proc = spawn(PYTHON_PATH, pythonArgs, { env: env, cwd: path.dirname(fileToDebug) }); await sleep(3000); // Send initialize, attach diff --git a/src/test/debugger/attach.test.ts b/src/test/debugger/attach.test.ts index 5e346c6cb048..7747a0450c01 100644 --- a/src/test/debugger/attach.test.ts +++ b/src/test/debugger/attach.test.ts @@ -12,7 +12,7 @@ import { createDeferred } from '../../client/common/helpers'; import { BufferDecoder } from '../../client/common/process/decoder'; import { ProcessService } from '../../client/common/process/proc'; import { AttachRequestArguments } from '../../client/debugger/Common/Contracts'; -import { sleep } from '../common'; +import { PYTHON_PATH, sleep } from '../common'; import { initialize, IS_APPVEYOR, IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; import { DEBUGGER_TIMEOUT } from './common/constants'; @@ -67,7 +67,7 @@ suite('Attach Debugger', () => { // tslint:disable-next-line:no-string-literal customEnv['PYTHONPATH'] = ptvsdPath; const procService = new ProcessService(new BufferDecoder()); - const result = procService.execObservable('python', [fileToDebug, port.toString()], { env: customEnv, cwd: path.dirname(fileToDebug) }); + const result = procService.execObservable(PYTHON_PATH, [fileToDebug, port.toString()], { env: customEnv, cwd: path.dirname(fileToDebug) }); procToKill = result.proc; const expectedOutputs = [ diff --git a/src/test/debugger/capabilities.test.ts b/src/test/debugger/capabilities.test.ts index 5f7f6f53c159..416985681c24 100644 --- a/src/test/debugger/capabilities.test.ts +++ b/src/test/debugger/capabilities.test.ts @@ -20,6 +20,7 @@ import { PTVSD_PATH } from '../../client/debugger/Common/constants'; import { ProtocolParser } from '../../client/debugger/Common/protocolParser'; import { ProtocolMessageWriter } from '../../client/debugger/Common/protocolWriter'; import { PythonDebugger } from '../../client/debugger/mainV2'; +import { PYTHON_PATH } from '../common'; import { IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; class Request extends Message implements DebugProtocol.InitializeRequest { @@ -80,7 +81,7 @@ suite('Debugging - Capabilities', () => { const port = await getFreePort({ host, port: 3000 }); const env = { ...process.env }; env.PYTHONPATH = PTVSD_PATH; - proc = spawn('python', ['-m', 'ptvsd', '--server', '--port', `${port}`, '--file', fileToDebug], { cwd: path.dirname(fileToDebug), env }); + proc = spawn(PYTHON_PATH, ['-m', 'ptvsd', '--server', '--port', `${port}`, '--file', fileToDebug], { cwd: path.dirname(fileToDebug), env }); await sleep(3000); const connected = createDeferred(); diff --git a/src/test/debugger/core/capabilities.test.ts b/src/test/debugger/core/capabilities.test.ts deleted file mode 100644 index 63bc7314a0e1..000000000000 --- a/src/test/debugger/core/capabilities.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// 1. Ensure the capabilitites of the debugger sent into the response to initialize matches -// that of the underlying (ptvsd) debugger -// I.e. ensure the response sent by us matches the response sent by ptvsd to the initialize request. diff --git a/src/test/debugger/misc.test.ts b/src/test/debugger/misc.test.ts index 0e17bc9a05c9..995be0db817a 100644 --- a/src/test/debugger/misc.test.ts +++ b/src/test/debugger/misc.test.ts @@ -15,7 +15,7 @@ import { FileSystem } from '../../client/common/platform/fileSystem'; import { PlatformService } from '../../client/common/platform/platformService'; import { PTVSD_PATH } from '../../client/debugger/Common/constants'; import { DebugOptions, LaunchRequestArguments } from '../../client/debugger/Common/Contracts'; -import { sleep } from '../common'; +import { PYTHON_PATH, sleep } from '../common'; import { IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; import { DEBUGGER_TIMEOUT } from './common/constants'; import { DebugClientEx } from './debugClient'; @@ -78,7 +78,7 @@ let testCounter = 0; cwd: debugFilesPath, stopOnEntry, debugOptions: [DebugOptions.RedirectOutput], - pythonPath: 'python', + pythonPath: PYTHON_PATH, args: [], env, envFile: '', diff --git a/src/test/debugger/module.test.ts b/src/test/debugger/module.test.ts index cdf88f27a68a..1e51b5e2b030 100644 --- a/src/test/debugger/module.test.ts +++ b/src/test/debugger/module.test.ts @@ -11,7 +11,7 @@ import { EXTENSION_ROOT_DIR } from '../../client/common/constants'; import { noop } from '../../client/common/core.utils'; import { PTVSD_PATH } from '../../client/debugger/Common/constants'; import { DebugOptions, LaunchRequestArguments } from '../../client/debugger/Common/Contracts'; -import { sleep } from '../common'; +import { PYTHON_PATH, sleep } from '../common'; import { IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; import { createDebugAdapter } from './utils'; @@ -46,7 +46,7 @@ suite(`Module Debugging - Misc tests: ${debuggerType}`, () => { program: '', cwd: workspaceDirectory, debugOptions: [DebugOptions.RedirectOutput], - pythonPath: 'python', + pythonPath: PYTHON_PATH, args: [], env, envFile: '', diff --git a/src/test/debugger/portAndHost.test.ts b/src/test/debugger/portAndHost.test.ts index 6d2d79491bbf..32fdf3941a74 100644 --- a/src/test/debugger/portAndHost.test.ts +++ b/src/test/debugger/portAndHost.test.ts @@ -9,6 +9,7 @@ import * as path from 'path'; import { DebugClient } from 'vscode-debugadapter-testsupport'; import { noop } from '../../client/common/core.utils'; import { DebugOptions, LaunchRequestArguments } from '../../client/debugger/Common/Contracts'; +import { PYTHON_PATH } from '../common'; import { IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; import { DEBUGGER_TIMEOUT } from './common/constants'; @@ -50,7 +51,7 @@ const EXPERIMENTAL_DEBUG_ADAPTER = path.join(__dirname, '..', '..', 'client', 'd cwd: debugFilesPath, stopOnEntry, debugOptions: [DebugOptions.RedirectOutput], - pythonPath: 'python', + pythonPath: PYTHON_PATH, args: [], envFile: '', host, port, diff --git a/src/test/interpreters/interpreterVersion.test.ts b/src/test/interpreters/interpreterVersion.test.ts index 6bd728d669ab..9e78d8ee5e66 100644 --- a/src/test/interpreters/interpreterVersion.test.ts +++ b/src/test/interpreters/interpreterVersion.test.ts @@ -7,6 +7,7 @@ import '../../client/common/extensions'; import { IProcessService } from '../../client/common/process/types'; import { IInterpreterVersionService } from '../../client/interpreter/contracts'; import { PIP_VERSION_REGEX } from '../../client/interpreter/interpreterVersion'; +import { PYTHON_PATH } from '../common'; import { initialize, initializeTest } from '../initialize'; import { UnitTestIocContainer } from '../unittests/serviceRegistry'; @@ -31,10 +32,10 @@ suite('Interpreters display version', () => { test('Must return the Python Version', async () => { const pythonProcess = ioc.serviceContainer.get(IProcessService); - const output = await pythonProcess.exec('python', ['--version'], { cwd: __dirname, mergeStdOutErr: true }); + const output = await pythonProcess.exec(PYTHON_PATH, ['--version'], { cwd: __dirname, mergeStdOutErr: true }); const version = output.stdout.splitLines()[0]; const interpreterVersion = ioc.serviceContainer.get(IInterpreterVersionService); - const pyVersion = await interpreterVersion.getVersion('python', 'DEFAULT_TEST_VALUE'); + const pyVersion = await interpreterVersion.getVersion(PYTHON_PATH, 'DEFAULT_TEST_VALUE'); assert.equal(pyVersion, version, 'Incorrect version'); }); test('Must return the default value when Python path is invalid', async () => { @@ -44,7 +45,7 @@ suite('Interpreters display version', () => { }); test('Must return the pip Version', async () => { const pythonProcess = ioc.serviceContainer.get(IProcessService); - const result = await pythonProcess.exec('python', ['-m', 'pip', '--version'], { cwd: __dirname, mergeStdOutErr: true }); + const result = await pythonProcess.exec(PYTHON_PATH, ['-m', 'pip', '--version'], { cwd: __dirname, mergeStdOutErr: true }); const output = result.stdout.splitLines()[0]; // Take the second part, see below example. // pip 9.0.1 from /Users/donjayamanne/anaconda3/lib/python3.6/site-packages (python 3.6). @@ -55,7 +56,7 @@ suite('Interpreters display version', () => { assert.isAtLeast(matches!.length, 1, 'Version number not found'); const interpreterVersion = ioc.serviceContainer.get(IInterpreterVersionService); - const pipVersionPromise = interpreterVersion.getPipVersion('python'); + const pipVersionPromise = interpreterVersion.getPipVersion(PYTHON_PATH); // tslint:disable-next-line:no-non-null-assertion await expect(pipVersionPromise).to.eventually.equal(matches![0].trim()); }); diff --git a/src/test/interpreters/virtualEnvManager.test.ts b/src/test/interpreters/virtualEnvManager.test.ts index bfd498b55de3..3d00204b9269 100644 --- a/src/test/interpreters/virtualEnvManager.test.ts +++ b/src/test/interpreters/virtualEnvManager.test.ts @@ -10,6 +10,7 @@ import { IBufferDecoder, IProcessService } from '../../client/common/process/typ import { VirtualEnvironmentManager } from '../../client/interpreter/virtualEnvs'; import { ServiceContainer } from '../../client/ioc/container'; import { ServiceManager } from '../../client/ioc/serviceManager'; +import { PYTHON_PATH } from '../common'; suite('Virtual environment manager', () => { let serviceManager: ServiceManager; @@ -22,15 +23,15 @@ suite('Virtual environment manager', () => { serviceContainer = new ServiceContainer(cont); }); - test('Plain Python environment suffix', async () => await testSuffix('')); - test('Venv environment suffix', async () => await testSuffix('venv')); - test('Virtualenv Python environment suffix', async () => await testSuffix('virtualenv')); + test('Plain Python environment suffix', async () => testSuffix('')); + test('Venv environment suffix', async () => testSuffix('venv')); + test('Virtualenv Python environment suffix', async () => testSuffix('virtualenv')); test('Run actual virtual env detection code', async () => { serviceManager.addSingleton(IProcessService, ProcessService); serviceManager.addSingleton(IBufferDecoder, BufferDecoder); const venvManager = new VirtualEnvironmentManager(serviceContainer); - const name = await venvManager.getEnvironmentName('python'); + const name = await venvManager.getEnvironmentName(PYTHON_PATH); const result = name === '' || name === 'venv' || name === 'virtualenv'; expect(result).to.be.equal(true, 'Running venv detection code failed.'); }); @@ -41,13 +42,13 @@ suite('Virtual environment manager', () => { const venvManager = new VirtualEnvironmentManager(serviceContainer); process - .setup(x => x.exec('python', TypeMoq.It.isAny())) + .setup(x => x.exec(PYTHON_PATH, TypeMoq.It.isAny())) .returns(() => Promise.resolve({ stdout: expectedName, stderr: '' })); - const name = await venvManager.getEnvironmentName('python'); + const name = await venvManager.getEnvironmentName(PYTHON_PATH); expect(name).to.be.equal(expectedName, 'Virtual envrironment name suffix is incorrect.'); } }); diff --git a/src/test/providers/shebangCodeLenseProvider.test.ts b/src/test/providers/shebangCodeLenseProvider.test.ts index bc89c1761d5e..b1e4a15bd8ae 100644 --- a/src/test/providers/shebangCodeLenseProvider.test.ts +++ b/src/test/providers/shebangCodeLenseProvider.test.ts @@ -1,8 +1,7 @@ import * as assert from 'assert'; import * as child_process from 'child_process'; import * as path from 'path'; -import * as vscode from 'vscode'; -import { CancellationTokenSource } from 'vscode'; +import { CancellationTokenSource, TextDocument, workspace } from 'vscode'; import { IS_WINDOWS, PythonSettings } from '../../client/common/configSettings'; import { ShebangCodeLensProvider } from '../../client/interpreter/display/shebangCodeLensProvider'; import { getFirstNonEmptyLineFromMultilineString } from '../../client/interpreter/helpers'; @@ -91,7 +90,7 @@ suite('Shebang detection', () => { }); async function openFile(fileName: string) { - return vscode.workspace.openTextDocument(fileName); + return workspace.openTextDocument(fileName); } async function getFullyQualifiedPathToInterpreter(pythonPath: string) { return new Promise(resolve => { @@ -101,8 +100,8 @@ suite('Shebang detection', () => { }).catch(() => undefined); } - async function setupCodeLens(document: vscode.TextDocument) { + async function setupCodeLens(document: TextDocument) { const codeLensProvider = new ShebangCodeLensProvider(ioc.serviceContainer); - return await codeLensProvider.provideCodeLenses(document, new CancellationTokenSource().token); + return codeLensProvider.provideCodeLenses(document, new CancellationTokenSource().token); } }); diff --git a/src/test/terminals/codeExecution/djangoShellCodeExect.test.ts b/src/test/terminals/codeExecution/djangoShellCodeExect.test.ts index b6814214abeb..e85da9496e88 100644 --- a/src/test/terminals/codeExecution/djangoShellCodeExect.test.ts +++ b/src/test/terminals/codeExecution/djangoShellCodeExect.test.ts @@ -13,6 +13,7 @@ import { ITerminalService, ITerminalServiceFactory } from '../../../client/commo import { IConfigurationService, IPythonSettings, ITerminalSettings } from '../../../client/common/types'; import { DjangoShellCodeExecutionProvider } from '../../../client/terminals/codeExecution/djangoShellCodeExecution'; import { ICodeExecutionService } from '../../../client/terminals/types'; +import { PYTHON_PATH } from '../../common'; // tslint:disable-next-line:max-func-body-length suite('Terminal - Django Shell Code Execution', () => { @@ -86,7 +87,7 @@ suite('Terminal - Django Shell Code Execution', () => { }); test('Ensure python path is returned as is, when building repl args on Windows', async () => { - const pythonPath = 'python'; + const pythonPath = PYTHON_PATH; const terminalArgs = ['-a', 'b', 'c']; const expectedTerminalArgs = terminalArgs.concat('manage.py', 'shell'); @@ -102,7 +103,7 @@ suite('Terminal - Django Shell Code Execution', () => { }); test('Ensure python path is returned as is, on non Windows', async () => { - const pythonPath = 'python'; + const pythonPath = PYTHON_PATH; const terminalArgs = ['-a', 'b', 'c']; const expectedTerminalArgs = terminalArgs.concat('manage.py', 'shell'); diff --git a/src/test/terminals/codeExecution/terminalCodeExec.test.ts b/src/test/terminals/codeExecution/terminalCodeExec.test.ts index 4466de64354f..2ada071c2f3c 100644 --- a/src/test/terminals/codeExecution/terminalCodeExec.test.ts +++ b/src/test/terminals/codeExecution/terminalCodeExec.test.ts @@ -15,6 +15,7 @@ import { DjangoShellCodeExecutionProvider } from '../../../client/terminals/code import { ReplProvider } from '../../../client/terminals/codeExecution/repl'; import { TerminalCodeExecutionProvider } from '../../../client/terminals/codeExecution/terminalCodeExecution'; import { ICodeExecutionService } from '../../../client/terminals/types'; +import { PYTHON_PATH } from '../../common'; // tslint:disable-next-line:max-func-body-length suite('Terminal Code Execution', () => { @@ -99,7 +100,7 @@ suite('Terminal Code Execution', () => { platform.setup(p => p.isWindows).returns(() => isWindows); platform.setup(p => p.isMac).returns(() => isOsx); platform.setup(p => p.isLinux).returns(() => isLinux); - settings.setup(s => s.pythonPath).returns(() => 'python'); + settings.setup(s => s.pythonPath).returns(() => PYTHON_PATH); terminalSettings.setup(t => t.launchArgs).returns(() => []); await executor.initializeRepl(); @@ -130,12 +131,12 @@ suite('Terminal Code Execution', () => { workspace.setup(w => w.getWorkspaceFolder(TypeMoq.It.isAny())).returns(() => workspaceFolder.object); workspaceFolder.setup(w => w.uri).returns(() => Uri.file(path.join('c', 'path', 'to'))); platform.setup(p => p.isWindows).returns(() => false); - settings.setup(s => s.pythonPath).returns(() => 'python'); + settings.setup(s => s.pythonPath).returns(() => PYTHON_PATH); terminalSettings.setup(t => t.launchArgs).returns(() => []); await executor.executeFile(file); - terminalService.verify(async t => await t.sendText(TypeMoq.It.isValue(`cd ${path.dirname(file.fsPath).fileToCommandArgument()}`)), TypeMoq.Times.once()); + terminalService.verify(async t => t.sendText(TypeMoq.It.isValue(`cd ${path.dirname(file.fsPath).fileToCommandArgument()}`)), TypeMoq.Times.once()); } test('Ensure we set current directory before executing file (non windows)', async () => { await ensureWeSetCurrentDirectoryBeforeExecutingAFile(false); @@ -150,12 +151,12 @@ suite('Terminal Code Execution', () => { workspace.setup(w => w.getWorkspaceFolder(TypeMoq.It.isAny())).returns(() => workspaceFolder.object); workspaceFolder.setup(w => w.uri).returns(() => Uri.file(path.join('c', 'path', 'to'))); platform.setup(p => p.isWindows).returns(() => isWindows); - settings.setup(s => s.pythonPath).returns(() => 'python'); + settings.setup(s => s.pythonPath).returns(() => PYTHON_PATH); terminalSettings.setup(t => t.launchArgs).returns(() => []); await executor.executeFile(file); const dir = path.dirname(file.fsPath).fileToCommandArgument(); - terminalService.verify(async t => await t.sendText(TypeMoq.It.isValue(`cd ${dir}`)), TypeMoq.Times.once()); + terminalService.verify(async t => t.sendText(TypeMoq.It.isValue(`cd ${dir}`)), TypeMoq.Times.once()); } test('Ensure we set current directory (and quote it when containing spaces) before executing file (non windows)', async () => { @@ -172,12 +173,12 @@ suite('Terminal Code Execution', () => { workspace.setup(w => w.getWorkspaceFolder(TypeMoq.It.isAny())).returns(() => workspaceFolder.object); workspaceFolder.setup(w => w.uri).returns(() => Uri.file(path.join('c', 'path', 'to', 'file with spaces in path'))); platform.setup(p => p.isWindows).returns(() => isWindows); - settings.setup(s => s.pythonPath).returns(() => 'python'); + settings.setup(s => s.pythonPath).returns(() => PYTHON_PATH); terminalSettings.setup(t => t.launchArgs).returns(() => []); await executor.executeFile(file); - terminalService.verify(async t => await t.sendText(TypeMoq.It.isAny()), TypeMoq.Times.never()); + terminalService.verify(async t => t.sendText(TypeMoq.It.isAny()), TypeMoq.Times.never()); } test('Ensure we do not set current directory before executing file if in the same directory (non windows)', async () => { await ensureWeDoNotSetCurrentDirectoryBeforeExecutingFileInSameDirectory(false); @@ -191,12 +192,12 @@ suite('Terminal Code Execution', () => { terminalSettings.setup(t => t.executeInFileDir).returns(() => true); workspace.setup(w => w.getWorkspaceFolder(TypeMoq.It.isAny())).returns(() => undefined); platform.setup(p => p.isWindows).returns(() => isWindows); - settings.setup(s => s.pythonPath).returns(() => 'python'); + settings.setup(s => s.pythonPath).returns(() => PYTHON_PATH); terminalSettings.setup(t => t.launchArgs).returns(() => []); await executor.executeFile(file); - terminalService.verify(async t => await t.sendText(TypeMoq.It.isAny()), TypeMoq.Times.never()); + terminalService.verify(async t => t.sendText(TypeMoq.It.isAny()), TypeMoq.Times.never()); } test('Ensure we do not set current directory before executing file if file is not in a workspace (non windows)', async () => { await ensureWeDoNotSetCurrentDirectoryBeforeExecutingFileNotInSameDirectory(false); @@ -215,12 +216,12 @@ suite('Terminal Code Execution', () => { await executor.executeFile(file); const expectedPythonPath = isWindows ? pythonPath.replace(/\\/g, '/') : pythonPath; const expectedArgs = terminalArgs.concat(file.fsPath.fileToCommandArgument()); - terminalService.verify(async t => await t.sendCommand(TypeMoq.It.isValue(expectedPythonPath), TypeMoq.It.isValue(expectedArgs)), TypeMoq.Times.once()); + terminalService.verify(async t => t.sendCommand(TypeMoq.It.isValue(expectedPythonPath), TypeMoq.It.isValue(expectedArgs)), TypeMoq.Times.once()); } test('Ensure python file execution script is sent to terminal on windows', async () => { const file = Uri.file(path.join('c', 'path', 'to', 'file with spaces in path', 'one.py')); - await testFileExecution(true, 'python', [], file); + await testFileExecution(true, PYTHON_PATH, [], file); }); test('Ensure python file execution script is sent to terminal on windows with fully qualified python path', async () => { @@ -230,12 +231,12 @@ suite('Terminal Code Execution', () => { test('Ensure python file execution script is not quoted when no spaces in file path', async () => { const file = Uri.file(path.join('c', 'path', 'to', 'file', 'one.py')); - await testFileExecution(true, 'python', [], file); + await testFileExecution(true, PYTHON_PATH, [], file); }); test('Ensure python file execution script supports custom python arguments', async () => { const file = Uri.file(path.join('c', 'path', 'to', 'file', 'one.py')); - await testFileExecution(false, 'python', ['-a', '-b', '-c'], file); + await testFileExecution(false, PYTHON_PATH, ['-a', '-b', '-c'], file); }); function testReplCommandArguments(isWindows: boolean, pythonPath: string, expectedPythonPath: string, terminalArgs: string[]) { @@ -265,7 +266,7 @@ suite('Terminal Code Execution', () => { }); test('Ensure python path is returned as is, when building repl args on Windows', () => { - const pythonPath = 'python'; + const pythonPath = PYTHON_PATH; const terminalArgs = ['-a', 'b', 'c']; testReplCommandArguments(true, pythonPath, pythonPath, terminalArgs); @@ -279,7 +280,7 @@ suite('Terminal Code Execution', () => { }); test('Ensure python path is returned as is, on non Windows', () => { - const pythonPath = 'python'; + const pythonPath = PYTHON_PATH; const terminalArgs = ['-a', 'b', 'c']; testReplCommandArguments(false, pythonPath, pythonPath, terminalArgs); @@ -291,8 +292,8 @@ suite('Terminal Code Execution', () => { // tslint:disable-next-line:no-any await executor.execute(undefined as any as string); - terminalService.verify(async t => await t.sendCommand(TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.never()); - terminalService.verify(async t => await t.sendText(TypeMoq.It.isAny()), TypeMoq.Times.never()); + terminalService.verify(async t => t.sendCommand(TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.never()); + terminalService.verify(async t => t.sendText(TypeMoq.It.isAny()), TypeMoq.Times.never()); }); test('Ensure repl is initialized once before sending text to the repl', async () => { @@ -308,7 +309,7 @@ suite('Terminal Code Execution', () => { await executor.execute('cmd3'); const expectedTerminalArgs = isDjangoRepl ? terminalArgs.concat(['manage.py', 'shell']) : terminalArgs; - terminalService.verify(async t => await t.sendCommand(TypeMoq.It.isValue(pythonPath), TypeMoq.It.isValue(expectedTerminalArgs)), TypeMoq.Times.once()); + terminalService.verify(async t => t.sendCommand(TypeMoq.It.isValue(pythonPath), TypeMoq.It.isValue(expectedTerminalArgs)), TypeMoq.Times.once()); }); test('Ensure repl is re-initialized when temrinal is closed', async () => { @@ -334,15 +335,15 @@ suite('Terminal Code Execution', () => { const expectedTerminalArgs = isDjangoRepl ? terminalArgs.concat(['manage.py', 'shell']) : terminalArgs; expect(closeTerminalCallback).not.to.be.an('undefined', 'Callback not initialized'); - terminalService.verify(async t => await t.sendCommand(TypeMoq.It.isValue(pythonPath), TypeMoq.It.isValue(expectedTerminalArgs)), TypeMoq.Times.once()); + terminalService.verify(async t => t.sendCommand(TypeMoq.It.isValue(pythonPath), TypeMoq.It.isValue(expectedTerminalArgs)), TypeMoq.Times.once()); closeTerminalCallback!.call(terminalService.object); await executor.execute('cmd4'); - terminalService.verify(async t => await t.sendCommand(TypeMoq.It.isValue(pythonPath), TypeMoq.It.isValue(expectedTerminalArgs)), TypeMoq.Times.exactly(2)); + terminalService.verify(async t => t.sendCommand(TypeMoq.It.isValue(pythonPath), TypeMoq.It.isValue(expectedTerminalArgs)), TypeMoq.Times.exactly(2)); closeTerminalCallback!.call(terminalService.object); await executor.execute('cmd5'); - terminalService.verify(async t => await t.sendCommand(TypeMoq.It.isValue(pythonPath), TypeMoq.It.isValue(expectedTerminalArgs)), TypeMoq.Times.exactly(3)); + terminalService.verify(async t => t.sendCommand(TypeMoq.It.isValue(pythonPath), TypeMoq.It.isValue(expectedTerminalArgs)), TypeMoq.Times.exactly(3)); }); test('Ensure code is sent to terminal', async () => { @@ -353,10 +354,10 @@ suite('Terminal Code Execution', () => { terminalSettings.setup(t => t.launchArgs).returns(() => terminalArgs); await executor.execute('cmd1'); - terminalService.verify(async t => await t.sendText('cmd1'), TypeMoq.Times.once()); + terminalService.verify(async t => t.sendText('cmd1'), TypeMoq.Times.once()); await executor.execute('cmd2'); - terminalService.verify(async t => await t.sendText('cmd2'), TypeMoq.Times.once()); + terminalService.verify(async t => t.sendText('cmd2'), TypeMoq.Times.once()); }); }); }); From b44372fbb65f4a95cc564d9688c5092dce812081 Mon Sep 17 00:00:00 2001 From: Jonathan Carter Date: Mon, 9 Apr 2018 11:55:38 -0700 Subject: [PATCH 103/433] Restrict language services to file/untitled schemes (#1298) --- src/client/activation/classic.ts | 20 +++++++++----------- src/client/extension.ts | 11 ++++++++--- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/client/activation/classic.ts b/src/client/activation/classic.ts index 61b82fae79f9..0574765f6336 100644 --- a/src/client/activation/classic.ts +++ b/src/client/activation/classic.ts @@ -20,10 +20,8 @@ import { TEST_OUTPUT_CHANNEL } from '../unittests/common/constants'; import * as tests from '../unittests/main'; import { IExtensionActivator } from './types'; -const PYTHON: DocumentFilter = { language: 'python' }; - export class ClassicExtensionActivator implements IExtensionActivator { - constructor(private serviceManager: IServiceManager, private pythonSettings: IPythonSettings) { + constructor(private serviceManager: IServiceManager, private pythonSettings: IPythonSettings, private documentSelector: DocumentFilter[]) { } public async activate(context: ExtensionContext): Promise { @@ -35,20 +33,20 @@ export class ClassicExtensionActivator implements IExtensionActivator { context.subscriptions.push(...activateGoToObjectDefinitionProvider(jediFactory)); context.subscriptions.push(jediFactory); - context.subscriptions.push(languages.registerRenameProvider(PYTHON, new PythonRenameProvider(this.serviceManager))); + context.subscriptions.push(languages.registerRenameProvider(this.documentSelector, new PythonRenameProvider(this.serviceManager))); const definitionProvider = new PythonDefinitionProvider(jediFactory); - context.subscriptions.push(languages.registerDefinitionProvider(PYTHON, definitionProvider)); - context.subscriptions.push(languages.registerHoverProvider(PYTHON, new PythonHoverProvider(jediFactory))); - context.subscriptions.push(languages.registerReferenceProvider(PYTHON, new PythonReferenceProvider(jediFactory))); - context.subscriptions.push(languages.registerCompletionItemProvider(PYTHON, new PythonCompletionItemProvider(jediFactory, this.serviceManager), '.')); - context.subscriptions.push(languages.registerCodeLensProvider(PYTHON, this.serviceManager.get(IShebangCodeLensProvider))); + context.subscriptions.push(languages.registerDefinitionProvider(this.documentSelector, definitionProvider)); + context.subscriptions.push(languages.registerHoverProvider(this.documentSelector, new PythonHoverProvider(jediFactory))); + context.subscriptions.push(languages.registerReferenceProvider(this.documentSelector, new PythonReferenceProvider(jediFactory))); + context.subscriptions.push(languages.registerCompletionItemProvider(this.documentSelector, new PythonCompletionItemProvider(jediFactory, this.serviceManager), '.')); + context.subscriptions.push(languages.registerCodeLensProvider(this.documentSelector, this.serviceManager.get(IShebangCodeLensProvider))); const symbolProvider = new PythonSymbolProvider(jediFactory); - context.subscriptions.push(languages.registerDocumentSymbolProvider(PYTHON, symbolProvider)); + context.subscriptions.push(languages.registerDocumentSymbolProvider(this.documentSelector, symbolProvider)); if (this.pythonSettings.devOptions.indexOf('DISABLE_SIGNATURE') === -1) { - context.subscriptions.push(languages.registerSignatureHelpProvider(PYTHON, new PythonSignatureProvider(jediFactory), '(', ',')); + context.subscriptions.push(languages.registerSignatureHelpProvider(this.documentSelector, new PythonSignatureProvider(jediFactory), '(', ',')); } const unitTestOutChannel = this.serviceManager.get(IOutputChannel, TEST_OUTPUT_CHANNEL); diff --git a/src/client/extension.ts b/src/client/extension.ts index 04457bb99a15..ed1b0d09443b 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -57,7 +57,12 @@ import { WorkspaceSymbols } from './workspaceSymbols/main'; const activationDeferred = createDeferred(); export const activated = activationDeferred.promise; -const PYTHON: DocumentFilter = { language: 'python' }; + +const PYTHON_LANGUAGE = 'python'; +const PYTHON: DocumentFilter[] = [ + { scheme: 'file', language: PYTHON_LANGUAGE }, + { scheme: 'untitled', language: PYTHON_LANGUAGE } +]; // tslint:disable-next-line:max-func-body-length export async function activate(context: ExtensionContext) { @@ -76,7 +81,7 @@ export async function activate(context: ExtensionContext) { const activator: IExtensionActivator = isPythonAnalysisEngineTest() || !pythonSettings.jediEnabled ? new AnalysisExtensionActivator(serviceManager, pythonSettings) - : new ClassicExtensionActivator(serviceManager, pythonSettings); + : new ClassicExtensionActivator(serviceManager, pythonSettings, PYTHON); await activator.activate(context); @@ -104,7 +109,7 @@ export async function activate(context: ExtensionContext) { // Enable indentAction // tslint:disable-next-line:no-non-null-assertion - languages.setLanguageConfiguration(PYTHON.language!, { + languages.setLanguageConfiguration(PYTHON_LANGUAGE!, { onEnterRules: [ { beforeText: /^\s*(?:def|class|for|if|elif|else|while|try|with|finally|except)\b.*:\s*\S+/, From e00915b83aa932137dbe5af8677a98e7d480b093 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 9 Apr 2018 12:07:12 -0700 Subject: [PATCH 104/433] remove unwanted file from extension (#1344) --- .vscodeignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscodeignore b/.vscodeignore index ecff38de7902..ed657c8e58e9 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -9,6 +9,7 @@ CODE_OF_CONDUCT.md CODING_STANDARDS.md CONTRIBUTING.md +CONTRIBUTING - PYTHON_ANALYSIS.md coverconfig.json gulpfile.js packageExtension.cmd From 6a7512ca29ec66f598103198e147ac2cca5896cd Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 10 Apr 2018 09:32:03 -0700 Subject: [PATCH 105/433] Add missing news entries (#1356) --- news/2 Fixes/1257-1.md | 1 + news/2 Fixes/1257-2.md | 1 + news/2 Fixes/726.md | 1 + news/3 Code Health/1298.md | 3 +++ 4 files changed, 6 insertions(+) create mode 100644 news/2 Fixes/1257-1.md create mode 100644 news/2 Fixes/1257-2.md create mode 100644 news/2 Fixes/726.md create mode 100644 news/3 Code Health/1298.md diff --git a/news/2 Fixes/1257-1.md b/news/2 Fixes/1257-1.md new file mode 100644 index 000000000000..0f53d565ae55 --- /dev/null +++ b/news/2 Fixes/1257-1.md @@ -0,0 +1 @@ +When `editor.formatOnType` is on, don't add a space for `*args` or `**kwargs` diff --git a/news/2 Fixes/1257-2.md b/news/2 Fixes/1257-2.md new file mode 100644 index 000000000000..c14a958df006 --- /dev/null +++ b/news/2 Fixes/1257-2.md @@ -0,0 +1 @@ +When `editor.formatOnType` is on, don't add a space between a string type specifier and the string literal diff --git a/news/2 Fixes/726.md b/news/2 Fixes/726.md new file mode 100644 index 000000000000..3600f6648324 --- /dev/null +++ b/news/2 Fixes/726.md @@ -0,0 +1 @@ +When `editor.formatOnType` is on, don't indent after a single-line statement block diff --git a/news/3 Code Health/1298.md b/news/3 Code Health/1298.md new file mode 100644 index 000000000000..12214ac71549 --- /dev/null +++ b/news/3 Code Health/1298.md @@ -0,0 +1,3 @@ +Only trigger the extension for `file` and `untitled` in preparation for +[Visual Studio Live Share](https://aka.ms/vsls) +(thanks to [Jonathan Carter](https://github.com/lostintangent)) From f2df5bc090b727f494277e271f0b6d876913b34e Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 10 Apr 2018 09:45:04 -0700 Subject: [PATCH 106/433] Mention which style guide takes precedence and import practices Closes #1223 --- CODING_STANDARDS.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CODING_STANDARDS.md b/CODING_STANDARDS.md index bafa2de536e7..1a1a8bfc401e 100644 --- a/CODING_STANDARDS.md +++ b/CODING_STANDARDS.md @@ -1,5 +1,5 @@ ## Coding guidelines for TypeScript -* The following standards are inspired from [Coding guidelines for TypeScript](https://github.com/Microsoft/TypeScript/wiki/Coding-guidelines). +* The following standards are inspired from [Coding guidelines for TypeScript](https://github.com/Microsoft/TypeScript/wiki/Coding-guidelines) (which you should follow when something is not specified in this document, although any pre-existing practices in a file being edited trump either style guide). ### Names @@ -31,6 +31,11 @@ Use undefined. Do not use null. Use single quotes for strings. +### Imports + +* Use ES6 module imports. +* Do not use bare `import *`; all imports should either explicitly pull in an object or import an entire module, otherwise you're implicitly polluting the global namespace and making it difficult to figure out from code examination where a name originates from. + ### Style * Use arrow functions over anonymous function expressions. From f5d38c4aa1feca205c90a61ab6169825b1a0ac46 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 10 Apr 2018 12:24:20 -0700 Subject: [PATCH 107/433] Check if interpreter file exists before displaying in a list (#1352) Fixes #1305 --- news/2 Fixes/1305.md | 1 + .../locators/services/currentPathService.ts | 22 ++++-- .../interpreters/currentPathService.test.ts | 78 +++++++++++++++++++ 3 files changed, 95 insertions(+), 6 deletions(-) create mode 100644 news/2 Fixes/1305.md create mode 100644 src/test/interpreters/currentPathService.test.ts diff --git a/news/2 Fixes/1305.md b/news/2 Fixes/1305.md new file mode 100644 index 000000000000..321d9d0d8300 --- /dev/null +++ b/news/2 Fixes/1305.md @@ -0,0 +1 @@ +Ensure interpreter file exists on the file system before including into list of interpreters. diff --git a/src/client/interpreter/locators/services/currentPathService.ts b/src/client/interpreter/locators/services/currentPathService.ts index 8b31e1968474..48444e0dfba9 100644 --- a/src/client/interpreter/locators/services/currentPathService.ts +++ b/src/client/interpreter/locators/services/currentPathService.ts @@ -2,8 +2,9 @@ import { inject, injectable } from 'inversify'; import * as _ from 'lodash'; import * as path from 'path'; import { Uri } from 'vscode'; -import { PythonSettings } from '../../../common/configSettings'; +import { IFileSystem } from '../../../common/platform/types'; import { IProcessService } from '../../../common/process/types'; +import { IConfigurationService } from '../../../common/types'; import { IServiceContainer } from '../../../ioc/types'; import { IInterpreterVersionService, InterpreterType, PythonInterpreter } from '../../contracts'; import { IVirtualEnvironmentManager } from '../../virtualEnvs/types'; @@ -11,11 +12,13 @@ import { CacheableLocatorService } from './cacheableLocatorService'; @injectable() export class CurrentPathService extends CacheableLocatorService { + private readonly fs: IFileSystem; public constructor(@inject(IVirtualEnvironmentManager) private virtualEnvMgr: IVirtualEnvironmentManager, @inject(IInterpreterVersionService) private versionProvider: IInterpreterVersionService, @inject(IProcessService) private processService: IProcessService, @inject(IServiceContainer) serviceContainer: IServiceContainer) { super('CurrentPathService', serviceContainer); + this.fs = serviceContainer.get(IFileSystem); } // tslint:disable-next-line:no-empty public dispose() { } @@ -23,7 +26,8 @@ export class CurrentPathService extends CacheableLocatorService { return this.suggestionsFromKnownPaths(); } private async suggestionsFromKnownPaths(resource?: Uri) { - const currentPythonInterpreter = this.getInterpreter(PythonSettings.getInstance(resource).pythonPath, '').then(interpreter => [interpreter]); + const configSettings = this.serviceContainer.get(IConfigurationService).getSettings(resource); + const currentPythonInterpreter = this.getInterpreter(configSettings.pythonPath, '').then(interpreter => [interpreter]); const python = this.getInterpreter('python', '').then(interpreter => [interpreter]); const python2 = this.getInterpreter('python2', '').then(interpreter => [interpreter]); const python3 = this.getInterpreter('python3', '').then(interpreter => [interpreter]); @@ -49,9 +53,15 @@ export class CurrentPathService extends CacheableLocatorService { }); } private async getInterpreter(pythonPath: string, defaultValue: string) { - return this.processService.exec(pythonPath, ['-c', 'import sys;print(sys.executable)'], {}) - .then(output => output.stdout.trim()) - .then(value => value.length === 0 ? defaultValue : value) - .catch(() => defaultValue); // Ignore exceptions in getting the executable. + try { + const output = await this.processService.exec(pythonPath, ['-c', 'import sys;print(sys.executable)'], {}); + const executablePath = output.stdout.trim(); + if (executablePath.length > 0 && await this.fs.fileExistsAsync(executablePath)) { + return executablePath; + } + return defaultValue; + } catch { + return defaultValue; // Ignore exceptions in getting the executable. + } } } diff --git a/src/test/interpreters/currentPathService.test.ts b/src/test/interpreters/currentPathService.test.ts new file mode 100644 index 000000000000..4d136735edf9 --- /dev/null +++ b/src/test/interpreters/currentPathService.test.ts @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { expect } from 'chai'; +import * as TypeMoq from 'typemoq'; +import { IFileSystem } from '../../client/common/platform/types'; +import { IProcessService } from '../../client/common/process/types'; +import { IConfigurationService, IPersistentState, IPersistentStateFactory, IPythonSettings } from '../../client/common/types'; +import { IInterpreterVersionService, InterpreterType, PythonInterpreter } from '../../client/interpreter/contracts'; +import { CurrentPathService } from '../../client/interpreter/locators/services/currentPathService'; +import { IVirtualEnvironmentManager } from '../../client/interpreter/virtualEnvs/types'; +import { IServiceContainer } from '../../client/ioc/types'; + +// tslint:disable-next-line:max-func-body-length +suite('Interpreters CurrentPath Service', () => { + let processService: TypeMoq.IMock; + let fileSystem: TypeMoq.IMock; + let serviceContainer: TypeMoq.IMock; + let virtualEnvironmentManager: TypeMoq.IMock; + let interpreterVersionService: TypeMoq.IMock; + let pythonSettings: TypeMoq.IMock; + let currentPathService: CurrentPathService; + let persistentState: TypeMoq.IMock>; + setup(async () => { + processService = TypeMoq.Mock.ofType(); + virtualEnvironmentManager = TypeMoq.Mock.ofType(); + interpreterVersionService = TypeMoq.Mock.ofType(); + const configurationService = TypeMoq.Mock.ofType(); + pythonSettings = TypeMoq.Mock.ofType(); + configurationService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); + const persistentStateFactory = TypeMoq.Mock.ofType(); + persistentState = TypeMoq.Mock.ofType>(); + // tslint:disable-next-line:no-any + persistentState.setup(p => p.value).returns(() => undefined as any); + persistentState.setup(p => p.updateValue(TypeMoq.It.isAny())).returns(() => Promise.resolve()); + fileSystem = TypeMoq.Mock.ofType(); + persistentStateFactory.setup(p => p.createGlobalPersistentState(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => persistentState.object); + + serviceContainer = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IProcessService), TypeMoq.It.isAny())).returns(() => processService.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IVirtualEnvironmentManager), TypeMoq.It.isAny())).returns(() => virtualEnvironmentManager.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInterpreterVersionService), TypeMoq.It.isAny())).returns(() => interpreterVersionService.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IFileSystem), TypeMoq.It.isAny())).returns(() => fileSystem.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPersistentStateFactory), TypeMoq.It.isAny())).returns(() => persistentStateFactory.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationService), TypeMoq.It.isAny())).returns(() => configurationService.object); + + currentPathService = new CurrentPathService(virtualEnvironmentManager.object, interpreterVersionService.object, processService.object, serviceContainer.object); + }); + + test('Interpreters that do not exist on the file system are not excluded from the list', async () => { + // Specific test for 1305 + const version = 'mockVersion'; + const envName = 'mockEnvName'; + interpreterVersionService.setup(v => v.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(version)); + virtualEnvironmentManager.setup(v => v.getEnvironmentName(TypeMoq.It.isAny())).returns(() => Promise.resolve(envName)); + + const execArgs = ['-c', 'import sys;print(sys.executable)']; + pythonSettings.setup(p => p.pythonPath).returns(() => 'root:Python'); + processService.setup(p => p.exec(TypeMoq.It.isValue('root:Python'), TypeMoq.It.isValue(execArgs), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: 'c:/root:python' })).verifiable(TypeMoq.Times.once()); + processService.setup(p => p.exec(TypeMoq.It.isValue('python'), TypeMoq.It.isValue(execArgs), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: 'c:/python1' })).verifiable(TypeMoq.Times.once()); + processService.setup(p => p.exec(TypeMoq.It.isValue('python2'), TypeMoq.It.isValue(execArgs), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: 'c:/python2' })).verifiable(TypeMoq.Times.once()); + processService.setup(p => p.exec(TypeMoq.It.isValue('python3'), TypeMoq.It.isValue(execArgs), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: 'c:/python3' })).verifiable(TypeMoq.Times.once()); + + fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue('c:/root:python'))).returns(() => Promise.resolve(true)).verifiable(TypeMoq.Times.once()); + fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue('c:/python1'))).returns(() => Promise.resolve(false)).verifiable(TypeMoq.Times.once()); + fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue('c:/python2'))).returns(() => Promise.resolve(false)).verifiable(TypeMoq.Times.once()); + fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue('c:/python3'))).returns(() => Promise.resolve(true)).verifiable(TypeMoq.Times.once()); + + const interpreters = await currentPathService.getInterpreters(); + processService.verifyAll(); + fileSystem.verifyAll(); + expect(interpreters).to.be.of.length(2); + expect(interpreters).to.deep.include({ displayName: `${version} (${envName})`, path: 'c:/root:python', type: InterpreterType.VirtualEnv }); + expect(interpreters).to.deep.include({ displayName: `${version} (${envName})`, path: 'c:/python3', type: InterpreterType.VirtualEnv }); + }); +}); From d8b0d36c024a7930614ab5f5d00a5d3eabdf90af Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 10 Apr 2018 12:27:18 -0700 Subject: [PATCH 108/433] added missing dependency (#1363) --- package.json | 3 +- yarn.lock | 89 +++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 87 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 26d728dd09ba..14a88d081d6b 100644 --- a/package.json +++ b/package.json @@ -1851,6 +1851,7 @@ "opn": "^5.1.0", "pidusage": "^1.2.0", "reflect-metadata": "^0.1.12", + "request": "^2.85.0", "request-progress": "^3.0.0", "rxjs": "^5.5.2", "semver": "^5.4.1", @@ -1932,4 +1933,4 @@ "publisherDisplayName": "Microsoft", "publisherId": "998b010b-e2af-44a5-a6cd-0b5fd3b9b6f8" } -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index 7027a7bdcfbc..9d5817cc0919 100644 --- a/yarn.lock +++ b/yarn.lock @@ -470,6 +470,13 @@ binary-extensions@^1.0.0: version "1.11.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205" +"binary@>= 0.3.0 < 1": + version "0.3.0" + resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79" + dependencies: + buffers "~0.1.1" + chainsaw "~0.1.0" + block-stream@*: version "0.0.9" resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" @@ -550,6 +557,10 @@ buffer-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" +buffers@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" + builtin-modules@^1.0.0, builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -619,6 +630,12 @@ chai@^4.1.2: pathval "^1.0.0" type-detect "^4.0.0" +chainsaw@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98" + dependencies: + traverse ">=0.3.0 <0.4" + chalk@^0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.5.1.tgz#663b3a648b68b55d04690d49167aa837858f2174" @@ -1472,6 +1489,15 @@ fstream-ignore@^1.0.5: inherits "2" minimatch "^3.0.0" +"fstream@>= 0.1.30 < 1": + version "0.1.31" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-0.1.31.tgz#7337f058fbbbbefa8c9f561a28cab0849202c988" + dependencies: + graceful-fs "~3.0.2" + inherits "~2.0.0" + mkdirp "0.5" + rimraf "2" + fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: version "1.0.11" resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" @@ -1695,7 +1721,7 @@ graceful-fs@4.X, graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, gr version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" -graceful-fs@^3.0.0: +graceful-fs@^3.0.0, graceful-fs@~3.0.2: version "3.0.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-3.0.11.tgz#7613c778a1afea62f25c630a086d7f3acbbdd818" dependencies: @@ -2915,6 +2941,13 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +"match-stream@>= 0.0.2 < 1": + version "0.0.2" + resolved "https://registry.yarnpkg.com/match-stream/-/match-stream-0.0.2.tgz#99eb050093b34dffade421b9ac0b410a9cfa17cf" + dependencies: + buffers "~0.1.1" + readable-stream "~1.0.0" + md5.js@1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" @@ -3052,7 +3085,7 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: +mkdirp@0.5, mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: @@ -3367,6 +3400,10 @@ osenv@^0.1.4: os-homedir "^1.0.0" os-tmpdir "^1.0.0" +"over@>= 0.0.5 < 1": + version "0.0.5" + resolved "https://registry.yarnpkg.com/over/-/over-0.0.5.tgz#f29852e70fd7e25f360e013a8ec44c82aedb5708" + p-map@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" @@ -3547,6 +3584,15 @@ pseudomap@^1.0.1, pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" +"pullstream@>= 0.4.1 < 1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/pullstream/-/pullstream-0.4.1.tgz#d6fb3bf5aed697e831150eb1002c25a3f8ae1314" + dependencies: + over ">= 0.0.5 < 1" + readable-stream "~1.0.31" + setimmediate ">= 1.0.2 < 2" + slice-stream ">= 1.0.0 < 2" + pump@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" @@ -3625,7 +3671,7 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" -"readable-stream@>=1.0.33-1 <1.1.0-0", readable-stream@~1.0.17: +"readable-stream@>=1.0.33-1 <1.1.0-0", readable-stream@~1.0.0, readable-stream@~1.0.17, readable-stream@~1.0.31: version "1.0.34" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" dependencies: @@ -3763,6 +3809,12 @@ replace-ext@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" +request-progress@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-3.0.0.tgz#4ca754081c7fec63f505e4faa825aa06cd669dbe" + dependencies: + throttleit "^1.0.0" + request@2.81.0: version "2.81.0" resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" @@ -3790,7 +3842,7 @@ request@2.81.0: tunnel-agent "^0.6.0" uuid "^3.0.0" -request@^2.83.0: +request@^2.83.0, request@^2.85.0: version "2.85.0" resolved "https://registry.yarnpkg.com/request/-/request-2.85.0.tgz#5a03615a47c61420b3eb99b7dba204f83603e1fa" dependencies: @@ -3986,6 +4038,10 @@ set-value@^2.0.0: is-plain-object "^2.0.3" split-string "^3.0.1" +"setimmediate@>= 1.0.1 < 2", "setimmediate@>= 1.0.2 < 2": + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + shortid@^2.2.8: version "2.2.8" resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.8.tgz#033b117d6a2e975804f6f0969dbe7d3d0b355131" @@ -4014,6 +4070,12 @@ slash@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" +"slice-stream@>= 1.0.0 < 2": + version "1.0.0" + resolved "https://registry.yarnpkg.com/slice-stream/-/slice-stream-1.0.0.tgz#5b33bd66f013b1a7f86460b03d463dec39ad3ea0" + dependencies: + readable-stream "~1.0.31" + snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" @@ -4341,6 +4403,10 @@ text-encoding@^0.6.4: version "0.6.4" resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19" +throttleit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c" + through2-filter@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-2.0.0.tgz#60bc55a0dacb76085db1f9dae99ab43f83d622ec" @@ -4450,6 +4516,10 @@ tough-cookie@~2.3.0, tough-cookie@~2.3.3: dependencies: punycode "^1.4.1" +"traverse@>=0.3.0 <0.4": + version "0.3.9" + resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" + tree-kill@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.0.tgz#5846786237b4239014f05db156b643212d4c6f36" @@ -4620,6 +4690,17 @@ untildify@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/untildify/-/untildify-3.0.2.tgz#7f1f302055b3fea0f3e81dc78eb36766cb65e3f1" +unzip@^0.1.11: + version "0.1.11" + resolved "https://registry.yarnpkg.com/unzip/-/unzip-0.1.11.tgz#89749c63b058d7d90d619f86b98aa1535d3b97f0" + dependencies: + binary ">= 0.3.0 < 1" + fstream ">= 0.1.30 < 1" + match-stream ">= 0.0.2 < 1" + pullstream ">= 0.4.1 < 1" + readable-stream "~1.0.31" + setimmediate ">= 1.0.1 < 2" + upath@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/upath/-/upath-1.0.4.tgz#ee2321ba0a786c50973db043a50b7bcba822361d" From c25ad14d6e7825f6e1cb9d1ed5eecddb3e855f88 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 10 Apr 2018 12:41:38 -0700 Subject: [PATCH 109/433] Reverted change that ended up considering symlinked interpreters as duplicate interpreter. (#1353) Fixes #1192 --- news/2 Fixes/1192.md | 1 + .../configuration/interpreterSelector.ts | 3 +- src/client/interpreter/contracts.ts | 1 - .../configuration/interpreterSelector.test.ts | 32 +++++++++---------- 4 files changed, 18 insertions(+), 19 deletions(-) create mode 100644 news/2 Fixes/1192.md diff --git a/news/2 Fixes/1192.md b/news/2 Fixes/1192.md new file mode 100644 index 000000000000..b97883d49d8a --- /dev/null +++ b/news/2 Fixes/1192.md @@ -0,0 +1 @@ +Reverted change that ended up considering symlinked interpreters as duplicate interpreter. diff --git a/src/client/interpreter/configuration/interpreterSelector.ts b/src/client/interpreter/configuration/interpreterSelector.ts index 1d116c7e2ab6..716dced6c0f7 100644 --- a/src/client/interpreter/configuration/interpreterSelector.ts +++ b/src/client/interpreter/configuration/interpreterSelector.ts @@ -76,10 +76,9 @@ export class InterpreterSelector implements IInterpreterSelector { private async removeDuplicates(interpreters: PythonInterpreter[]): Promise { const result: PythonInterpreter[] = []; - await Promise.all(interpreters.map(async item => item.realPath = await this.fileSystem.getRealPathAsync(item.path))); interpreters.forEach(x => { if (result.findIndex(a => a.displayName === x.displayName - && a.type === x.type && this.fileSystem.arePathsSame(a.realPath!, x.realPath!)) < 0) { + && a.type === x.type && this.fileSystem.arePathsSame(path.dirname(a.path), path.dirname(x.path))) < 0) { result.push(x); } }); diff --git a/src/client/interpreter/contracts.ts b/src/client/interpreter/contracts.ts index d2540595fa96..9029a05dbb4e 100644 --- a/src/client/interpreter/contracts.ts +++ b/src/client/interpreter/contracts.ts @@ -67,7 +67,6 @@ export type PythonInterpreter = { envName?: string; envPath?: string; cachedEntry?: boolean; - realPath?: string; }; export type WorkspacePythonPath = { diff --git a/src/test/configuration/interpreterSelector.test.ts b/src/test/configuration/interpreterSelector.test.ts index b53dd61c79d2..5dbb7f7bd70d 100644 --- a/src/test/configuration/interpreterSelector.test.ts +++ b/src/test/configuration/interpreterSelector.test.ts @@ -15,7 +15,7 @@ import { IServiceContainer } from '../../client/ioc/types'; class InterpreterQuickPickItem implements IInterpreterQuickPickItem { public path: string; public label: string; - public description: string; + public description!: string; public detail?: string; constructor(l: string, p: string) { this.path = p; @@ -24,7 +24,7 @@ class InterpreterQuickPickItem implements IInterpreterQuickPickItem { } // tslint:disable-next-line:max-func-body-length -suite('Intepreters - selector', () => { +suite('Interpreters - selector', () => { let serviceContainer: IServiceContainer; let workspace: TypeMoq.IMock; let appShell: TypeMoq.IMock; @@ -65,14 +65,14 @@ suite('Intepreters - selector', () => { test('Suggestions', async () => { const initial: PythonInterpreter[] = [ - { displayName: '1', path: 'path1', type: InterpreterType.Unknown }, - { displayName: '2', path: 'path1', type: InterpreterType.Unknown }, - { displayName: '1', path: 'path1', type: InterpreterType.Unknown }, - { displayName: '2', path: 'path2', type: InterpreterType.Unknown }, - { displayName: '2', path: 'path2', type: InterpreterType.Unknown }, - { displayName: '2 (virtualenv)', path: 'path2', type: InterpreterType.VirtualEnv }, - { displayName: '3', path: 'path2', type: InterpreterType.Unknown }, - { displayName: '4', path: 'path4', type: InterpreterType.Conda } + { displayName: '1', path: 'c:/path1/path1', type: InterpreterType.Unknown }, + { displayName: '2', path: 'c:/path1/path1', type: InterpreterType.Unknown }, + { displayName: '1', path: 'c:/path1/path1', type: InterpreterType.Unknown }, + { displayName: '2', path: 'c:/path2/path2', type: InterpreterType.Unknown }, + { displayName: '2', path: 'c:/path2/path2', type: InterpreterType.Unknown }, + { displayName: '2 (virtualenv)', path: 'c:/path2/path2', type: InterpreterType.VirtualEnv }, + { displayName: '3', path: 'c:/path2/path2', type: InterpreterType.Unknown }, + { displayName: '4', path: 'c:/path4/path4', type: InterpreterType.Conda } ]; interpreterService .setup(x => x.getInterpreters(TypeMoq.It.isAny())) @@ -82,12 +82,12 @@ suite('Intepreters - selector', () => { const actual = await selector.getSuggestions(); const expected: InterpreterQuickPickItem[] = [ - new InterpreterQuickPickItem('1', 'path1'), - new InterpreterQuickPickItem('2', 'path1'), - new InterpreterQuickPickItem('2', 'path2'), - new InterpreterQuickPickItem('2 (virtualenv)', 'path2'), - new InterpreterQuickPickItem('3', 'path2'), - new InterpreterQuickPickItem('4', 'path4') + new InterpreterQuickPickItem('1', 'c:/path1/path1'), + new InterpreterQuickPickItem('2', 'c:/path1/path1'), + new InterpreterQuickPickItem('2', 'c:/path2/path2'), + new InterpreterQuickPickItem('2 (virtualenv)', 'c:/path2/path2'), + new InterpreterQuickPickItem('3', 'c:/path2/path2'), + new InterpreterQuickPickItem('4', 'c:/path4/path4') ]; assert.equal(actual.length, expected.length, 'Suggestion lengths are different.'); From 77d74523b98f52311896ef0d660a4eb9c7904a6c Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 10 Apr 2018 12:42:04 -0700 Subject: [PATCH 110/433] Pass necessary flags to ptvsd during attach (#1332) Fixes #1331 --- src/client/debugger/Common/Contracts.ts | 3 ++- src/client/debugger/configProviders/pythonV2Provider.ts | 3 +++ src/test/debugger/configProvider/provider.attach.test.ts | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/client/debugger/Common/Contracts.ts b/src/client/debugger/Common/Contracts.ts index 85bc8d7f4a2c..f54917476a66 100644 --- a/src/client/debugger/Common/Contracts.ts +++ b/src/client/debugger/Common/Contracts.ts @@ -46,7 +46,8 @@ export enum DebugOptions { BreakOnSystemExitZero = 'BreakOnSystemExitZero', Sudo = 'Sudo', Pyramid = 'Pyramid', - FixFilePathCase = 'FixFilePathCase' + FixFilePathCase = 'FixFilePathCase', + WindowsClient = 'WindowsClient' } export interface ExceptionHandling { diff --git a/src/client/debugger/configProviders/pythonV2Provider.ts b/src/client/debugger/configProviders/pythonV2Provider.ts index 4c6b74d7faac..47e587daabc7 100644 --- a/src/client/debugger/configProviders/pythonV2Provider.ts +++ b/src/client/debugger/configProviders/pythonV2Provider.ts @@ -41,6 +41,9 @@ export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvide if (this.serviceContainer.get(IPlatformService).isWindows && isLocalHost) { debugConfiguration.debugOptions.push(DebugOptions.FixFilePathCase); } + if (this.serviceContainer.get(IPlatformService).isWindows) { + debugConfiguration.debugOptions.push(DebugOptions.WindowsClient); + } if (!debugConfiguration.pathMappings) { debugConfiguration.pathMappings = []; diff --git a/src/test/debugger/configProvider/provider.attach.test.ts b/src/test/debugger/configProvider/provider.attach.test.ts index 596cc907c7dc..30d1a4192800 100644 --- a/src/test/debugger/configProvider/provider.attach.test.ts +++ b/src/test/debugger/configProvider/provider.attach.test.ts @@ -35,6 +35,7 @@ enum OS { const debugOptionsAvailable = [DebugOptions.RedirectOutput]; if (os.value === OS.Windows && provider.debugType === 'pythonExperimental') { debugOptionsAvailable.push(DebugOptions.FixFilePathCase); + debugOptionsAvailable.push(DebugOptions.WindowsClient); } setup(() => { serviceContainer = TypeMoq.Mock.ofType(); From 976bc9424c14e7eb4af12f8ee60e875a58792e63 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 10 Apr 2018 15:41:49 -0700 Subject: [PATCH 111/433] Use a specific version of pycodestyle to resolve broken CI tests (#1366) Fixes #1365 --- requirements.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 439a8d6999b7..40d8dae202cb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,11 @@ +# Install flake8 first, as both flake8 and autopep8 require pycodestyle, +# but flake8 has a tighter pinning. +flake8 autopep8 yapf pylint pep8 prospector -flake8 pydocstyle nose pytest From d06c4fe819016e80df3ca33934bb2796c8837ad0 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Wed, 11 Apr 2018 15:14:06 -0700 Subject: [PATCH 112/433] Use a carret range for 'tmp' --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 14a88d081d6b..fd002fb79265 100644 --- a/package.json +++ b/package.json @@ -1856,7 +1856,7 @@ "rxjs": "^5.5.2", "semver": "^5.4.1", "sudo-prompt": "^8.0.0", - "tmp": "0.0.29", + "tmp": "^0.0.29", "tree-kill": "^1.1.0", "typescript-char": "^0.0.0", "uint64be": "^1.0.1", From 43bf0323b85844bc7d14d8d2a2fa8a33547711c5 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Thu, 12 Apr 2018 12:00:46 -0700 Subject: [PATCH 113/433] Fix handling of escaped string in tokenizer + PTVS launch hardening (#1377) * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * #1096 The if statement is automatically formatted incorrectly * Merge fix * Add more tests * More tests * Typo * Test * Also better handle multiline arguments * Add a couple missing periods [skip ci] * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * Merge fix * #1257 On type formatting errors for args and kwargs * Handle f-strings * Stop importing from test code * #1308 Single line statements leading to an indentation on the next line * #726 editing python after inline if statement invalid indent * Undo change * Move constant * Harden LS startup error checks * #1364 Intellisense doesn't work after specific const string --- src/client/activation/analysis.ts | 36 +++++++++++++++++++++-------- src/client/language/tokenizer.ts | 3 +++ src/test/language/tokenizer.test.ts | 17 ++++++++++++++ 3 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/client/activation/analysis.ts b/src/client/activation/analysis.ts index 554daace8d96..cfc03a4d0d5d 100644 --- a/src/client/activation/analysis.ts +++ b/src/client/activation/analysis.ts @@ -3,9 +3,11 @@ import * as path from 'path'; import { ExtensionContext, OutputChannel } from 'vscode'; -import { Disposable, LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient'; +import { Message } from 'vscode-jsonrpc'; +import { CloseAction, Disposable, ErrorAction, ErrorHandler, LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient'; import { IApplicationShell } from '../common/application/types'; import { isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; +import { createDeferred, Deferred } from '../common/helpers'; import { IFileSystem, IPlatformService } from '../common/platform/types'; import { IProcessService } from '../common/process/types'; import { StopWatch } from '../common/stopWatch'; @@ -21,6 +23,18 @@ const dotNetCommand = 'dotnet'; const languageClientName = 'Python Tools'; const analysisEngineFolder = 'analysis'; +class LanguageServerStartupErrorHandler implements ErrorHandler { + constructor(private readonly deferred: Deferred) { } + public error(error: Error, message: Message, count: number): ErrorAction { + this.deferred.reject(); + return ErrorAction.Shutdown; + } + public closed(): CloseAction { + this.deferred.reject(); + return CloseAction.DoNotRestart; + } +} + export class AnalysisExtensionActivator implements IExtensionActivator { private readonly configuration: IConfigurationService; private readonly appShell: IApplicationShell; @@ -92,16 +106,23 @@ export class AnalysisExtensionActivator implements IExtensionActivator { private async tryStartLanguageClient(context: ExtensionContext, lc: LanguageClient): Promise { let disposable: Disposable | undefined; + const deferred = createDeferred(); try { + lc.clientOptions.errorHandler = new LanguageServerStartupErrorHandler(deferred); + disposable = lc.start(); - await lc.onReady(); + lc.onReady() + .then(() => deferred.resolve()) + .catch(ex => deferred.reject()); + await deferred.promise; + this.output.appendLine(`Language server ready: ${this.sw.elapsedTime} ms`); context.subscriptions.push(disposable); } catch (ex) { if (disposable) { disposable.dispose(); - throw ex; } + throw ex; } } @@ -157,12 +178,8 @@ export class AnalysisExtensionActivator implements IExtensionActivator { // tslint:disable-next-line:no-string-literal properties['SearchPaths'] = searchPaths; - if (isTestExecution()) { - // tslint:disable-next-line:no-string-literal - properties['TestEnvironment'] = true; - } - const selector: string[] = [PYTHON]; + // Options to control the language client return { // Register the server for Python documents @@ -181,7 +198,8 @@ export class AnalysisExtensionActivator implements IExtensionActivator { trimDocumentationText: false, maxDocumentationTextLength: 0 }, - asyncStartup: true + asyncStartup: true, + testEnvironment: isTestExecution() } }; } diff --git a/src/client/language/tokenizer.ts b/src/client/language/tokenizer.ts index fcb29ed8b9a3..e1c8c4b03d9e 100644 --- a/src/client/language/tokenizer.ts +++ b/src/client/language/tokenizer.ts @@ -366,6 +366,9 @@ export class Tokenizer implements ITokenizer { private skipToSingleEndQuote(quote: number): void { while (!this.cs.isEndOfStream()) { + if (this.cs.currentChar === Char.LineFeed || this.cs.currentChar === Char.CarriageReturn) { + return; // Unterminated single-line string + } if (this.cs.currentChar === Char.Backslash && this.cs.nextChar === quote) { this.cs.advance(2); continue; diff --git a/src/test/language/tokenizer.test.ts b/src/test/language/tokenizer.test.ts index 8d37f49dd791..202f0c774297 100644 --- a/src/test/language/tokenizer.test.ts +++ b/src/test/language/tokenizer.test.ts @@ -116,6 +116,23 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(0).type, TokenType.String); assert.equal(tokens.getItemAt(0).length, 14); }); + test('Strings: escape at the end of single quoted string ', async () => { + const t = new Tokenizer(); + // tslint:disable-next-line:quotemark + const tokens = t.tokenize("'quoted\\'\nx"); + assert.equal(tokens.count, 2); + assert.equal(tokens.getItemAt(0).type, TokenType.String); + assert.equal(tokens.getItemAt(0).length, 9); + assert.equal(tokens.getItemAt(1).type, TokenType.Identifier); + }); + test('Strings: escape at the end of double quoted string ', async () => { + const t = new Tokenizer(); + const tokens = t.tokenize('"quoted\\"\nx'); + assert.equal(tokens.count, 2); + assert.equal(tokens.getItemAt(0).type, TokenType.String); + assert.equal(tokens.getItemAt(0).length, 9); + assert.equal(tokens.getItemAt(1).type, TokenType.Identifier); + }); test('Comments', async () => { const t = new Tokenizer(); const tokens = t.tokenize(' #co"""mment1\n\t\n#comm\'ent2 '); From d5891dae551cc335f29e6cb4c3eae56c3ec4a6fb Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 12 Apr 2018 14:41:23 -0700 Subject: [PATCH 114/433] Pin all production dependencies (#1375) --- .appveyor.yml | 2 +- .travis.yml | 2 +- news/3 Code Health/1374.md | 1 + package.json | 68 ++++++++++++++--------------- yarn.lock | 88 ++++++++++++++++++++------------------ 5 files changed, 84 insertions(+), 77 deletions(-) create mode 100644 news/3 Code Health/1374.md diff --git a/.appveyor.yml b/.appveyor.yml index 99a980c431cb..8be46ea0344b 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -34,7 +34,7 @@ init: install: - ps: Install-Product node $env:nodejs_version - npm i -g yarn - - yarn + - yarn --frozen-lockfile - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - python -m pip install -U pip - pip install -t ./pythonFiles/experimental/ptvsd git+https://github.com/Microsoft/ptvsd/ diff --git a/.travis.yml b/.travis.yml index 915c33dd9cad..0fe5de4bafdf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,7 +38,7 @@ before_install: | install: - python -m pip install --upgrade -r requirements.txt - python -m pip install -t ./pythonFiles/experimental/ptvsd git+https://github.com/Microsoft/ptvsd/ - - yarn + - yarn --frozen-lockfile script: - yarn run clean diff --git a/news/3 Code Health/1374.md b/news/3 Code Health/1374.md new file mode 100644 index 000000000000..779b3920a1a8 --- /dev/null +++ b/news/3 Code Health/1374.md @@ -0,0 +1 @@ +Pin all production dependencies. diff --git a/package.json b/package.json index fd002fb79265..434dc231a1d8 100644 --- a/package.json +++ b/package.json @@ -1836,40 +1836,40 @@ "debugger-coverage": "gulp debugger-coverage" }, "dependencies": { - "arch": "^2.1.0", - "diff-match-patch": "^1.0.0", - "fs-extra": "^4.0.2", - "fuzzy": "^0.1.3", - "get-port": "^3.2.0", - "iconv-lite": "^0.4.19", - "inversify": "^4.5.2", - "line-by-line": "^0.1.5", - "lodash": "^4.17.4", - "md5": "^2.2.1", - "minimatch": "^3.0.3", - "named-js-regexp": "^1.3.1", - "opn": "^5.1.0", - "pidusage": "^1.2.0", - "reflect-metadata": "^0.1.12", - "request": "^2.85.0", - "request-progress": "^3.0.0", - "rxjs": "^5.5.2", - "semver": "^5.4.1", - "sudo-prompt": "^8.0.0", - "tmp": "^0.0.29", - "tree-kill": "^1.1.0", - "typescript-char": "^0.0.0", - "uint64be": "^1.0.1", - "unicode": "^10.0.0", - "untildify": "^3.0.2", - "unzip": "^0.1.11", - "vscode-debugadapter": "^1.28.0", - "vscode-debugprotocol": "^1.28.0", - "vscode-extension-telemetry": "^0.0.14", - "vscode-languageclient": "^3.1.0", - "vscode-languageserver": "^3.1.0", - "winreg": "^1.2.4", - "xml2js": "^0.4.17" + "arch": "2.1.0", + "diff-match-patch": "1.0.0", + "fs-extra": "4.0.3", + "fuzzy": "0.1.3", + "get-port": "3.2.0", + "iconv-lite": "0.4.21", + "inversify": "4.11.1", + "line-by-line": "0.1.6", + "lodash": "4.17.5", + "md5": "2.2.1", + "minimatch": "3.0.4", + "named-js-regexp": "1.3.3", + "opn": "5.3.0", + "pidusage": "1.2.0", + "reflect-metadata": "0.1.12", + "request": "2.85.0", + "request-progress": "3.0.0", + "rxjs": "5.5.9", + "semver": "5.5.0", + "sudo-prompt": "8.2.0", + "tmp": "0.0.29", + "tree-kill": "1.2.0", + "typescript-char": "0.0.0", + "uint64be": "1.0.1", + "unicode": "10.0.0", + "untildify": "3.0.2", + "unzip": "0.1.11", + "vscode-debugadapter": "1.28.0", + "vscode-debugprotocol": "1.28.0", + "vscode-extension-telemetry": "0.0.15", + "vscode-languageclient": "3.5.1", + "vscode-languageserver": "3.5.1", + "winreg": "1.2.4", + "xml2js": "0.4.19" }, "devDependencies": { "@types/chai": "^4.1.2", diff --git a/yarn.lock b/yarn.lock index 9d5817cc0919..743208b1dcf3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -268,7 +268,7 @@ aproba@^1.0.3: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" -arch@^2.1.0: +arch@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/arch/-/arch-2.1.0.tgz#3613aa46149064b3c1f0607919bf1d4786e82889" @@ -1053,7 +1053,7 @@ diagnostic-channel@0.2.0: dependencies: semver "^5.3.0" -diff-match-patch@^1.0.0: +diff-match-patch@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.0.tgz#1cc3c83a490d67f95d91e39f6ad1f2e086b63048" @@ -1455,7 +1455,7 @@ from@~0: version "0.1.7" resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" -fs-extra@^4.0.2: +fs-extra@4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" dependencies: @@ -1511,7 +1511,7 @@ function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" -fuzzy@^0.1.3: +fuzzy@0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/fuzzy/-/fuzzy-0.1.3.tgz#4c76ec2ff0ac1a36a9dccf9a00df8623078d4ed8" @@ -1548,7 +1548,7 @@ get-func-name@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" -get-port@^3.2.0: +get-port@3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" @@ -2131,9 +2131,11 @@ husky@^0.14.3: normalize-path "^1.0.0" strip-indent "^2.0.0" -iconv-lite@^0.4.19: - version "0.4.19" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" +iconv-lite@0.4.21: + version "0.4.21" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.21.tgz#c47f8733d02171189ebc4a400f3218d348094798" + dependencies: + safer-buffer "^2.1.0" indent-string@^2.1.0: version "2.1.0" @@ -2164,7 +2166,7 @@ interpret@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" -inversify@^4.5.2: +inversify@4.11.1: version "4.11.1" resolved "https://registry.yarnpkg.com/inversify/-/inversify-4.11.1.tgz#9a10635d1fd347da11da96475b3608babd5945a6" @@ -2617,7 +2619,7 @@ liftoff@^2.1.0: rechoir "^0.6.2" resolve "^1.1.7" -line-by-line@^0.1.5: +line-by-line@0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/line-by-line/-/line-by-line-0.1.6.tgz#6236edd1db2d1695addf11f0268e74a181561c30" @@ -2871,7 +2873,7 @@ lodash.values@~2.4.1: dependencies: lodash.keys "~2.4.1" -lodash@^4.17.4: +lodash@4.17.5, lodash@^4.17.4: version "4.17.5" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" @@ -2955,7 +2957,7 @@ md5.js@1.3.4: hash-base "^3.0.0" inherits "^2.0.1" -md5@^2.2.1: +md5@2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" dependencies: @@ -3043,7 +3045,7 @@ mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.7: dependencies: mime-db "~1.33.0" -"minimatch@2 || 3", minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: +"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" dependencies: @@ -3140,7 +3142,7 @@ multipipe@^0.1.0, multipipe@^0.1.2: dependencies: duplexer2 "0.0.2" -named-js-regexp@^1.3.1: +named-js-regexp@1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/named-js-regexp/-/named-js-regexp-1.3.3.tgz#a2eb1655c74cb82213a4fc82777dfb67b895d8c8" @@ -3336,7 +3338,7 @@ once@~1.3.0: dependencies: wrappy "1" -opn@^5.1.0: +opn@5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/opn/-/opn-5.3.0.tgz#64871565c863875f052cfdf53d3e3cb5adb53b1c" dependencies: @@ -3507,7 +3509,7 @@ performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" -pidusage@^1.2.0: +pidusage@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pidusage/-/pidusage-1.2.0.tgz#65ee96ace4e08a4cd3f9240996c85b367171ee92" @@ -3734,7 +3736,7 @@ redent@^1.0.0: indent-string "^2.1.0" strip-indent "^1.0.1" -reflect-metadata@^0.1.12: +reflect-metadata@0.1.12: version "0.1.12" resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.12.tgz#311bf0c6b63cd782f228a81abe146a2bfa9c56f2" @@ -3809,7 +3811,7 @@ replace-ext@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" -request-progress@^3.0.0: +request-progress@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-3.0.0.tgz#4ca754081c7fec63f505e4faa825aa06cd669dbe" dependencies: @@ -3842,7 +3844,7 @@ request@2.81.0: tunnel-agent "^0.6.0" uuid "^3.0.0" -request@^2.83.0, request@^2.85.0: +request@2.85.0, request@^2.83.0: version "2.85.0" resolved "https://registry.yarnpkg.com/request/-/request-2.85.0.tgz#5a03615a47c61420b3eb99b7dba204f83603e1fa" dependencies: @@ -3972,9 +3974,9 @@ rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.1: dependencies: glob "^7.0.5" -rxjs@^5.5.2: - version "5.5.7" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.7.tgz#afb3d1642b069b2fbf203903d6501d1acb4cda27" +rxjs@5.5.9: + version "5.5.9" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.9.tgz#12a0487794b00f5eb370fec2751bd973a89886fb" dependencies: symbol-observable "1.0.1" @@ -3988,6 +3990,10 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" +safer-buffer@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + samsam@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50" @@ -4000,7 +4006,7 @@ sax@>=0.6.0: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" -"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0, semver@^5.4.1: +"semver@2 || 3 || 4 || 5", semver@5.5.0, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1: version "5.5.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" @@ -4344,9 +4350,9 @@ strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" -sudo-prompt@^8.0.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-8.1.0.tgz#62dce8013b80dd242e5b6ca15d8b8cffb7c85472" +sudo-prompt@8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-8.2.0.tgz#bcd4aaacdb367b77b4bffcce1c658c2b1dd327f3" supports-color@4.4.0: version "4.4.0" @@ -4520,7 +4526,7 @@ tough-cookie@~2.3.0, tough-cookie@~2.3.3: version "0.3.9" resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" -tree-kill@^1.1.0: +tree-kill@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.0.tgz#5846786237b4239014f05db156b643212d4c6f36" @@ -4607,7 +4613,7 @@ typemoq@^2.1.0: lodash "^4.17.4" postinstall-build "^5.0.1" -typescript-char@^0.0.0: +typescript-char@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/typescript-char/-/typescript-char-0.0.0.tgz#558feda737c765a610b737eefbb1775ee9bc8dab" @@ -4639,7 +4645,7 @@ uid-number@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" -uint64be@^1.0.1: +uint64be@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/uint64be/-/uint64be-1.0.1.tgz#1f7154202f2a1b8af353871dda651bf34ce93e95" @@ -4651,7 +4657,7 @@ underscore@~1.8.3: version "1.8.3" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022" -unicode@^10.0.0: +unicode@10.0.0: version "10.0.0" resolved "https://registry.yarnpkg.com/unicode/-/unicode-10.0.0.tgz#e5d51c1db93b6c71a0b879e0b0c4af7e6fdf688e" @@ -4686,11 +4692,11 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" -untildify@^3.0.2: +untildify@3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/untildify/-/untildify-3.0.2.tgz#7f1f302055b3fea0f3e81dc78eb36766cb65e3f1" -unzip@^0.1.11: +unzip@0.1.11: version "0.1.11" resolved "https://registry.yarnpkg.com/unzip/-/unzip-0.1.11.tgz#89749c63b058d7d90d619f86b98aa1535d3b97f0" dependencies: @@ -4916,7 +4922,7 @@ vscode-debugadapter-testsupport@^1.27.0: dependencies: vscode-debugprotocol "1.27.0" -vscode-debugadapter@^1.28.0: +vscode-debugadapter@1.28.0: version "1.28.0" resolved "https://registry.yarnpkg.com/vscode-debugadapter/-/vscode-debugadapter-1.28.0.tgz#ebd6653e3f41db324d9547595375571a8732e966" dependencies: @@ -4927,13 +4933,13 @@ vscode-debugprotocol@1.27.0: version "1.27.0" resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.27.0.tgz#735a43a3cc1235fe587c0ef93fe4e328def7b17c" -vscode-debugprotocol@1.28.0, vscode-debugprotocol@^1.28.0: +vscode-debugprotocol@1.28.0: version "1.28.0" resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.28.0.tgz#b9fb97c3fb2dadbec78e5c1619ff12bf741ce406" -vscode-extension-telemetry@^0.0.14: - version "0.0.14" - resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.14.tgz#17454705b6bb8757351b955d812923f02ee895bf" +vscode-extension-telemetry@0.0.15: + version "0.0.15" + resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.15.tgz#685c32f3b67e8fb85ba689c1d7f88ff90ff87856" dependencies: applicationinsights "1.0.1" @@ -4941,7 +4947,7 @@ vscode-jsonrpc@3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz#87239d9e166b2d7352245b8a813597804c1d63aa" -vscode-languageclient@^3.1.0: +vscode-languageclient@3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-3.5.1.tgz#c78e582459c24e58f88020dfa34065e976186a98" dependencies: @@ -4958,7 +4964,7 @@ vscode-languageserver-types@3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz#e48d79962f0b8e02de955e3f524908e2b19c0374" -vscode-languageserver@^3.1.0: +vscode-languageserver@3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-3.5.1.tgz#e0044b7df4d2447ce12632dfc98f1ab0afacbdff" dependencies: @@ -5012,7 +5018,7 @@ window-size@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" -winreg@^1.2.4: +winreg@1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/winreg/-/winreg-1.2.4.tgz#ba065629b7a925130e15779108cf540990e98d1b" @@ -5038,7 +5044,7 @@ xml2js@0.2.8: dependencies: sax "0.5.x" -xml2js@^0.4.17: +xml2js@0.4.19: version "0.4.19" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" dependencies: From cd22803e109c32bedb5263e2a3c38d71b227dffc Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Fri, 13 Apr 2018 10:29:27 -0700 Subject: [PATCH 115/433] Add telemetry reporting on VS analysis engine usage (#1382) * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * #1096 The if statement is automatically formatted incorrectly * Merge fix * Add more tests * More tests * Typo * Test * Also better handle multiline arguments * Add a couple missing periods [skip ci] * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * Merge fix * #1257 On type formatting errors for args and kwargs * Handle f-strings * Stop importing from test code * #1308 Single line statements leading to an indentation on the next line * #726 editing python after inline if statement invalid indent * Undo change * Move constant * Harden LS startup error checks * #1364 Intellisense doesn't work after specific const string * Telemetry for the analysis enging * PR feedback --- src/client/activation/analysis.ts | 21 +++++++++++++++++++-- src/client/telemetry/constants.ts | 4 ++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/client/activation/analysis.ts b/src/client/activation/analysis.ts index cfc03a4d0d5d..f6702f075ddc 100644 --- a/src/client/activation/analysis.ts +++ b/src/client/activation/analysis.ts @@ -13,6 +13,13 @@ import { IProcessService } from '../common/process/types'; import { StopWatch } from '../common/stopWatch'; import { IConfigurationService, IOutputChannel, IPythonSettings } from '../common/types'; import { IServiceContainer } from '../ioc/types'; +import { + PYTHON_ANALYSIS_ENGINE_DOWNLOADED, + PYTHON_ANALYSIS_ENGINE_ENABLED, + PYTHON_ANALYSIS_ENGINE_ERROR, + PYTHON_ANALYSIS_ENGINE_STARTUP +} from '../telemetry/constants'; +import { getTelemetryReporter } from '../telemetry/telemetry'; import { AnalysisEngineDownloader } from './downloader'; import { InterpreterDataService } from './interpreterDataService'; import { PlatformData } from './platformData'; @@ -26,7 +33,7 @@ const analysisEngineFolder = 'analysis'; class LanguageServerStartupErrorHandler implements ErrorHandler { constructor(private readonly deferred: Deferred) { } public error(error: Error, message: Message, count: number): ErrorAction { - this.deferred.reject(); + this.deferred.reject(error); return ErrorAction.Shutdown; } public closed(): CloseAction { @@ -71,6 +78,9 @@ export class AnalysisExtensionActivator implements IExtensionActivator { const mscorlib = path.join(context.extensionPath, analysisEngineFolder, 'mscorlib.dll'); let downloadPackage = false; + const reporter = getTelemetryReporter(); + reporter.sendTelemetryEvent(PYTHON_ANALYSIS_ENGINE_ENABLED); + if (!await this.fs.fileExistsAsync(mscorlib)) { // Depends on .NET Runtime or SDK this.languageClient = this.createSimpleLanguageClient(context, clientOptions); @@ -80,6 +90,7 @@ export class AnalysisExtensionActivator implements IExtensionActivator { } catch (ex) { if (await this.isDotNetInstalled()) { this.appShell.showErrorMessage(`.NET Runtime appears to be installed but the language server did not start. Error ${ex}`); + reporter.sendTelemetryEvent(PYTHON_ANALYSIS_ENGINE_ERROR, { error: 'Failed to start (MSIL)' }); return false; } // No .NET Runtime, no mscorlib - need to download self-contained package. @@ -90,6 +101,7 @@ export class AnalysisExtensionActivator implements IExtensionActivator { if (downloadPackage) { const downloader = new AnalysisEngineDownloader(this.services, analysisEngineFolder); await downloader.downloadAnalysisEngine(context); + reporter.sendTelemetryEvent(PYTHON_ANALYSIS_ENGINE_DOWNLOADED); } const serverModule = path.join(context.extensionPath, analysisEngineFolder, this.platformData.getEngineExecutableName()); @@ -100,6 +112,7 @@ export class AnalysisExtensionActivator implements IExtensionActivator { return true; } catch (ex) { this.appShell.showErrorMessage(`Language server failed to start. Error ${ex}`); + reporter.sendTelemetryEvent(PYTHON_ANALYSIS_ENGINE_ERROR, { error: 'Failed to start (platform)' }); return false; } } @@ -108,16 +121,20 @@ export class AnalysisExtensionActivator implements IExtensionActivator { let disposable: Disposable | undefined; const deferred = createDeferred(); try { + const sw = new StopWatch(); lc.clientOptions.errorHandler = new LanguageServerStartupErrorHandler(deferred); disposable = lc.start(); lc.onReady() .then(() => deferred.resolve()) - .catch(ex => deferred.reject()); + .catch(deferred.reject); await deferred.promise; this.output.appendLine(`Language server ready: ${this.sw.elapsedTime} ms`); context.subscriptions.push(disposable); + + const reporter = getTelemetryReporter(); + reporter.sendTelemetryEvent(PYTHON_ANALYSIS_ENGINE_STARTUP, {}, { startup_time: sw.elapsedTime }); } catch (ex) { if (disposable) { disposable.dispose(); diff --git a/src/client/telemetry/constants.ts b/src/client/telemetry/constants.ts index bf02b07c63c7..be0cdc8a2c21 100644 --- a/src/client/telemetry/constants.ts +++ b/src/client/telemetry/constants.ts @@ -30,3 +30,7 @@ export const UNITTEST_STOP = 'UNITTEST.STOP'; export const UNITTEST_RUN = 'UNITTEST.RUN'; export const UNITTEST_DISCOVER = 'UNITTEST.DISCOVER'; export const UNITTEST_VIEW_OUTPUT = 'UNITTEST.VIEW_OUTPUT'; +export const PYTHON_ANALYSIS_ENGINE_ENABLED = 'PYTHON_ANALYSIS_ENGINE.ENABLED'; +export const PYTHON_ANALYSIS_ENGINE_DOWNLOADED = 'PYTHON_ANALYSIS_ENGINE.DOWNLOADED'; +export const PYTHON_ANALYSIS_ENGINE_ERROR = 'PYTHON_ANALYSIS_ENGINE.ERROR'; +export const PYTHON_ANALYSIS_ENGINE_STARTUP = 'PYTHON_ANALYSIS_ENGINE.STARTUP'; From f689d6318410404e10e451e03c34e4e0187cc12b Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 13 Apr 2018 11:25:17 -0700 Subject: [PATCH 116/433] add PYTHONPATH to search paths (#1388) --- src/client/activation/analysis.ts | 7 ++++++- src/client/common/variables/types.ts | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/client/activation/analysis.ts b/src/client/activation/analysis.ts index f6702f075ddc..7ed94f893a8f 100644 --- a/src/client/activation/analysis.ts +++ b/src/client/activation/analysis.ts @@ -12,6 +12,7 @@ import { IFileSystem, IPlatformService } from '../common/platform/types'; import { IProcessService } from '../common/process/types'; import { StopWatch } from '../common/stopWatch'; import { IConfigurationService, IOutputChannel, IPythonSettings } from '../common/types'; +import { IEnvironmentVariablesProvider } from '../common/variables/types'; import { IServiceContainer } from '../ioc/types'; import { PYTHON_ANALYSIS_ENGINE_DOWNLOADED, @@ -192,8 +193,12 @@ export class AnalysisExtensionActivator implements IExtensionActivator { searchPaths = `${searchPaths};${extraPaths.join(';')}`; } } + + const envProvider = this.services.get(IEnvironmentVariablesProvider); + const pythonPath = (await envProvider.getEnvironmentVariables()).PYTHONPATH; + // tslint:disable-next-line:no-string-literal - properties['SearchPaths'] = searchPaths; + properties['SearchPaths'] = `${searchPaths};${pythonPath ? pythonPath : ''}`; const selector: string[] = [PYTHON]; diff --git a/src/client/common/variables/types.ts b/src/client/common/variables/types.ts index 3b5ee44a3481..1130ffd33c8c 100644 --- a/src/client/common/variables/types.ts +++ b/src/client/common/variables/types.ts @@ -39,5 +39,5 @@ export const IEnvironmentVariablesProvider = Symbol('IEnvironmentVariablesProvid export interface IEnvironmentVariablesProvider { onDidEnvironmentVariablesChange: Event; - getEnvironmentVariables(resource?: Uri): Promise; + getEnvironmentVariables(resource?: Uri): Promise; } From 0dbb57f59fb31f82d2aae0e1b8a405afa5d19c68 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 13 Apr 2018 17:52:32 -0700 Subject: [PATCH 117/433] Run scripts on Travis as and when required Run scripts on Travis as and when required --- .travis.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0fe5de4bafdf..99ec74c36292 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,10 +41,10 @@ install: - yarn --frozen-lockfile script: - - yarn run clean - - yarn run vscode:prepublish - - yarn run cover:enable - if [ $DEBUGGER_TEST == "true" ]; then + yarn run clean; + yarn run vscode:prepublish; + yarn run cover:enable; yarn run testDebugger --silent; fi - yarn run debugger-coverage @@ -57,19 +57,19 @@ script: # - if [ $TRAVIS_UPLOAD_COVERAGE == "true" ]; then # bash <(curl -s https://codecov.io/bash); # fi - - yarn run clean - - yarn run vscode:prepublish - - yarn run cover:enable - if [ $SINGLE_WORKSPACE_TEST == "true" ]; then + yarn run clean; + yarn run vscode:prepublish; + yarn run cover:enable; yarn run testSingleWorkspace --silent; fi - if [ $TRAVIS_UPLOAD_COVERAGE == "true" ]; then bash <(curl -s https://codecov.io/bash); fi - - yarn run clean - - yarn run vscode:prepublish - - yarn run cover:enable - if [ $MULTIROOT_WORKSPACE_TEST == "true" ]; then + yarn run clean; + yarn run vscode:prepublish; + yarn run cover:enable; yarn run testMultiWorkspace --silent; fi - if [ $TRAVIS_UPLOAD_COVERAGE == "true" ]; then From eccbe9e43fe3cea29b9ef234db7e136bfe542b6a Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 16 Apr 2018 15:09:47 -0700 Subject: [PATCH 118/433] Link to the Italian PR --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5687fda5a28c..18bb6feb2a63 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ To see all available Python commands, open the Command Palette and type ```Pytho The extension is available in multiple languages thanks to external contributors (if you would like to contribute a translation, see the -[pull request which added simplified Chinese](https://github.com/Microsoft/vscode-python/pull/240)): +[pull request which added Italian](https://github.com/Microsoft/vscode-python/pull/1152)): * `en` * `it` From d4df05833e486bb518b8532e203191911404e451 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 16 Apr 2018 16:41:40 -0700 Subject: [PATCH 119/433] Add a missing news entry --- news/2 Fixes/1364.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/2 Fixes/1364.md diff --git a/news/2 Fixes/1364.md b/news/2 Fixes/1364.md new file mode 100644 index 000000000000..df93759e9dd4 --- /dev/null +++ b/news/2 Fixes/1364.md @@ -0,0 +1 @@ +Do not have the formatter consider single-quoted string multiline even if it is not terminated. From d3c6dc0531c30b099658eb61943f1b1ede6b5a35 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 16 Apr 2018 20:32:25 -0700 Subject: [PATCH 120/433] Running python code without debugging using the experimental debugger (#1373) Fixes #882 Remove python file used to launch the PTVSD debugger Added ability to run code without debugging using PTVSD Launching PTVSD using -m (run as a python module) --- pythonFiles/experimental/ptvsd_launcher.py | 96 --------------- .../debugger/DebugClients/DebugFactory.ts | 16 ++- .../debugger/DebugClients/LocalDebugClient.ts | 23 ++-- .../debugger/DebugClients/launcherProvider.ts | 10 +- .../DebugClients/localDebugClientV2.ts | 29 +++++ .../debugger/DebugClients/nonDebugClientV2.ts | 35 ++++++ src/client/debugger/mainV2.ts | 5 + .../debugger/launcherScriptProvider.test.ts | 8 +- src/test/debugger/run.test.ts | 112 ++++++++++++++++++ .../pythonFiles/debugging/sampleWithSleep.py | 8 ++ 10 files changed, 220 insertions(+), 122 deletions(-) delete mode 100644 pythonFiles/experimental/ptvsd_launcher.py create mode 100644 src/client/debugger/DebugClients/localDebugClientV2.ts create mode 100644 src/client/debugger/DebugClients/nonDebugClientV2.ts create mode 100644 src/test/debugger/run.test.ts create mode 100644 src/test/pythonFiles/debugging/sampleWithSleep.py diff --git a/pythonFiles/experimental/ptvsd_launcher.py b/pythonFiles/experimental/ptvsd_launcher.py deleted file mode 100644 index 3be94266f010..000000000000 --- a/pythonFiles/experimental/ptvsd_launcher.py +++ /dev/null @@ -1,96 +0,0 @@ -# Python Tools for Visual Studio -# Copyright(c) Microsoft Corporation -# All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the License); you may not use -# this file except in compliance with the License. You may obtain a copy of the -# License at http://www.apache.org/licenses/LICENSE-2.0 -# -# THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS -# OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY -# IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, -# MERCHANTABLITY OR NON-INFRINGEMENT. -# -# See the Apache Version 2.0 License for specific language governing -# permissions and limitations under the License. - -""" -Starts Debugging, expected to start with normal program -to start as first argument and directory to run from as -the second argument. -""" - -__author__ = "Microsoft Corporation " -__version__ = "3.2.0.0" - -import os -import os.path -import sys -import traceback - -# Arguments are: -# 1. Working directory. -# 2. VS debugger port to connect to. -# 3. GUID for the debug session. -# 4. Debug options (as list of names - see enum PythonDebugOptions). -# 5. '-g' to use the installed ptvsd package, rather than bundled one. -# 6. '-m' or '-c' to override the default run-as mode. [optional] -# 7. Startup script name. -# 8. Script arguments. - -# change to directory we expected to start from -os.chdir(sys.argv[1]) - -port_num = int(sys.argv[2]) -debug_id = sys.argv[3] -debug_options = set([opt.strip() for opt in sys.argv[4].split(',')]) - -del sys.argv[0:5] - -# Use bundled ptvsd or not? -bundled_ptvsd = True -if sys.argv and sys.argv[0] == '-g': - bundled_ptvsd = False - del sys.argv[0] - -# set run_as mode appropriately -run_as = 'script' -if sys.argv and sys.argv[0] == '-m': - run_as = 'module' - del sys.argv[0] -if sys.argv and sys.argv[0] == '-c': - run_as = 'code' - del sys.argv[0] - -# preserve filename before we del sys -filename = sys.argv[0] - -# fix sys.path to be the script file dir -sys.path[0] = '' - -# Load the debugger package -try: - if bundled_ptvsd: - ptvs_lib_path = os.path.dirname(__file__) - sys.path.insert(0, ptvs_lib_path) - import ptvsd - import ptvsd.debugger as vspd - vspd.DONT_DEBUG.append(os.path.normcase(__file__)) -except: - traceback.print_exc() - print(''' -Internal error detected. Please copy the above traceback and report at -https://github.com/Microsoft/vscode-python/issues/new - -Press Enter to close. . .''') - try: - raw_input() - except NameError: - input() - sys.exit(1) -finally: - if bundled_ptvsd: - sys.path.remove(ptvs_lib_path) - -# and start debugging -vspd.debug(filename, port_num, debug_id, debug_options, run_as) diff --git a/src/client/debugger/DebugClients/DebugFactory.ts b/src/client/debugger/DebugClients/DebugFactory.ts index 21229631727e..f8c3b6378e9c 100644 --- a/src/client/debugger/DebugClients/DebugFactory.ts +++ b/src/client/debugger/DebugClients/DebugFactory.ts @@ -1,17 +1,25 @@ import { DebugSession } from 'vscode-debugadapter'; import { AttachRequestArguments, LaunchRequestArguments } from '../Common/Contracts'; +import { IDebugLauncherScriptProvider } from '../types'; import { DebugClient } from './DebugClient'; -import { DebuggerLauncherScriptProvider, DebuggerV2LauncherScriptProvider, NoDebugLauncherScriptProvider } from './launcherProvider'; +import { DebuggerLauncherScriptProvider, NoDebugLauncherScriptProvider } from './launcherProvider'; import { LocalDebugClient } from './LocalDebugClient'; +import { LocalDebugClientV2 } from './localDebugClientV2'; import { NonDebugClient } from './NonDebugClient'; +import { NonDebugClientV2 } from './nonDebugClientV2'; import { RemoteDebugClient } from './RemoteDebugClient'; export function CreateLaunchDebugClient(launchRequestOptions: LaunchRequestArguments, debugSession: DebugSession, canLaunchTerminal: boolean): DebugClient<{}> { + let launchScriptProvider: IDebugLauncherScriptProvider; + let debugClientClass: typeof LocalDebugClient; if (launchRequestOptions.noDebug === true) { - return new NonDebugClient(launchRequestOptions, debugSession, canLaunchTerminal, new NoDebugLauncherScriptProvider()); + launchScriptProvider = new NoDebugLauncherScriptProvider(); + debugClientClass = launchRequestOptions.type === 'pythonExperimental' ? NonDebugClientV2 : NonDebugClient; + } else { + launchScriptProvider = new DebuggerLauncherScriptProvider(); + debugClientClass = launchRequestOptions.type === 'pythonExperimental' ? LocalDebugClientV2 : LocalDebugClient; } - const launchScriptProvider = launchRequestOptions.type === 'pythonExperimental' ? new DebuggerV2LauncherScriptProvider() : new DebuggerLauncherScriptProvider(); - return new LocalDebugClient(launchRequestOptions, debugSession, canLaunchTerminal, launchScriptProvider); + return new debugClientClass(launchRequestOptions, debugSession, canLaunchTerminal, launchScriptProvider); } export function CreateAttachDebugClient(attachRequestOptions: AttachRequestArguments, debugSession: DebugSession): DebugClient<{}> { return new RemoteDebugClient(attachRequestOptions, debugSession); diff --git a/src/client/debugger/DebugClients/LocalDebugClient.ts b/src/client/debugger/DebugClients/LocalDebugClient.ts index abcb14919bc8..661c50f12c76 100644 --- a/src/client/debugger/DebugClients/LocalDebugClient.ts +++ b/src/client/debugger/DebugClients/LocalDebugClient.ts @@ -94,10 +94,7 @@ export class LocalDebugClient extends DebugClient { if (typeof this.args.pythonPath === 'string' && this.args.pythonPath.trim().length > 0) { pythonPath = this.args.pythonPath; } - const ptVSToolsFilePath = this.launcherScriptProvider.getLauncherFilePath(); - const launcherArgs = this.buildLauncherArguments(); - - const args = [ptVSToolsFilePath, processCwd, dbgServer.port.toString(), '34806ad9-833a-4524-8cd6-18ca4aa74f14'].concat(launcherArgs); + const args = this.buildLaunchArguments(processCwd, dbgServer.port); switch (this.args.console) { case 'externalTerminal': case 'integratedTerminal': { @@ -154,8 +151,13 @@ export class LocalDebugClient extends DebugClient { let x = 0; }); } + private buildLaunchArguments(cwd: string, debugPort: number): string[] { + return [...this.buildDebugArguments(cwd, debugPort), ...this.buildStandardArguments()]; + } + // tslint:disable-next-line:member-ordering - protected buildLauncherArguments(): string[] { + protected buildDebugArguments(cwd: string, debugPort: number): string[] { + const ptVSToolsFilePath = this.launcherScriptProvider.getLauncherFilePath(); const vsDebugOptions: string[] = [DebugOptions.RedirectOutput]; if (Array.isArray(this.args.debugOptions)) { this.args.debugOptions.filter(opt => VALID_DEBUG_OPTIONS.indexOf(opt) >= 0) @@ -166,15 +168,18 @@ export class LocalDebugClient extends DebugClient { if (djangoIndex >= 0) { vsDebugOptions[djangoIndex] = 'DjangoDebugging'; } + return [ptVSToolsFilePath, cwd, debugPort.toString(), '34806ad9-833a-4524-8cd6-18ca4aa74f14', vsDebugOptions.join(',')]; + } + // tslint:disable-next-line:member-ordering + protected buildStandardArguments() { const programArgs = Array.isArray(this.args.args) && this.args.args.length > 0 ? this.args.args : []; if (typeof this.args.module === 'string' && this.args.module.length > 0) { - return [vsDebugOptions.join(','), '-m', this.args.module].concat(programArgs); + return ['-m', this.args.module, ...programArgs]; } - const args = [vsDebugOptions.join(',')]; if (this.args.program && this.args.program.length > 0) { - args.push(this.args.program); + return [this.args.program, ...programArgs]; } - return args.concat(programArgs); + return programArgs; } private launchExternalTerminal(sudo: boolean, cwd: string, pythonPath: string, args: string[], env: {}) { return new Promise((resolve, reject) => { diff --git a/src/client/debugger/DebugClients/launcherProvider.ts b/src/client/debugger/DebugClients/launcherProvider.ts index 25f722457918..06ea43e0002e 100644 --- a/src/client/debugger/DebugClients/launcherProvider.ts +++ b/src/client/debugger/DebugClients/launcherProvider.ts @@ -1,6 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +'use strict'; + +// tslint:disable:max-classes-per-file + import * as path from 'path'; import { IDebugLauncherScriptProvider } from '../types'; @@ -15,9 +19,3 @@ export class DebuggerLauncherScriptProvider implements IDebugLauncherScriptProvi return path.join(path.dirname(__dirname), '..', '..', '..', 'pythonFiles', 'PythonTools', 'visualstudio_py_launcher.py'); } } - -export class DebuggerV2LauncherScriptProvider implements IDebugLauncherScriptProvider { - public getLauncherFilePath(): string { - return path.join(path.dirname(__dirname), '..', '..', '..', 'pythonFiles', 'experimental', 'ptvsd_launcher.py'); - } -} diff --git a/src/client/debugger/DebugClients/localDebugClientV2.ts b/src/client/debugger/DebugClients/localDebugClientV2.ts new file mode 100644 index 000000000000..417efba39e26 --- /dev/null +++ b/src/client/debugger/DebugClients/localDebugClientV2.ts @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { DebugSession } from 'vscode-debugadapter'; +import { LaunchRequestArguments } from '../Common/Contracts'; +import { IDebugLauncherScriptProvider } from '../types'; +import { LocalDebugClient } from './LocalDebugClient'; + +export class LocalDebugClientV2 extends LocalDebugClient { + constructor(args: LaunchRequestArguments, debugSession: DebugSession, canLaunchTerminal: boolean, launcherScriptProvider: IDebugLauncherScriptProvider) { + super(args, debugSession, canLaunchTerminal, launcherScriptProvider); + } + protected buildDebugArguments(cwd: string, debugPort: number): string[] { + const noDebugArg = this.args.noDebug ? ['--nodebug'] : []; + return ['-m', 'ptvsd', ...noDebugArg, '--host', 'localhost', '--port', debugPort.toString()]; + } + protected buildStandardArguments() { + const programArgs = Array.isArray(this.args.args) && this.args.args.length > 0 ? this.args.args : []; + if (typeof this.args.module === 'string' && this.args.module.length > 0) { + return ['-m', this.args.module, ...programArgs]; + } + if (this.args.program && this.args.program.length > 0) { + return ['--file', this.args.program, ...programArgs]; + } + return programArgs; + } +} diff --git a/src/client/debugger/DebugClients/nonDebugClientV2.ts b/src/client/debugger/DebugClients/nonDebugClientV2.ts new file mode 100644 index 000000000000..0d47171e810c --- /dev/null +++ b/src/client/debugger/DebugClients/nonDebugClientV2.ts @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { ChildProcess } from 'child_process'; +import { DebugSession } from 'vscode-debugadapter'; +import { LaunchRequestArguments } from '../Common/Contracts'; +import { IDebugLauncherScriptProvider } from '../types'; +import { DebugType } from './DebugClient'; +import { LocalDebugClientV2 } from './localDebugClientV2'; + +export class NonDebugClientV2 extends LocalDebugClientV2 { + constructor(args: LaunchRequestArguments, debugSession: DebugSession, canLaunchTerminal: boolean, launcherScriptProvider: IDebugLauncherScriptProvider) { + super(args, debugSession, canLaunchTerminal, launcherScriptProvider); + } + + public get DebugType(): DebugType { + return DebugType.RunLocal; + } + + public Stop() { + super.Stop(); + if (this.pyProc) { + try { + this.pyProc!.kill(); + // tslint:disable-next-line:no-empty + } catch { } + this.pyProc = undefined; + } + } + protected handleProcessOutput(proc: ChildProcess, _failedToLaunch: (error: Error | string | Buffer) => void) { + // Do nothing + } +} diff --git a/src/client/debugger/mainV2.ts b/src/client/debugger/mainV2.ts index 280e04b58d59..2d8855e7354b 100644 --- a/src/client/debugger/mainV2.ts +++ b/src/client/debugger/mainV2.ts @@ -21,6 +21,7 @@ import { DebugProtocol } from 'vscode-debugprotocol'; import '../../client/common/extensions'; import { noop, sleep } from '../common/core.utils'; import { createDeferred, Deferred, isNotInstalledError } from '../common/helpers'; +import { IFileSystem } from '../common/platform/types'; import { ICurrentProcess } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { AttachRequestArguments, LaunchRequestArguments } from './Common/Contracts'; @@ -98,6 +99,10 @@ export class PythonDebugger extends DebugSession { } protected launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): void { + const fs = this.serviceContainer.get(IFileSystem); + if ((typeof args.module !== 'string' || args.module.length === 0) && args.program && !fs.fileExistsSync(args.program)) { + return this.sendErrorResponse(response, { format: `File does not exist. "${args.program}"`, id: 1 }, undefined, undefined, ErrorDestination.User); + } this.launchPTVSD(args) .then(() => this.waitForPTVSDToConnect(args)) .then(() => this.emit('debugger_launched')) diff --git a/src/test/debugger/launcherScriptProvider.test.ts b/src/test/debugger/launcherScriptProvider.test.ts index 147869aa8f20..3a9358a9ff8e 100644 --- a/src/test/debugger/launcherScriptProvider.test.ts +++ b/src/test/debugger/launcherScriptProvider.test.ts @@ -4,7 +4,7 @@ import { expect } from 'chai'; import * as fs from 'fs'; import * as path from 'path'; -import { DebuggerLauncherScriptProvider, DebuggerV2LauncherScriptProvider, NoDebugLauncherScriptProvider } from '../../client/debugger/DebugClients/launcherProvider'; +import { DebuggerLauncherScriptProvider, NoDebugLauncherScriptProvider } from '../../client/debugger/DebugClients/launcherProvider'; suite('Debugger - Launcher Script Provider', () => { test('Ensure stable debugger gets the old launcher from PythonTools directory', () => { @@ -19,10 +19,4 @@ suite('Debugger - Launcher Script Provider', () => { expect(launcherPath).to.be.equal(expectedPath); expect(fs.existsSync(launcherPath)).to.be.equal(true, 'file does not exist'); }); - test('Ensure experimental debugger gets the new launcher from experimentals directory', () => { - const launcherPath = new DebuggerV2LauncherScriptProvider().getLauncherFilePath(); - const expectedPath = path.join(path.dirname(__dirname), '..', '..', 'pythonFiles', 'experimental', 'ptvsd_launcher.py'); - expect(launcherPath).to.be.equal(expectedPath); - expect(fs.existsSync(launcherPath)).to.be.equal(true, 'file does not exist'); - }); }); diff --git a/src/test/debugger/run.test.ts b/src/test/debugger/run.test.ts new file mode 100644 index 000000000000..f1ebdc3ebd18 --- /dev/null +++ b/src/test/debugger/run.test.ts @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:no-invalid-this no-require-imports no-require-imports no-var-requires + +import { expect } from 'chai'; +import * as path from 'path'; +import { DebugClient } from 'vscode-debugadapter-testsupport'; +import { DebugProtocol } from 'vscode-debugprotocol'; +import { EXTENSION_ROOT_DIR } from '../../client/common/constants'; +import { noop } from '../../client/common/core.utils'; +import { PTVSD_PATH } from '../../client/debugger/Common/constants'; +import { DebugOptions, LaunchRequestArguments } from '../../client/debugger/Common/Contracts'; +import { PYTHON_PATH, sleep } from '../common'; +import { IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; +import { createDebugAdapter } from './utils'; + +const isProcessRunning = require('is-running') as (number) => boolean; + +const debugFilesPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'debugging'); +const debuggerType = 'pythonExperimental'; +suite('Run without Debugging', () => { + let debugClient: DebugClient; + setup(async function () { + if (!IS_MULTI_ROOT_TEST || !TEST_DEBUGGER) { + this.skip(); + } + await new Promise(resolve => setTimeout(resolve, 1000)); + const coverageDirectory = path.join(EXTENSION_ROOT_DIR, `debug_coverage_nodebug${this.currentTest.title}`); + debugClient = await createDebugAdapter(coverageDirectory); + }); + teardown(async () => { + // Wait for a second before starting another test (sometimes, sockets take a while to get closed). + await sleep(1000); + try { + await debugClient.stop().catch(noop); + // tslint:disable-next-line:no-empty + } catch (ex) { } + await sleep(1000); + }); + function buildLauncArgs(pythonFile: string, stopOnEntry: boolean = false): LaunchRequestArguments { + // tslint:disable-next-line:no-unnecessary-local-variable + const options: LaunchRequestArguments = { + program: path.join(debugFilesPath, pythonFile), + cwd: debugFilesPath, + stopOnEntry, + noDebug: true, + debugOptions: [DebugOptions.RedirectOutput], + pythonPath: PYTHON_PATH, + args: [], + env: { PYTHONPATH: PTVSD_PATH }, + envFile: '', + logToFile: true, + type: debuggerType + }; + + return options; + } + + test('Should run program to the end', async () => { + await Promise.all([ + debugClient.configurationSequence(), + debugClient.launch(buildLauncArgs('simplePrint.py', false)), + debugClient.waitForEvent('initialized'), + debugClient.waitForEvent('terminated') + ]); + }); + test('test stderr output for Python', async () => { + await Promise.all([ + debugClient.configurationSequence(), + debugClient.launch(buildLauncArgs('stdErrOutput.py', false)), + debugClient.waitForEvent('initialized'), + debugClient.assertOutput('stderr', 'error output'), + debugClient.waitForEvent('terminated') + ]); + }); + test('Test stdout output', async () => { + await Promise.all([ + debugClient.configurationSequence(), + debugClient.launch(buildLauncArgs('stdOutOutput.py', false)), + debugClient.waitForEvent('initialized'), + debugClient.assertOutput('stdout', 'normal output'), + debugClient.waitForEvent('terminated') + ]); + }); + test('Should kill python process when ending debug session', async () => { + const processIdOutput = new Promise(resolve => { + debugClient.on('output', (event: DebugProtocol.OutputEvent) => { + if (event.event === 'output' && event.body.category === 'stdout') { + resolve(parseInt(event.body.output.trim(), 10)); + } + }); + }); + await Promise.all([ + debugClient.configurationSequence(), + debugClient.launch(buildLauncArgs('sampleWithSleep.py', false)), + debugClient.waitForEvent('initialized'), + processIdOutput + ]); + + const processId = await processIdOutput; + expect(processId).to.be.greaterThan(0, 'Invalid process id'); + + await debugClient.stop(); + await sleep(1000); + + // Confirm the process is dead + expect(isProcessRunning(processId)).to.be.equal(false, 'Python program is still alive'); + }); +}); diff --git a/src/test/pythonFiles/debugging/sampleWithSleep.py b/src/test/pythonFiles/debugging/sampleWithSleep.py new file mode 100644 index 000000000000..7a84f4f0da0c --- /dev/null +++ b/src/test/pythonFiles/debugging/sampleWithSleep.py @@ -0,0 +1,8 @@ +import time +import os +print(os.getpid()) +time.sleep(1) +for i in 10000: + time.sleep(0.1) + print(i) +print('end') From 12b62069d29a79da4567561f22c78e0857b355cb Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Tue, 17 Apr 2018 13:08:31 -0700 Subject: [PATCH 121/433] Jedi 0.12 (#1418) * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * #1096 The if statement is automatically formatted incorrectly * Merge fix * Add more tests * More tests * Typo * Test * Also better handle multiline arguments * Add a couple missing periods [skip ci] * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * Merge fix * #1257 On type formatting errors for args and kwargs * Handle f-strings * Stop importing from test code * #1308 Single line statements leading to an indentation on the next line * #726 editing python after inline if statement invalid indent * Undo change * Move constant * Harden LS startup error checks * #1364 Intellisense doesn't work after specific const string * Telemetry for the analysis enging * PR feedback * Fix typo * Test baseline update * Jedi 0.12 * Priority to goto_defition * News --- news/1 Enhancements/1400.md | 1 + news/2 Fixes/1033.md | 1 + pythonFiles/completion.py | 2 +- pythonFiles/jedi/__init__.py | 6 +- pythonFiles/jedi/_compatibility.py | 314 +++++++-- pythonFiles/jedi/api/__init__.py | 148 ++-- pythonFiles/jedi/api/classes.py | 30 +- pythonFiles/jedi/api/completion.py | 24 +- pythonFiles/jedi/api/environment.py | 393 +++++++++++ pythonFiles/jedi/api/exceptions.py | 10 + pythonFiles/jedi/api/helpers.py | 41 +- pythonFiles/jedi/api/interpreter.py | 22 +- pythonFiles/jedi/api/keywords.py | 68 +- pythonFiles/jedi/api/project.py | 200 ++++++ pythonFiles/jedi/api/replstartup.py | 4 +- pythonFiles/jedi/cache.py | 26 +- pythonFiles/jedi/common/utils.py | 12 + pythonFiles/jedi/debug.py | 2 +- pythonFiles/jedi/evaluate/__init__.py | 51 +- pythonFiles/jedi/evaluate/analysis.py | 18 +- pythonFiles/jedi/evaluate/arguments.py | 61 +- pythonFiles/jedi/evaluate/base_context.py | 42 +- pythonFiles/jedi/evaluate/cache.py | 4 +- .../jedi/evaluate/compiled/__init__.py | 651 +----------------- pythonFiles/jedi/evaluate/compiled/access.py | 490 +++++++++++++ pythonFiles/jedi/evaluate/compiled/context.py | 474 +++++++++++++ pythonFiles/jedi/evaluate/compiled/fake.py | 221 ++---- .../jedi/evaluate/compiled/fake/builtins.pym | 5 +- .../jedi/evaluate/compiled/getattr_static.py | 9 +- pythonFiles/jedi/evaluate/compiled/mixed.py | 90 +-- .../evaluate/compiled/subprocess/__init__.py | 340 +++++++++ .../evaluate/compiled/subprocess/__main__.py | 49 ++ .../evaluate/compiled/subprocess/functions.py | 113 +++ .../jedi/evaluate/context/asynchronous.py | 38 + pythonFiles/jedi/evaluate/context/function.py | 48 +- pythonFiles/jedi/evaluate/context/instance.py | 85 ++- pythonFiles/jedi/evaluate/context/iterable.py | 219 +++--- pythonFiles/jedi/evaluate/context/klass.py | 10 +- pythonFiles/jedi/evaluate/context/module.py | 51 +- .../jedi/evaluate/context/namespace.py | 28 +- pythonFiles/jedi/evaluate/docstrings.py | 104 +-- pythonFiles/jedi/evaluate/dynamic.py | 74 +- pythonFiles/jedi/evaluate/filters.py | 201 ++++-- pythonFiles/jedi/evaluate/finder.py | 42 +- pythonFiles/jedi/evaluate/flow_analysis.py | 17 +- pythonFiles/jedi/evaluate/helpers.py | 21 +- pythonFiles/jedi/evaluate/imports.py | 249 +++---- pythonFiles/jedi/evaluate/param.py | 5 +- pythonFiles/jedi/evaluate/pep0484.py | 192 ++++-- pythonFiles/jedi/evaluate/project.py | 40 -- pythonFiles/jedi/evaluate/recursion.py | 11 +- pythonFiles/jedi/evaluate/site.py | 110 --- pythonFiles/jedi/evaluate/stdlib.py | 80 ++- pythonFiles/jedi/evaluate/syntax_tree.py | 231 ++++--- pythonFiles/jedi/evaluate/sys_path.py | 134 +--- pythonFiles/jedi/evaluate/utils.py | 44 ++ pythonFiles/jedi/parser_utils.py | 29 +- pythonFiles/jedi/refactoring.py | 19 +- pythonFiles/jedi/utils.py | 8 +- pythonFiles/parso/__init__.py | 2 +- pythonFiles/parso/_compatibility.py | 2 +- pythonFiles/parso/grammar.py | 36 +- pythonFiles/parso/pgen2/pgen.py | 5 +- pythonFiles/parso/python/diff.py | 14 +- pythonFiles/parso/python/errors.py | 119 +--- pythonFiles/parso/python/fstring.py | 211 ------ pythonFiles/parso/python/grammar26.txt | 3 +- pythonFiles/parso/python/grammar27.txt | 3 +- pythonFiles/parso/python/grammar33.txt | 3 +- pythonFiles/parso/python/grammar34.txt | 3 +- pythonFiles/parso/python/grammar35.txt | 3 +- pythonFiles/parso/python/grammar36.txt | 9 +- pythonFiles/parso/python/grammar37.txt | 9 +- pythonFiles/parso/python/parser.py | 26 +- pythonFiles/parso/python/token.py | 9 + pythonFiles/parso/python/tokenize.py | 236 ++++++- pythonFiles/parso/python/tree.py | 27 + pythonFiles/parso/tree.py | 3 +- src/client/common/configSettings.ts | 11 +- src/client/common/types.ts | 8 +- src/client/providers/jediProxy.ts | 193 +++--- src/client/providers/signatureProvider.ts | 34 +- src/test/definitions/hover.ptvs.test.ts | 126 +++- src/test/signature/signature.jedi.test.ts | 16 +- src/test/signature/signature.ptvs.test.ts | 15 +- 85 files changed, 4525 insertions(+), 2613 deletions(-) create mode 100644 news/1 Enhancements/1400.md create mode 100644 news/2 Fixes/1033.md create mode 100644 pythonFiles/jedi/api/environment.py create mode 100644 pythonFiles/jedi/api/exceptions.py create mode 100644 pythonFiles/jedi/api/project.py create mode 100644 pythonFiles/jedi/common/utils.py create mode 100644 pythonFiles/jedi/evaluate/compiled/access.py create mode 100644 pythonFiles/jedi/evaluate/compiled/context.py create mode 100644 pythonFiles/jedi/evaluate/compiled/subprocess/__init__.py create mode 100644 pythonFiles/jedi/evaluate/compiled/subprocess/__main__.py create mode 100644 pythonFiles/jedi/evaluate/compiled/subprocess/functions.py create mode 100644 pythonFiles/jedi/evaluate/context/asynchronous.py delete mode 100644 pythonFiles/jedi/evaluate/project.py delete mode 100644 pythonFiles/jedi/evaluate/site.py delete mode 100644 pythonFiles/parso/python/fstring.py diff --git a/news/1 Enhancements/1400.md b/news/1 Enhancements/1400.md new file mode 100644 index 000000000000..47b3cf611c88 --- /dev/null +++ b/news/1 Enhancements/1400.md @@ -0,0 +1 @@ +Intergrate Jedi 0.12. See https://github.com/davidhalter/jedi/issues/1063#issuecomment-381417297 for details. \ No newline at end of file diff --git a/news/2 Fixes/1033.md b/news/2 Fixes/1033.md new file mode 100644 index 000000000000..31fec8720909 --- /dev/null +++ b/news/2 Fixes/1033.md @@ -0,0 +1 @@ +Fix go to definition functionality across files. \ No newline at end of file diff --git a/pythonFiles/completion.py b/pythonFiles/completion.py index e530be32b367..2a0d6e3d095b 100644 --- a/pythonFiles/completion.py +++ b/pythonFiles/completion.py @@ -570,7 +570,7 @@ def _process_request(self, request): if lookup == 'definitions': defs = [] try: - defs = self._get_definitionsx(script.goto_assignments(follow_imports=False), request['id']) + defs = self._get_definitionsx(script.goto_definitions(follow_imports=False), request['id']) except: pass try: diff --git a/pythonFiles/jedi/__init__.py b/pythonFiles/jedi/__init__.py index 1a1080ad2fd4..ff2de906d9cd 100644 --- a/pythonFiles/jedi/__init__.py +++ b/pythonFiles/jedi/__init__.py @@ -36,8 +36,12 @@ good text editor, while still having very good IDE features for Python. """ -__version__ = '0.11.1' +__version__ = '0.12.0' from jedi.api import Script, Interpreter, set_debug_function, \ preload_module, names from jedi import settings +from jedi.api.environment import find_virtualenvs, find_system_environments, \ + get_default_environment, InvalidPythonEnvironment, create_environment, \ + get_system_environment +from jedi.api.exceptions import InternalError diff --git a/pythonFiles/jedi/_compatibility.py b/pythonFiles/jedi/_compatibility.py index 52a20fe2c07c..8b55fb8f4a1d 100644 --- a/pythonFiles/jedi/_compatibility.py +++ b/pythonFiles/jedi/_compatibility.py @@ -1,25 +1,25 @@ """ -To ensure compatibility from Python ``2.6`` - ``3.3``, a module has been +To ensure compatibility from Python ``2.7`` - ``3.x``, a module has been created. Clearly there is huge need to use conforming syntax. """ +import binascii +import errno import sys -import imp import os import re import pkgutil import warnings +import inspect +import subprocess try: import importlib except ImportError: pass -# Cannot use sys.version.major and minor names, because in Python 2.6 it's not -# a namedtuple. is_py3 = sys.version_info[0] >= 3 is_py33 = is_py3 and sys.version_info[1] >= 3 is_py34 = is_py3 and sys.version_info[1] >= 4 is_py35 = is_py3 and sys.version_info[1] >= 5 -is_py26 = not is_py3 and sys.version_info[1] < 7 py_version = int(str(sys.version_info[0]) + str(sys.version_info[1])) @@ -35,28 +35,24 @@ def close(self): del self.loader -def find_module_py34(string, path=None, fullname=None): - implicit_namespace_pkg = False +def find_module_py34(string, path=None, full_name=None): spec = None loader = None spec = importlib.machinery.PathFinder.find_spec(string, path) - if hasattr(spec, 'origin'): - origin = spec.origin - implicit_namespace_pkg = origin == 'namespace' - - # We try to disambiguate implicit namespace pkgs with non implicit namespace pkgs - if implicit_namespace_pkg: - fullname = string if not path else fullname - implicit_ns_info = ImplicitNSInfo(fullname, spec.submodule_search_locations._path) - return None, implicit_ns_info, False - - # we have found the tail end of the dotted path - if hasattr(spec, 'loader'): + if spec is not None: + # We try to disambiguate implicit namespace pkgs with non implicit namespace pkgs + if not spec.has_location: + full_name = string if not path else full_name + implicit_ns_info = ImplicitNSInfo(full_name, spec.submodule_search_locations._path) + return None, implicit_ns_info, False + + # we have found the tail end of the dotted path loader = spec.loader return find_module_py33(string, path, loader) -def find_module_py33(string, path=None, loader=None, fullname=None): + +def find_module_py33(string, path=None, loader=None, full_name=None): loader = loader or importlib.machinery.PathFinder.find_module(string, path) if loader is None and path is None: # Fallback to find builtins @@ -74,7 +70,7 @@ def find_module_py33(string, path=None, loader=None, fullname=None): raise ImportError("Originally " + repr(e)) if loader is None: - raise ImportError("Couldn't find a loader for {0}".format(string)) + raise ImportError("Couldn't find a loader for {}".format(string)) try: is_package = loader.is_package(string) @@ -109,7 +105,10 @@ def find_module_py33(string, path=None, loader=None, fullname=None): return module_file, module_path, is_package -def find_module_pre_py33(string, path=None, fullname=None): +def find_module_pre_py33(string, path=None, full_name=None): + # This import is here, because in other places it will raise a + # DeprecationWarning. + import imp try: module_file, module_path, description = imp.find_module(string, path) module_type = description[2] @@ -127,14 +126,7 @@ def find_module_pre_py33(string, path=None, fullname=None): if loader: is_package = loader.is_package(string) is_archive = hasattr(loader, 'archive') - try: - module_path = loader.get_filename(string) - except AttributeError: - # fallback for py26 - try: - module_path = loader._get_filename(string) - except AttributeError: - continue + module_path = loader.get_filename(string) if is_package: module_path = os.path.dirname(module_path) if is_archive: @@ -142,14 +134,14 @@ def find_module_pre_py33(string, path=None, fullname=None): file = None if not is_package or is_archive: file = DummyFile(loader, string) - return (file, module_path, is_package) + return file, module_path, is_package except ImportError: pass - raise ImportError("No module named {0}".format(string)) + raise ImportError("No module named {}".format(string)) find_module = find_module_py33 if is_py33 else find_module_pre_py33 -find_module = find_module_py34 if is_py34 else find_module +find_module = find_module_py34 if is_py34 else find_module find_module.__doc__ = """ Provides information about a module. @@ -161,12 +153,80 @@ def find_module_pre_py33(string, path=None, fullname=None): """ +def _iter_modules(paths, prefix=''): + # Copy of pkgutil.iter_modules adapted to work with namespaces + + for path in paths: + importer = pkgutil.get_importer(path) + + if not isinstance(importer, importlib.machinery.FileFinder): + # We're only modifying the case for FileFinder. All the other cases + # still need to be checked (like zip-importing). Do this by just + # calling the pkgutil version. + for mod_info in pkgutil.iter_modules([path], prefix): + yield mod_info + continue + + # START COPY OF pkutils._iter_file_finder_modules. + if importer.path is None or not os.path.isdir(importer.path): + return + + yielded = {} + + try: + filenames = os.listdir(importer.path) + except OSError: + # ignore unreadable directories like import does + filenames = [] + filenames.sort() # handle packages before same-named modules + + for fn in filenames: + modname = inspect.getmodulename(fn) + if modname == '__init__' or modname in yielded: + continue + + # jedi addition: Avoid traversing special directories + if fn.startswith('.') or fn == '__pycache__': + continue + + path = os.path.join(importer.path, fn) + ispkg = False + + if not modname and os.path.isdir(path) and '.' not in fn: + modname = fn + # A few jedi modifications: Don't check if there's an + # __init__.py + try: + os.listdir(path) + except OSError: + # ignore unreadable directories like import does + continue + ispkg = True + + if modname and '.' not in modname: + yielded[modname] = 1 + yield importer, prefix + modname, ispkg + # END COPY + +iter_modules = _iter_modules if py_version >= 34 else pkgutil.iter_modules + + class ImplicitNSInfo(object): """Stores information returned from an implicit namespace spec""" def __init__(self, name, paths): self.name = name self.paths = paths + +if is_py3: + all_suffixes = importlib.machinery.all_suffixes +else: + def all_suffixes(): + # Is deprecated and raises a warning in Python 3.6. + import imp + return [suffix for suffix, _, _ in imp.get_suffixes()] + + # unicode function try: unicode = unicode @@ -208,7 +268,7 @@ def use_metaclass(meta, *bases): """ Create a class with a metaclass. """ if not bases: bases = (object,) - return meta("HackClass", bases, {}) + return meta("Py2CompatibilityMetaClass", bases, {}) try: @@ -219,19 +279,37 @@ def use_metaclass(meta, *bases): encoding = 'ascii' -def u(string): +def u(string, errors='strict'): """Cast to unicode DAMMIT! Written because Python2 repr always implicitly casts to a string, so we have to cast back to a unicode (and we now that we always deal with valid unicode, because we check that in the beginning). """ - if is_py3: - return str(string) - - if not isinstance(string, unicode): - return unicode(str(string), 'UTF-8') + if isinstance(string, bytes): + return unicode(string, encoding='UTF-8', errors=errors) return string + +def cast_path(obj): + """ + Take a bytes or str path and cast it to unicode. + + Apparently it is perfectly fine to pass both byte and unicode objects into + the sys.path. This probably means that byte paths are normal at other + places as well. + + Since this just really complicates everything and Python 2.7 will be EOL + soon anyway, just go with always strings. + """ + return u(obj, errors='replace') + + +def force_unicode(obj): + # Intentionally don't mix those two up, because those two code paths might + # be different in the future (maybe windows?). + return cast_path(obj) + + try: import builtins # module name in python 3 except ImportError: @@ -242,11 +320,6 @@ def u(string): def literal_eval(string): - # py3.0, py3.1 and py32 don't support unicode literals. Support those, I - # don't want to write two versions of the tokenizer. - if is_py3 and sys.version_info.minor < 3: - if re.match('[uU][\'"]', string): - string = string[1:] return ast.literal_eval(string) @@ -260,6 +333,11 @@ def literal_eval(string): except NameError: FileNotFoundError = IOError +try: + NotADirectoryError = NotADirectoryError +except NameError: + NotADirectoryError = IOError + def no_unicode_pprint(dct): """ @@ -273,6 +351,13 @@ def no_unicode_pprint(dct): print(re.sub("u'", "'", s)) +def print_to_stderr(*args): + if is_py3: + eval("print(*args, file=sys.stderr)") + else: + print >> sys.stderr, args + + def utf8_repr(func): """ ``__repr__`` methods in Python 2 don't allow unicode objects to be @@ -289,3 +374,142 @@ def wrapper(self): return func else: return wrapper + + +if is_py3: + import queue +else: + import Queue as queue + + +import pickle +if sys.version_info[:2] == (3, 3): + """ + Monkeypatch the unpickler in Python 3.3. This is needed, because the + argument `encoding='bytes'` is not supported in 3.3, but badly needed to + communicate with Python 2. + """ + + class NewUnpickler(pickle._Unpickler): + dispatch = dict(pickle._Unpickler.dispatch) + + def _decode_string(self, value): + # Used to allow strings from Python 2 to be decoded either as + # bytes or Unicode strings. This should be used only with the + # STRING, BINSTRING and SHORT_BINSTRING opcodes. + if self.encoding == "bytes": + return value + else: + return value.decode(self.encoding, self.errors) + + def load_string(self): + data = self.readline()[:-1] + # Strip outermost quotes + if len(data) >= 2 and data[0] == data[-1] and data[0] in b'"\'': + data = data[1:-1] + else: + raise pickle.UnpicklingError("the STRING opcode argument must be quoted") + self.append(self._decode_string(pickle.codecs.escape_decode(data)[0])) + dispatch[pickle.STRING[0]] = load_string + + def load_binstring(self): + # Deprecated BINSTRING uses signed 32-bit length + len, = pickle.struct.unpack('>> defs[0].type + >>> defs = [str(d.type) for d in defs] # It's unicode and in Py2 has u before it. + >>> defs[0] 'module' - >>> defs[1].type + >>> defs[1] 'class' - >>> defs[2].type + >>> defs[2] 'instance' - >>> defs[3].type + >>> defs[3] 'function' """ @@ -159,7 +157,7 @@ def to_reverse(): except IndexError: pass - if name.api_type == 'module': + if name.api_type in 'module': module_contexts = name.infer() if module_contexts: module_context, = module_contexts @@ -259,7 +257,7 @@ def docstring(self, raw=False, fast=True): @property def description(self): """A textual description of the object.""" - return u(self._name.string_name) + return self._name.string_name @property def full_name(self): @@ -324,9 +322,9 @@ def get_param_names(context): param_names = param_names[1:] elif isinstance(context, (instance.AbstractInstanceContext, ClassContext)): if isinstance(context, ClassContext): - search = '__init__' + search = u'__init__' else: - search = '__call__' + search = u'__call__' names = context.get_function_slot_names(search) if not names: return [] @@ -377,8 +375,7 @@ def get_line_code(self, before=0, after=0): if self.in_builtin_module(): return '' - path = self._name.get_root_context().py__file__() - lines = parser_cache[self._evaluator.grammar._hashed][path].lines + lines = self._name.get_root_context().code_lines index = self._name.start_pos[0] - 1 start_index = max(index - before, 0) @@ -406,7 +403,7 @@ def _complete(self, like_name): and self.type == 'Function': append = '(' - if isinstance(self._name, ParamName) and self._stack is not None: + if self._name.api_type == 'param' and self._stack is not None: node_names = list(self._stack.get_node_names(self._evaluator.grammar._pgen_grammar)) if 'trailer' in node_names and 'argument' not in node_names: append += '=' @@ -525,7 +522,7 @@ def description(self): if typ == 'function': # For the description we want a short and a pythonic way. typ = 'def' - return typ + ' ' + u(self._name.string_name) + return typ + ' ' + self._name.string_name elif typ == 'param': code = search_ancestor(tree_name, 'param').get_code( include_prefix=False, @@ -533,7 +530,6 @@ def description(self): ) return typ + ' ' + code - definition = tree_name.get_definition() or tree_name # Remove the prefix, because that's not what we want for get_code # here. @@ -555,7 +551,7 @@ def desc_with_module(self): .. todo:: Add full path. This function is should return a `module.class.function` path. """ - position = '' if self.in_builtin_module else '@%s' % (self.line) + position = '' if self.in_builtin_module else '@%s' % self.line return "%s:%s%s" % (self.module_name, self.description, position) @memoize_method diff --git a/pythonFiles/jedi/api/completion.py b/pythonFiles/jedi/api/completion.py index 559a4d3f8320..c88a031e4679 100644 --- a/pythonFiles/jedi/api/completion.py +++ b/pythonFiles/jedi/api/completion.py @@ -2,6 +2,7 @@ from parso.python import tree from parso.tree import search_ancestor, Leaf +from jedi._compatibility import Parameter from jedi import debug from jedi import settings from jedi.api import classes @@ -18,24 +19,21 @@ def get_call_signature_param_names(call_signatures): for call_sig in call_signatures: for p in call_sig.params: # Allow protected access, because it's a public API. - tree_name = p._name.tree_name - # Compiled modules typically don't allow keyword arguments. - if tree_name is not None: - # Allow access on _definition here, because it's a - # public API and we don't want to make the internal - # Name object public. - tree_param = tree.search_ancestor(tree_name, 'param') - if tree_param.star_count == 0: # no *args/**kwargs - yield p._name + if p._name.get_kind() in (Parameter.POSITIONAL_OR_KEYWORD, + Parameter.KEYWORD_ONLY): + yield p._name def filter_names(evaluator, completion_names, stack, like_name): comp_dct = {} + if settings.case_insensitive_completion: + like_name = like_name.lower() for name in completion_names: - if settings.case_insensitive_completion \ - and name.string_name.lower().startswith(like_name.lower()) \ - or name.string_name.startswith(like_name): + string = name.string_name + if settings.case_insensitive_completion: + string = string.lower() + if string.startswith(like_name): new = classes.Completion( evaluator, name, @@ -208,7 +206,7 @@ def _get_context_completions(self): def _get_keyword_completion_names(self, keywords_): for k in keywords_: - yield keywords.keyword(self._evaluator, k).name + yield keywords.KeywordName(self._evaluator, k) def _global_completions(self): context = get_user_scope(self._module_context, self._position) diff --git a/pythonFiles/jedi/api/environment.py b/pythonFiles/jedi/api/environment.py new file mode 100644 index 000000000000..51b390f36ab4 --- /dev/null +++ b/pythonFiles/jedi/api/environment.py @@ -0,0 +1,393 @@ +""" +Environments are a way to activate different Python versions or Virtualenvs for +static analysis. The Python binary in that environment is going to be executed. +""" +import os +import re +import sys +import hashlib +import filecmp +from subprocess import PIPE +from collections import namedtuple +# When dropping Python 2.7 support we should consider switching to +# `shutil.which`. +from distutils.spawn import find_executable + +from jedi._compatibility import GeneralizedPopen +from jedi.cache import memoize_method, time_cache +from jedi.evaluate.compiled.subprocess import get_subprocess, \ + EvaluatorSameProcess, EvaluatorSubprocess + +import parso + +_VersionInfo = namedtuple('VersionInfo', 'major minor micro') + +_SUPPORTED_PYTHONS = ['3.6', '3.5', '3.4', '3.3', '2.7'] +_SAFE_PATHS = ['/usr/bin', '/usr/local/bin'] +_CURRENT_VERSION = '%s.%s' % (sys.version_info.major, sys.version_info.minor) + + +class InvalidPythonEnvironment(Exception): + """ + If you see this exception, the Python executable or Virtualenv you have + been trying to use is probably not a correct Python version. + """ + + +class _BaseEnvironment(object): + @memoize_method + def get_grammar(self): + version_string = '%s.%s' % (self.version_info.major, self.version_info.minor) + return parso.load_grammar(version=version_string) + + @property + def _sha256(self): + try: + return self._hash + except AttributeError: + self._hash = _calculate_sha256_for_file(self.executable) + return self._hash + + +class Environment(_BaseEnvironment): + """ + This class is supposed to be created by internal Jedi architecture. You + should not create it directly. Please use create_environment or the other + functions instead. It is then returned by that function. + """ + def __init__(self, path, executable): + self.path = os.path.abspath(path) + """ + The path to an environment, matches ``sys.prefix``. + """ + self.executable = os.path.abspath(executable) + """ + The Python executable, matches ``sys.executable``. + """ + self.version_info = self._get_version() + """ + + Like ``sys.version_info``. A tuple to show the current Environment's + Python version. + """ + + def _get_version(self): + try: + process = GeneralizedPopen([self.executable, '--version'], stdout=PIPE, stderr=PIPE) + stdout, stderr = process.communicate() + retcode = process.poll() + if retcode: + raise InvalidPythonEnvironment() + except OSError: + raise InvalidPythonEnvironment() + + # Until Python 3.4 wthe version string is part of stderr, after that + # stdout. + output = stdout + stderr + match = re.match(br'Python (\d+)\.(\d+)\.(\d+)', output) + if match is None: + raise InvalidPythonEnvironment("--version not working") + + return _VersionInfo(*[int(m) for m in match.groups()]) + + def __repr__(self): + version = '.'.join(str(i) for i in self.version_info) + return '<%s: %s in %s>' % (self.__class__.__name__, version, self.path) + + def get_evaluator_subprocess(self, evaluator): + return EvaluatorSubprocess(evaluator, self._get_subprocess()) + + def _get_subprocess(self): + return get_subprocess(self.executable) + + @memoize_method + def get_sys_path(self): + """ + The sys path for this environment. Does not include potential + modifications like ``sys.path.append``. + + :returns: list of str + """ + # It's pretty much impossible to generate the sys path without actually + # executing Python. The sys path (when starting with -S) itself depends + # on how the Python version was compiled (ENV variables). + # If you omit -S when starting Python (normal case), additionally + # site.py gets executed. + return self._get_subprocess().get_sys_path() + + +class SameEnvironment(Environment): + def __init__(self): + super(SameEnvironment, self).__init__(sys.prefix, sys.executable) + + def _get_version(self): + return _VersionInfo(*sys.version_info[:3]) + + +class InterpreterEnvironment(_BaseEnvironment): + def __init__(self): + self.version_info = _VersionInfo(*sys.version_info[:3]) + + def get_evaluator_subprocess(self, evaluator): + return EvaluatorSameProcess(evaluator) + + def get_sys_path(self): + return sys.path + + +def _get_virtual_env_from_var(): + var = os.environ.get('VIRTUAL_ENV') + if var is not None: + if var == sys.prefix: + return SameEnvironment() + + try: + return create_environment(var) + except InvalidPythonEnvironment: + pass + + +def _calculate_sha256_for_file(path): + sha256 = hashlib.sha256() + with open(path, 'rb') as f: + for block in iter(lambda: f.read(filecmp.BUFSIZE), b''): + sha256.update(block) + return sha256.hexdigest() + + +def get_default_environment(): + """ + Tries to return an active Virtualenv. If there is no VIRTUAL_ENV variable + set it will return the latest Python version installed on the system. This + makes it possible to use as many new Python features as possible when using + autocompletion and other functionality. + + :returns: :class:`Environment` + """ + virtual_env = _get_virtual_env_from_var() + if virtual_env is not None: + return virtual_env + + for environment in find_system_environments(): + return environment + + # If no Python Environment is found, use the environment we're already + # using. + return SameEnvironment() + + +@time_cache(seconds=10 * 60) # 10 Minutes +def get_cached_default_environment(): + return get_default_environment() + + +def find_virtualenvs(paths=None, **kwargs): + """ + :param paths: A list of paths in your file system to be scanned for + Virtualenvs. It will search in these paths and potentially execute the + Python binaries. Also the VIRTUAL_ENV variable will be checked if it + contains a valid Virtualenv. + :param safe: Default True. In case this is False, it will allow this + function to execute potential `python` environments. An attacker might + be able to drop an executable in a path this function is searching by + default. If the executable has not been installed by root, it will not + be executed. + + :yields: :class:`Environment` + """ + def py27_comp(paths=None, safe=True): + if paths is None: + paths = [] + + _used_paths = set() + + # Using this variable should be safe, because attackers might be able + # to drop files (via git) but not environment variables. + virtual_env = _get_virtual_env_from_var() + if virtual_env is not None: + yield virtual_env + _used_paths.add(virtual_env.path) + + for directory in paths: + if not os.path.isdir(directory): + continue + + directory = os.path.abspath(directory) + for path in os.listdir(directory): + path = os.path.join(directory, path) + if path in _used_paths: + # A path shouldn't be evaluated twice. + continue + _used_paths.add(path) + + try: + executable = _get_executable_path(path, safe=safe) + yield Environment(path, executable) + except InvalidPythonEnvironment: + pass + + return py27_comp(paths, **kwargs) + + +def find_system_environments(): + """ + Ignores virtualenvs and returns the Python versions that were installed on + your system. This might return nothing, if you're running Python e.g. from + a portable version. + + The environments are sorted from latest to oldest Python version. + + :yields: :class:`Environment` + """ + for version_string in _SUPPORTED_PYTHONS: + try: + yield get_system_environment(version_string) + except InvalidPythonEnvironment: + pass + + +# TODO: the logic to find the Python prefix is much more complicated than that. +# See Modules/getpath.c for UNIX and PC/getpathp.c for Windows in CPython's +# source code. A solution would be to deduce it by running the Python +# interpreter and printing the value of sys.prefix. +def _get_python_prefix(executable): + if os.name != 'nt': + return os.path.dirname(os.path.dirname(executable)) + landmark = os.path.join('Lib', 'os.py') + prefix = os.path.dirname(executable) + while prefix: + if os.path.join(prefix, landmark): + return prefix + prefix = os.path.dirname(prefix) + raise InvalidPythonEnvironment( + "Cannot find prefix of executable %s." % executable) + + +# TODO: this function should probably return a list of environments since +# multiple Python installations can be found on a system for the same version. +def get_system_environment(version): + """ + Return the first Python environment found for a string of the form 'X.Y' + where X and Y are the major and minor versions of Python. + + :raises: :exc:`.InvalidPythonEnvironment` + :returns: :class:`Environment` + """ + exe = find_executable('python' + version) + if exe: + if exe == sys.executable: + return SameEnvironment() + return Environment(_get_python_prefix(exe), exe) + + if os.name == 'nt': + for prefix, exe in _get_executables_from_windows_registry(version): + return Environment(prefix, exe) + raise InvalidPythonEnvironment("Cannot find executable python%s." % version) + + +def create_environment(path, safe=True): + """ + Make it possible to create an environment by hand. + + :raises: :exc:`.InvalidPythonEnvironment` + :returns: :class:`Environment` + """ + return Environment(path, _get_executable_path(path, safe=safe)) + + +def _get_executable_path(path, safe=True): + """ + Returns None if it's not actually a virtual env. + """ + + if os.name == 'nt': + python = os.path.join(path, 'Scripts', 'python.exe') + else: + python = os.path.join(path, 'bin', 'python') + if not os.path.exists(python): + raise InvalidPythonEnvironment("%s seems to be missing." % python) + + if safe and not _is_safe(python): + raise InvalidPythonEnvironment("The python binary is potentially unsafe.") + return python + + +def _get_executables_from_windows_registry(version): + # The winreg module is named _winreg on Python 2. + try: + import winreg + except ImportError: + import _winreg as winreg + + # TODO: support Python Anaconda. + sub_keys = [ + r'SOFTWARE\Python\PythonCore\{version}\InstallPath', + r'SOFTWARE\Wow6432Node\Python\PythonCore\{version}\InstallPath', + r'SOFTWARE\Python\PythonCore\{version}-32\InstallPath', + r'SOFTWARE\Wow6432Node\Python\PythonCore\{version}-32\InstallPath' + ] + for root_key in [winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE]: + for sub_key in sub_keys: + sub_key = sub_key.format(version=version) + try: + with winreg.OpenKey(root_key, sub_key) as key: + prefix = winreg.QueryValueEx(key, '')[0] + exe = os.path.join(prefix, 'python.exe') + if os.path.isfile(exe): + yield prefix, exe + except WindowsError: + pass + + +def _is_safe(executable_path): + # Resolve sym links. A venv typically is a symlink to a known Python + # binary. Only virtualenvs copy symlinks around. + real_path = os.path.realpath(executable_path) + + if _is_unix_safe_simple(real_path): + return True + + # Just check the list of known Python versions. If it's not in there, + # it's likely an attacker or some Python that was not properly + # installed in the system. + for environment in find_system_environments(): + if environment.executable == real_path: + return True + + # If the versions don't match, just compare the binary files. If we + # don't do that, only venvs will be working and not virtualenvs. + # venvs are symlinks while virtualenvs are actual copies of the + # Python files. + # This still means that if the system Python is updated and the + # virtualenv's Python is not (which is probably never going to get + # upgraded), it will not work with Jedi. IMO that's fine, because + # people should just be using venv. ~ dave + if environment._sha256 == _calculate_sha256_for_file(real_path): + return True + return False + + +def _is_unix_safe_simple(real_path): + if _is_unix_admin(): + # In case we are root, just be conservative and + # only execute known paths. + return any(real_path.startswith(p) for p in _SAFE_PATHS) + + uid = os.stat(real_path).st_uid + # The interpreter needs to be owned by root. This means that it wasn't + # written by a user and therefore attacking Jedi is not as simple. + # The attack could look like the following: + # 1. A user clones a repository. + # 2. The repository has an innocent looking folder called foobar. jedi + # searches for the folder and executes foobar/bin/python --version if + # there's also a foobar/bin/activate. + # 3. The bin/python is obviously not a python script but a bash script or + # whatever the attacker wants. + return uid == 0 + + +def _is_unix_admin(): + try: + return os.getuid() == 0 + except AttributeError: + return False # Windows diff --git a/pythonFiles/jedi/api/exceptions.py b/pythonFiles/jedi/api/exceptions.py new file mode 100644 index 000000000000..99cebdb7ddb5 --- /dev/null +++ b/pythonFiles/jedi/api/exceptions.py @@ -0,0 +1,10 @@ +class _JediError(Exception): + pass + + +class InternalError(_JediError): + pass + + +class WrongVersion(_JediError): + pass diff --git a/pythonFiles/jedi/api/helpers.py b/pythonFiles/jedi/api/helpers.py index 2c4d8e0d10fc..221fc4dfe0d4 100644 --- a/pythonFiles/jedi/api/helpers.py +++ b/pythonFiles/jedi/api/helpers.py @@ -7,12 +7,13 @@ from parso.python.parser import Parser from parso.python import tree -from parso import split_lines from jedi._compatibility import u from jedi.evaluate.syntax_tree import eval_atom from jedi.evaluate.helpers import evaluate_call_of_leaf -from jedi.cache import time_cache +from jedi.evaluate.compiled import get_string_context_set +from jedi.evaluate.base_context import ContextSet +from jedi.cache import call_signature_time_cache CompletionParts = namedtuple('CompletionParts', ['path', 'has_dot', 'name']) @@ -44,7 +45,7 @@ def _get_code(code_lines, start_pos, end_pos): lines[-1] = lines[-1][:end_pos[1]] # Remove first line indentation. lines[0] = lines[0][start_pos[1]:] - return '\n'.join(lines) + return ''.join(lines) class OnErrorLeaf(Exception): @@ -53,28 +54,11 @@ def error_leaf(self): return self.args[0] -def _is_on_comment(leaf, position): - comment_lines = split_lines(leaf.prefix) - difference = leaf.start_pos[0] - position[0] - prefix_start_pos = leaf.get_start_pos_of_prefix() - if difference == 0: - indent = leaf.start_pos[1] - elif position[0] == prefix_start_pos[0]: - indent = prefix_start_pos[1] - else: - indent = 0 - line = comment_lines[-difference - 1][:position[1] - indent] - return '#' in line - - def _get_code_for_stack(code_lines, module_node, position): leaf = module_node.get_leaf_for_position(position, include_prefixes=True) # It might happen that we're on whitespace or on a comment. This means # that we would not get the right leaf. if leaf.start_pos >= position: - if _is_on_comment(leaf, position): - return u('') - # If we're not on a comment simply get the previous leaf and proceed. leaf = leaf.get_previous_leaf() if leaf is None: @@ -125,6 +109,9 @@ def tokenize_without_endmarker(code): for token_ in tokens: if token_.string == safeword: raise EndMarkerReached() + elif token_.prefix.endswith(safeword): + # This happens with comments. + raise EndMarkerReached() else: yield token_ @@ -134,7 +121,7 @@ def tokenize_without_endmarker(code): # completion. # Use Z as a prefix because it's not part of a number suffix. safeword = 'ZZZ_USER_WANTS_TO_COMPLETE_HERE_WITH_JEDI' - code = code + safeword + code = code + ' ' + safeword p = Parser(grammar._pgen_grammar, error_recovery=True) try: @@ -208,6 +195,8 @@ def evaluate_goto_definition(evaluator, context, leaf): return evaluate_call_of_leaf(context, leaf) elif isinstance(leaf, tree.Literal): return eval_atom(context, leaf) + elif leaf.type in ('fstring_string', 'fstring_start', 'fstring_end'): + return get_string_context_set(evaluator) return [] @@ -294,14 +283,14 @@ def get_call_signature_details(module, position): return None -@time_cache("call_signatures_validity") +@call_signature_time_cache("call_signatures_validity") def cache_call_signatures(evaluator, context, bracket_leaf, code_lines, user_pos): """This function calculates the cache key.""" - index = user_pos[0] - 1 + line_index = user_pos[0] - 1 - before_cursor = code_lines[index][:user_pos[1]] - other_lines = code_lines[bracket_leaf.start_pos[0]:index] - whole = '\n'.join(other_lines + [before_cursor]) + before_cursor = code_lines[line_index][:user_pos[1]] + other_lines = code_lines[bracket_leaf.start_pos[0]:line_index] + whole = ''.join(other_lines + [before_cursor]) before_bracket = re.match(r'.*\(', whole, re.DOTALL) module_path = context.get_root_context().py__file__() diff --git a/pythonFiles/jedi/api/interpreter.py b/pythonFiles/jedi/api/interpreter.py index 202f345e94b9..c9b7bd69bbe0 100644 --- a/pythonFiles/jedi/api/interpreter.py +++ b/pythonFiles/jedi/api/interpreter.py @@ -5,24 +5,34 @@ from jedi.evaluate.context import ModuleContext from jedi.evaluate import compiled from jedi.evaluate.compiled import mixed +from jedi.evaluate.compiled.access import create_access_path from jedi.evaluate.base_context import Context +def _create(evaluator, obj): + return compiled.create_from_access_path( + evaluator, create_access_path(evaluator, obj) + ) + + class NamespaceObject(object): def __init__(self, dct): self.__dict__ = dct class MixedModuleContext(Context): - resets_positions = True type = 'mixed_module' - def __init__(self, evaluator, tree_module, namespaces, path): + def __init__(self, evaluator, tree_module, namespaces, path, code_lines): self.evaluator = evaluator self._namespaces = namespaces self._namespace_objects = [NamespaceObject(n) for n in namespaces] - self._module_context = ModuleContext(evaluator, tree_module, path=path) + self._module_context = ModuleContext( + evaluator, tree_module, + path=path, + code_lines=code_lines + ) self.tree_node = tree_module def get_node(self): @@ -33,7 +43,7 @@ def get_filters(self, *args, **kwargs): yield filter for namespace_obj in self._namespace_objects: - compiled_object = compiled.create(self.evaluator, namespace_obj) + compiled_object = _create(self.evaluator, namespace_obj) mixed_object = mixed.MixedObject( self.evaluator, parent_context=self, @@ -43,5 +53,9 @@ def get_filters(self, *args, **kwargs): for filter in mixed_object.get_filters(*args, **kwargs): yield filter + @property + def code_lines(self): + return self._module_context.code_lines + def __getattr__(self, name): return getattr(self._module_context, name) diff --git a/pythonFiles/jedi/api/keywords.py b/pythonFiles/jedi/api/keywords.py index a1bc4e7f8556..2991a0f81a56 100644 --- a/pythonFiles/jedi/api/keywords.py +++ b/pythonFiles/jedi/api/keywords.py @@ -1,10 +1,7 @@ import pydoc -import keyword -from jedi._compatibility import is_py3, is_py35 from jedi.evaluate.utils import ignored from jedi.evaluate.filters import AbstractNameDefinition -from parso.python.tree import Leaf try: from pydoc_data import topics as pydoc_topics @@ -17,87 +14,30 @@ # pydoc_data module in its file python3x.zip. pydoc_topics = None -if is_py3: - if is_py35: - # in python 3.5 async and await are not proper keywords, but for - # completion pursposes should as as though they are - keys = keyword.kwlist + ["async", "await"] - else: - keys = keyword.kwlist -else: - keys = keyword.kwlist + ['None', 'False', 'True'] - - -def has_inappropriate_leaf_keyword(pos, module): - relevant_errors = filter( - lambda error: error.first_pos[0] == pos[0], - module.error_statement_stacks) - - for error in relevant_errors: - if error.next_token in keys: - return True - - return False - - -def completion_names(evaluator, stmt, pos, module): - keyword_list = all_keywords(evaluator) - - if not isinstance(stmt, Leaf) or has_inappropriate_leaf_keyword(pos, module): - keyword_list = filter( - lambda keyword: not keyword.only_valid_as_leaf, - keyword_list - ) - return [keyword.name for keyword in keyword_list] - - -def all_keywords(evaluator, pos=(0, 0)): - return set([Keyword(evaluator, k, pos) for k in keys]) - - -def keyword(evaluator, string, pos=(0, 0)): - if string in keys: - return Keyword(evaluator, string, pos) - else: - return None - def get_operator(evaluator, string, pos): return Keyword(evaluator, string, pos) -keywords_only_valid_as_leaf = ( - 'continue', - 'break', -) - - class KeywordName(AbstractNameDefinition): - api_type = 'keyword' + api_type = u'keyword' def __init__(self, evaluator, name): self.evaluator = evaluator self.string_name = name - self.parent_context = evaluator.BUILTINS - - def eval(self): - return set() + self.parent_context = evaluator.builtins_module def infer(self): return [Keyword(self.evaluator, self.string_name, (0, 0))] class Keyword(object): - api_type = 'keyword' + api_type = u'keyword' def __init__(self, evaluator, name, pos): self.name = KeywordName(evaluator, name) self.start_pos = pos - self.parent = evaluator.BUILTINS - - @property - def only_valid_as_leaf(self): - return self.name.value in keywords_only_valid_as_leaf + self.parent = evaluator.builtins_module @property def names(self): diff --git a/pythonFiles/jedi/api/project.py b/pythonFiles/jedi/api/project.py new file mode 100644 index 000000000000..ca6992b5db7f --- /dev/null +++ b/pythonFiles/jedi/api/project.py @@ -0,0 +1,200 @@ +import os +import json + +from jedi._compatibility import FileNotFoundError, NotADirectoryError +from jedi.api.environment import SameEnvironment, \ + get_cached_default_environment +from jedi.api.exceptions import WrongVersion +from jedi._compatibility import force_unicode +from jedi.evaluate.sys_path import discover_buildout_paths +from jedi.evaluate.cache import evaluator_as_method_param_cache +from jedi.common.utils import traverse_parents + +_CONFIG_FOLDER = '.jedi' +_CONTAINS_POTENTIAL_PROJECT = 'setup.py', '.git', '.hg', 'requirements.txt', 'MANIFEST.in' + +_SERIALIZER_VERSION = 1 + + +def _remove_duplicates_from_path(path): + used = set() + for p in path: + if p in used: + continue + used.add(p) + yield p + + +def _force_unicode_list(lst): + return list(map(force_unicode, lst)) + + +class Project(object): + # TODO serialize environment + _serializer_ignore_attributes = ('_environment',) + _environment = None + + @staticmethod + def _get_json_path(base_path): + return os.path.join(base_path, _CONFIG_FOLDER, 'project.json') + + @classmethod + def load(cls, path): + """ + :param path: The path of the directory you want to use as a project. + """ + with open(cls._get_json_path(path)) as f: + version, data = json.load(f) + + if version == 1: + self = cls.__new__() + self.__dict__.update(data) + return self + else: + raise WrongVersion( + "The Jedi version of this project seems newer than what we can handle." + ) + + def __init__(self, path, **kwargs): + """ + :param path: The base path for this project. + :param sys_path: list of str. You can override the sys path if you + want. By default the ``sys.path.`` is generated from the + environment (virtualenvs, etc). + :param smart_sys_path: If this is enabled (default), adds paths from + local directories. Otherwise you will have to rely on your packages + being properly configured on the ``sys.path``. + """ + def py2_comp(path, environment=None, sys_path=None, + smart_sys_path=True, _django=False): + self._path = path + if isinstance(environment, SameEnvironment): + self._environment = environment + + self._sys_path = sys_path + self._smart_sys_path = smart_sys_path + self._django = _django + + py2_comp(path, **kwargs) + + def _get_base_sys_path(self, environment=None): + if self._sys_path is not None: + return self._sys_path + + # The sys path has not been set explicitly. + if environment is None: + environment = self.get_environment() + + sys_path = environment.get_sys_path() + try: + sys_path.remove('') + except ValueError: + pass + return sys_path + + @evaluator_as_method_param_cache() + def _get_sys_path(self, evaluator, environment=None): + """ + Keep this method private for all users of jedi. However internally this + one is used like a public method. + """ + suffixed = [] + prefixed = [] + + sys_path = list(self._get_base_sys_path(environment)) + if self._smart_sys_path: + prefixed.append(self._path) + + if evaluator.script_path is not None: + suffixed += discover_buildout_paths(evaluator, evaluator.script_path) + + traversed = [] + for parent in traverse_parents(evaluator.script_path): + traversed.append(parent) + if parent == self._path: + # Don't go futher than the project path. + break + + # AFAIK some libraries have imports like `foo.foo.bar`, which + # leads to the conclusion to by default prefer longer paths + # rather than shorter ones by default. + suffixed += reversed(traversed) + + if self._django: + prefixed.append(self._path) + + path = prefixed + sys_path + suffixed + return list(_force_unicode_list(_remove_duplicates_from_path(path))) + + def save(self): + data = dict(self.__dict__) + for attribute in self._serializer_ignore_attributes: + data.pop(attribute, None) + + with open(self._get_json_path(self._path), 'wb') as f: + return json.dump((_SERIALIZER_VERSION, data), f) + + def get_environment(self): + if self._environment is None: + return get_cached_default_environment() + + return self._environment + + def __repr__(self): + return '<%s: %s>' % (self.__class__.__name__, self._path) + + +def _is_potential_project(path): + for name in _CONTAINS_POTENTIAL_PROJECT: + if os.path.exists(os.path.join(path, name)): + return True + return False + + +def _is_django_path(directory): + """ Detects the path of the very well known Django library (if used) """ + try: + with open(os.path.join(directory, 'manage.py'), 'rb') as f: + return b"DJANGO_SETTINGS_MODULE" in f.read() + except (FileNotFoundError, NotADirectoryError): + return False + + return False + + +def get_default_project(path=None): + if path is None: + path = os.getcwd() + + check = os.path.realpath(path) + probable_path = None + first_no_init_file = None + for dir in traverse_parents(check, include_current=True): + try: + return Project.load(dir) + except (FileNotFoundError, NotADirectoryError): + pass + + if first_no_init_file is None: + if os.path.exists(os.path.join(dir, '__init__.py')): + # In the case that a __init__.py exists, it's in 99% just a + # Python package and the project sits at least one level above. + continue + else: + first_no_init_file = dir + + if _is_django_path(dir): + return Project(dir, _django=True) + + if probable_path is None and _is_potential_project(dir): + probable_path = dir + + if probable_path is not None: + # TODO search for setup.py etc + return Project(probable_path) + + if first_no_init_file is not None: + return Project(first_no_init_file) + + curdir = path if os.path.isdir(path) else os.path.dirname(path) + return Project(curdir) diff --git a/pythonFiles/jedi/api/replstartup.py b/pythonFiles/jedi/api/replstartup.py index 5bfcc8ce889e..4c44a626b775 100644 --- a/pythonFiles/jedi/api/replstartup.py +++ b/pythonFiles/jedi/api/replstartup.py @@ -11,8 +11,8 @@ [GCC 4.6.1] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import os - >>> os.path.join().split().in # doctest: +SKIP - os.path.join().split().index os.path.join().split().insert + >>> os.path.join('a', 'b').split().in # doctest: +SKIP + ..dex ..sert """ import jedi.utils diff --git a/pythonFiles/jedi/cache.py b/pythonFiles/jedi/cache.py index 01138e75a99b..6c0c2a830942 100644 --- a/pythonFiles/jedi/cache.py +++ b/pythonFiles/jedi/cache.py @@ -12,6 +12,7 @@ these variables are being cleaned after every API usage. """ import time +from functools import wraps from jedi import settings from parso.cache import parser_cache @@ -74,7 +75,7 @@ def clear_time_caches(delete_all=False): del tc[key] -def time_cache(time_add_setting): +def call_signature_time_cache(time_add_setting): """ This decorator works as follows: Call it with a setting and after that use the function with a callable that returns the key. @@ -106,8 +107,31 @@ def wrapper(*args, **kwargs): return _temp +def time_cache(seconds): + def decorator(func): + cache = {} + + @wraps(func) + def wrapper(*args, **kwargs): + key = (args, frozenset(kwargs.items())) + try: + created, result = cache[key] + if time.time() < created + seconds: + return result + except KeyError: + pass + result = func(*args, **kwargs) + cache[key] = time.time(), result + return result + + wrapper.clear_cache = lambda: cache.clear() + return wrapper + return decorator + + def memoize_method(method): """A normal memoize function.""" + @wraps(method) def wrapper(self, *args, **kwargs): cache_dict = self.__dict__.setdefault('_memoize_method_dct', {}) dct = cache_dict.setdefault(method, {}) diff --git a/pythonFiles/jedi/common/utils.py b/pythonFiles/jedi/common/utils.py new file mode 100644 index 000000000000..72726a4696e6 --- /dev/null +++ b/pythonFiles/jedi/common/utils.py @@ -0,0 +1,12 @@ +import os + + +def traverse_parents(path, include_current=False): + if not include_current: + path = os.path.dirname(path) + + previous = None + while previous != path: + yield path + previous = path + path = os.path.dirname(path) diff --git a/pythonFiles/jedi/debug.py b/pythonFiles/jedi/debug.py index 8caf1accb17f..a4fd86465bac 100644 --- a/pythonFiles/jedi/debug.py +++ b/pythonFiles/jedi/debug.py @@ -35,7 +35,7 @@ def _lazy_colorama_init(): # need this. initialise.atexit_done = True try: - init() + init(strip=False) except Exception: # Colorama fails with initializing under vim and is buggy in # version 0.3.6. diff --git a/pythonFiles/jedi/evaluate/__init__.py b/pythonFiles/jedi/evaluate/__init__.py index 20461071abdb..3ba52b89f408 100644 --- a/pythonFiles/jedi/evaluate/__init__.py +++ b/pythonFiles/jedi/evaluate/__init__.py @@ -17,7 +17,8 @@ ``eval_expr_stmt``. There's separate logic for autocompletion in the API, the evaluator is all about evaluating an expression. -TODO this paragraph is not what jedi does anymore. +TODO this paragraph is not what jedi does anymore, it's similar, but not the +same. Now you need to understand what follows after ``eval_expr_stmt``. Let's make an example:: @@ -62,10 +63,9 @@ that are not used are just being ignored. """ -import sys - from parso.python import tree import parso +from parso import python_bytes_to_unicode from jedi import debug from jedi import parser_utils @@ -86,31 +86,42 @@ class Evaluator(object): - def __init__(self, grammar, project): - self.grammar = grammar + def __init__(self, project, environment=None, script_path=None): + if environment is None: + environment = project.get_environment() + self.environment = environment + self.script_path = script_path + self.compiled_subprocess = environment.get_evaluator_subprocess(self) + self.grammar = environment.get_grammar() + self.latest_grammar = parso.load_grammar(version='3.6') self.memoize_cache = {} # for memoize decorators - # To memorize modules -> equals `sys.modules`. - self.modules = {} # like `sys.modules`. + self.module_cache = imports.ModuleCache() # does the job of `sys.modules`. self.compiled_cache = {} # see `evaluate.compiled.create()` self.inferred_element_counts = {} self.mixed_cache = {} # see `evaluate.compiled.mixed._create()` self.analysis = [] self.dynamic_params_depth = 0 self.is_analysis = False - self.python_version = sys.version_info[:2] self.project = project - project.add_evaluator(self) + self.access_cache = {} self.reset_recursion_limitations() + self.allow_different_encoding = True - # Constants - self.BUILTINS = compiled.get_special_object(self, 'BUILTINS') + @property + @evaluator_function_cache() + def builtins_module(self): + return compiled.get_special_object(self, u'BUILTINS') def reset_recursion_limitations(self): self.recursion_detector = recursion.RecursionDetector() self.execution_recursion_detector = recursion.ExecutionRecursionDetector(self) + def get_sys_path(self): + """Convenience function""" + return self.project._get_sys_path(self, environment=self.environment) + def eval_element(self, context, element): if isinstance(context, CompForContext): return eval_node(context, element) @@ -124,7 +135,11 @@ def eval_element(self, context, element): if_stmt = None break predefined_if_name_dict = context.predefined_names.get(if_stmt) - if predefined_if_name_dict is None and if_stmt and if_stmt.type == 'if_stmt': + # TODO there's a lot of issues with this one. We actually should do + # this in a different way. Caching should only be active in certain + # cases and this all sucks. + if predefined_if_name_dict is None and if_stmt \ + and if_stmt.type == 'if_stmt' and self.is_analysis: if_stmt_test = if_stmt.children[1] name_dicts = [{}] # If we already did a check, we don't want to do it again -> If @@ -357,3 +372,15 @@ def from_scope_node(scope_node, child_is_funcdef=None, is_nested=True, node_is_o node = node.parent scope_node = parent_scope(node) return from_scope_node(scope_node, is_nested=True, node_is_object=node_is_object) + + def parse_and_get_code(self, code=None, path=None, **kwargs): + if self.allow_different_encoding: + if code is None: + with open(path, 'rb') as f: + code = f.read() + code = python_bytes_to_unicode(code, errors='replace') + + return self.grammar.parse(code=code, path=path, **kwargs), code + + def parse(self, *args, **kwargs): + return self.parse_and_get_code(*args, **kwargs)[0] diff --git a/pythonFiles/jedi/evaluate/analysis.py b/pythonFiles/jedi/evaluate/analysis.py index c825e5fef9e9..ded4e9f20880 100644 --- a/pythonFiles/jedi/evaluate/analysis.py +++ b/pythonFiles/jedi/evaluate/analysis.py @@ -1,9 +1,12 @@ """ Module for statical analysis. """ -from jedi import debug from parso.python import tree + +from jedi._compatibility import force_unicode +from jedi import debug from jedi.evaluate.compiled import CompiledObject +from jedi.evaluate.helpers import is_string CODES = { @@ -114,9 +117,10 @@ def add_attribute_error(name_context, lookup_context, name): # instead of an error, if that happens. typ = Error if isinstance(lookup_context, AbstractInstanceContext): - slot_names = lookup_context.get_function_slot_names('__getattr__') + \ - lookup_context.get_function_slot_names('__getattribute__') + slot_names = lookup_context.get_function_slot_names(u'__getattr__') + \ + lookup_context.get_function_slot_names(u'__getattribute__') for n in slot_names: + # TODO do we even get here? if isinstance(name, CompiledInstanceName) and \ n.parent_context.obj == object: typ = Warning @@ -139,7 +143,7 @@ def _check_for_exception_catch(node_context, jedi_name, exception, payload=None) """ def check_match(cls, exception): try: - return isinstance(cls, CompiledObject) and issubclass(exception, cls.obj) + return isinstance(cls, CompiledObject) and cls.is_super_class(exception) except TypeError: return False @@ -160,7 +164,7 @@ def check_try_for_except(obj, exception): except_classes = node_context.eval_node(node) for cls in except_classes: from jedi.evaluate.context import iterable - if isinstance(cls, iterable.AbstractIterable) and \ + if isinstance(cls, iterable.Sequence) and \ cls.array_type == 'tuple': # multiple exceptions for lazy_context in cls.py__iter__(): @@ -189,8 +193,8 @@ def check_hasattr(node, suite): # Check name key, lazy_context = args[1] names = list(lazy_context.infer()) - assert len(names) == 1 and isinstance(names[0], CompiledObject) - assert names[0].obj == payload[1].value + assert len(names) == 1 and is_string(names[0]) + assert force_unicode(names[0].get_safe_value()) == payload[1].value # Check objects key, lazy_context = args[0] diff --git a/pythonFiles/jedi/evaluate/arguments.py b/pythonFiles/jedi/evaluate/arguments.py index 32b9238c6f4d..beab4c8c9541 100644 --- a/pythonFiles/jedi/evaluate/arguments.py +++ b/pythonFiles/jedi/evaluate/arguments.py @@ -10,6 +10,7 @@ from jedi.evaluate.context import iterable from jedi.evaluate.param import get_params, ExecutedParam + def try_iter_content(types, depth=0): """Helper method for static analysis.""" if depth > 10: @@ -29,6 +30,8 @@ def try_iter_content(types, depth=0): class AbstractArguments(object): context = None + argument_node = None + trailer = None def eval_argument_clinic(self, parameters): """Uses a list with argument clinic information (see PEP 436).""" @@ -95,29 +98,30 @@ def __init__(self, evaluator, context, argument_node, trailer=None): self.trailer = trailer # Can be None, e.g. in a class definition. def _split(self): - if isinstance(self.argument_node, (tuple, list)): - for el in self.argument_node: - yield 0, el - else: - if not (self.argument_node.type == 'arglist' or ( - # in python 3.5 **arg is an argument, not arglist - (self.argument_node.type == 'argument') and - self.argument_node.children[0] in ('*', '**'))): - yield 0, self.argument_node - return - - iterator = iter(self.argument_node.children) - for child in iterator: - if child == ',': - continue - elif child in ('*', '**'): - yield len(child.value), next(iterator) - elif child.type == 'argument' and \ - child.children[0] in ('*', '**'): - assert len(child.children) == 2 - yield len(child.children[0].value), child.children[1] - else: - yield 0, child + if self.argument_node is None: + return + + # Allow testlist here as well for Python2's class inheritance + # definitions. + if not (self.argument_node.type in ('arglist', 'testlist') or ( + # in python 3.5 **arg is an argument, not arglist + (self.argument_node.type == 'argument') and + self.argument_node.children[0] in ('*', '**'))): + yield 0, self.argument_node + return + + iterator = iter(self.argument_node.children) + for child in iterator: + if child == ',': + continue + elif child in ('*', '**'): + yield len(child.value), next(iterator) + elif child.type == 'argument' and \ + child.children[0] in ('*', '**'): + assert len(child.children) == 2 + yield len(child.children[0].value), child.children[1] + else: + yield 0, child def unpack(self, funcdef=None): named_args = [] @@ -126,7 +130,6 @@ def unpack(self, funcdef=None): arrays = self.context.eval_node(el) iterators = [_iterate_star_args(self.context, a, el, funcdef) for a in arrays] - iterators = list(iterators) for values in list(zip_longest(*iterators)): # TODO zip_longest yields None, that means this would raise # an exception? @@ -134,7 +137,7 @@ def unpack(self, funcdef=None): [v for v in values if v is not None] ) elif star_count == 2: - arrays = self._evaluator.eval_element(self.context, el) + arrays = self.context.eval_node(el) for dct in arrays: for key, values in _star_star_dict(self.context, dct, el, funcdef): yield key, values @@ -197,7 +200,11 @@ def get_calling_nodes(self): arguments = param.var_args break - return [arguments.argument_node or arguments.trailer] + if arguments.argument_node is not None: + return [arguments.argument_node] + if arguments.trailer is not None: + return [arguments.trailer] + return [] class ValuesArguments(AbstractArguments): @@ -235,7 +242,7 @@ def _star_star_dict(context, array, input_node, funcdef): # For now ignore this case. In the future add proper iterators and just # make one call without crazy isinstance checks. return {} - elif isinstance(array, iterable.AbstractIterable) and array.array_type == 'dict': + elif isinstance(array, iterable.Sequence) and array.array_type == 'dict': return array.exact_key_items() else: if funcdef is not None: diff --git a/pythonFiles/jedi/evaluate/base_context.py b/pythonFiles/jedi/evaluate/base_context.py index 693a99aae7aa..2c6fe6cd2c88 100644 --- a/pythonFiles/jedi/evaluate/base_context.py +++ b/pythonFiles/jedi/evaluate/base_context.py @@ -1,3 +1,11 @@ +""" +Contexts are the "values" that Python would return. However Contexts are at the +same time also the "contexts" that a user is currently sitting in. + +A ContextSet is typically used to specify the return of a function or any other +static analysis operation. In jedi there are always multiple returns and not +just one. +""" from parso.python.tree import ExprStmt, CompFor from jedi import debug @@ -63,10 +71,13 @@ def execute_evaluated(self, *value_list): arguments = ValuesArguments([ContextSet(value) for value in value_list]) return self.execute(arguments) - def iterate(self, contextualized_node=None): - debug.dbg('iterate') + def iterate(self, contextualized_node=None, is_async=False): + debug.dbg('iterate %s', self) try: - iter_method = self.py__iter__ + if is_async: + iter_method = self.py__aiter__ + else: + iter_method = self.py__iter__ except AttributeError: if contextualized_node is not None: from jedi.evaluate import analysis @@ -81,17 +92,22 @@ def iterate(self, contextualized_node=None): def get_item(self, index_contexts, contextualized_node): from jedi.evaluate.compiled import CompiledObject - from jedi.evaluate.context.iterable import Slice, AbstractIterable + from jedi.evaluate.context.iterable import Slice, Sequence result = ContextSet() for index in index_contexts: - if isinstance(index, (CompiledObject, Slice)): + if isinstance(index, Slice): index = index.obj + if isinstance(index, CompiledObject): + try: + index = index.get_safe_value() + except ValueError: + pass - if type(index) not in (float, int, str, unicode, slice, type(Ellipsis)): + if type(index) not in (float, int, str, unicode, slice, bytes): # If the index is not clearly defined, we have to get all the # possiblities. - if isinstance(self, AbstractIterable) and self.array_type == 'dict': + if isinstance(self, Sequence) and self.array_type == 'dict': result |= self.dict_values() else: result |= iterate_contexts(ContextSet(self)) @@ -139,10 +155,6 @@ def py__getattribute__(self, name_or_str, name_context=None, position=None, return f.filter_name(filters) return f.find(filters, attribute_lookup=not search_global) - return self.evaluator.find_types( - self, name_or_str, name_context, position, search_global, is_goto, - analysis_errors) - def create_context(self, node, node_is_context=False, node_is_object=False): return self.evaluator.create_context(self, node, node_is_context, node_is_object) @@ -169,14 +181,14 @@ def py__doc__(self, include_call_signature=False): return None -def iterate_contexts(contexts, contextualized_node=None): +def iterate_contexts(contexts, contextualized_node=None, is_async=False): """ Calls `iterate`, on all contexts but ignores the ordering and just returns all contexts that the iterate functions yield. """ return ContextSet.from_sets( lazy_context.infer() - for lazy_context in contexts.iterate(contextualized_node) + for lazy_context in contexts.iterate(contextualized_node, is_async=is_async) ) @@ -241,9 +253,9 @@ class ContextSet(BaseContextSet): def py__class__(self): return ContextSet.from_iterable(c.py__class__() for c in self._set) - def iterate(self, contextualized_node=None): + def iterate(self, contextualized_node=None, is_async=False): from jedi.evaluate.lazy_context import get_merged_lazy_context - type_iters = [c.iterate(contextualized_node) for c in self._set] + type_iters = [c.iterate(contextualized_node, is_async=is_async) for c in self._set] for lazy_contexts in zip_longest(*type_iters): yield get_merged_lazy_context( [l for l in lazy_contexts if l is not None] diff --git a/pythonFiles/jedi/evaluate/cache.py b/pythonFiles/jedi/evaluate/cache.py index b7c7cd7e979f..c619e698a3c8 100644 --- a/pythonFiles/jedi/evaluate/cache.py +++ b/pythonFiles/jedi/evaluate/cache.py @@ -59,7 +59,7 @@ def decorator(func): return decorator -def _memoize_meta_class(): +def evaluator_as_method_param_cache(): def decorator(call): return _memoize_default(second_arg_is_evaluator=True)(call) @@ -72,6 +72,6 @@ class CachedMetaClass(type): class initializations. Either you do it this way or with decorators, but with decorators you lose class access (isinstance, etc). """ - @_memoize_meta_class() + @evaluator_as_method_param_cache() def __call__(self, *args, **kwargs): return super(CachedMetaClass, self).__call__(*args, **kwargs) diff --git a/pythonFiles/jedi/evaluate/compiled/__init__.py b/pythonFiles/jedi/evaluate/compiled/__init__.py index f9f2e0781e13..357d26cc87fc 100644 --- a/pythonFiles/jedi/evaluate/compiled/__init__.py +++ b/pythonFiles/jedi/evaluate/compiled/__init__.py @@ -1,638 +1,39 @@ -""" -Imitate the parser representation. -""" -import inspect -import re -import sys -import os -import types -from functools import partial +from jedi._compatibility import unicode +from jedi.evaluate.compiled.context import CompiledObject, CompiledName, \ + CompiledObjectFilter, CompiledContextName, create_from_access_path, \ + create_from_name -from jedi._compatibility import builtins as _builtins, unicode, py_version -from jedi import debug -from jedi.cache import underscore_memoization, memoize_method -from jedi.evaluate.filters import AbstractFilter, AbstractNameDefinition, \ - ContextNameMixin -from jedi.evaluate.base_context import Context, ContextSet -from jedi.evaluate.lazy_context import LazyKnownContext -from jedi.evaluate.compiled.getattr_static import getattr_static -from . import fake - -_sep = os.path.sep -if os.path.altsep is not None: - _sep += os.path.altsep -_path_re = re.compile('(?:\.[^{0}]+|[{0}]__init__\.py)$'.format(re.escape(_sep))) -del _sep - -# Those types don't exist in typing. -MethodDescriptorType = type(str.replace) -WrapperDescriptorType = type(set.__iter__) -# `object.__subclasshook__` is an already executed descriptor. -object_class_dict = type.__dict__["__dict__"].__get__(object) -ClassMethodDescriptorType = type(object_class_dict['__subclasshook__']) - -ALLOWED_DESCRIPTOR_ACCESS = ( - types.FunctionType, - types.GetSetDescriptorType, - types.MemberDescriptorType, - MethodDescriptorType, - WrapperDescriptorType, - ClassMethodDescriptorType, - staticmethod, - classmethod, -) - -class CheckAttribute(object): - """Raises an AttributeError if the attribute X isn't available.""" - def __init__(self, func): - self.func = func - # Remove the py in front of e.g. py__call__. - self.check_name = func.__name__[2:] - - def __get__(self, instance, owner): - # This might raise an AttributeError. That's wanted. - if self.check_name == '__iter__': - # Python iterators are a bit strange, because there's no need for - # the __iter__ function as long as __getitem__ is defined (it will - # just start with __getitem__(0). This is especially true for - # Python 2 strings, where `str.__iter__` is not even defined. - try: - iter(instance.obj) - except TypeError: - raise AttributeError - else: - getattr(instance.obj, self.check_name) - return partial(self.func, instance) - - -class CompiledObject(Context): - path = None # modules have this attribute - set it to None. - used_names = lambda self: {} # To be consistent with modules. - - def __init__(self, evaluator, obj, parent_context=None, faked_class=None): - super(CompiledObject, self).__init__(evaluator, parent_context) - self.obj = obj - # This attribute will not be set for most classes, except for fakes. - self.tree_node = faked_class - - def get_root_node(self): - # To make things a bit easier with filters we add this method here. - return self.get_root_context() - - @CheckAttribute - def py__call__(self, params): - if inspect.isclass(self.obj): - from jedi.evaluate.context import CompiledInstance - return ContextSet(CompiledInstance(self.evaluator, self.parent_context, self, params)) - else: - return ContextSet.from_iterable(self._execute_function(params)) - - @CheckAttribute - def py__class__(self): - return create(self.evaluator, self.obj.__class__) - - @CheckAttribute - def py__mro__(self): - return (self,) + tuple(create(self.evaluator, cls) for cls in self.obj.__mro__[1:]) - - @CheckAttribute - def py__bases__(self): - return tuple(create(self.evaluator, cls) for cls in self.obj.__bases__) - - def py__bool__(self): - return bool(self.obj) - - def py__file__(self): - try: - return self.obj.__file__ - except AttributeError: - return None - - def is_class(self): - return inspect.isclass(self.obj) - - def py__doc__(self, include_call_signature=False): - return inspect.getdoc(self.obj) or '' - - def get_param_names(self): - obj = self.obj - try: - if py_version < 33: - raise ValueError("inspect.signature was introduced in 3.3") - if py_version == 34: - # In 3.4 inspect.signature are wrong for str and int. This has - # been fixed in 3.5. The signature of object is returned, - # because no signature was found for str. Here we imitate 3.5 - # logic and just ignore the signature if the magic methods - # don't match object. - # 3.3 doesn't even have the logic and returns nothing for str - # and classes that inherit from object. - user_def = inspect._signature_get_user_defined_method - if (inspect.isclass(obj) - and not user_def(type(obj), '__init__') - and not user_def(type(obj), '__new__') - and (obj.__init__ != object.__init__ - or obj.__new__ != object.__new__)): - raise ValueError - - signature = inspect.signature(obj) - except ValueError: # Has no signature - params_str, ret = self._parse_function_doc() - tokens = params_str.split(',') - if inspect.ismethoddescriptor(obj): - tokens.insert(0, 'self') - for p in tokens: - parts = p.strip().split('=') - yield UnresolvableParamName(self, parts[0]) - else: - for signature_param in signature.parameters.values(): - yield SignatureParamName(self, signature_param) - - def __repr__(self): - return '<%s: %s>' % (self.__class__.__name__, repr(self.obj)) - - @underscore_memoization - def _parse_function_doc(self): - doc = self.py__doc__() - if doc is None: - return '', '' - - return _parse_function_doc(doc) - - @property - def api_type(self): - obj = self.obj - if inspect.isclass(obj): - return 'class' - elif inspect.ismodule(obj): - return 'module' - elif inspect.isbuiltin(obj) or inspect.ismethod(obj) \ - or inspect.ismethoddescriptor(obj) or inspect.isfunction(obj): - return 'function' - # Everything else... - return 'instance' - - @property - def type(self): - """Imitate the tree.Node.type values.""" - cls = self._get_class() - if inspect.isclass(cls): - return 'classdef' - elif inspect.ismodule(cls): - return 'file_input' - elif inspect.isbuiltin(cls) or inspect.ismethod(cls) or \ - inspect.ismethoddescriptor(cls): - return 'funcdef' - - @underscore_memoization - def _cls(self): - """ - We used to limit the lookups for instantiated objects like list(), but - this is not the case anymore. Python itself - """ - # Ensures that a CompiledObject is returned that is not an instance (like list) - return self - - def _get_class(self): - if not fake.is_class_instance(self.obj) or \ - inspect.ismethoddescriptor(self.obj): # slots - return self.obj - - try: - return self.obj.__class__ - except AttributeError: - # happens with numpy.core.umath._UFUNC_API (you get it - # automatically by doing `import numpy`. - return type - - def get_filters(self, search_global=False, is_instance=False, - until_position=None, origin_scope=None): - yield self._ensure_one_filter(is_instance) - - @memoize_method - def _ensure_one_filter(self, is_instance): - """ - search_global shouldn't change the fact that there's one dict, this way - there's only one `object`. - """ - return CompiledObjectFilter(self.evaluator, self, is_instance) - - @CheckAttribute - def py__getitem__(self, index): - if type(self.obj) not in (str, list, tuple, unicode, bytes, bytearray, dict): - # Get rid of side effects, we won't call custom `__getitem__`s. - return ContextSet() - - return ContextSet(create(self.evaluator, self.obj[index])) - - @CheckAttribute - def py__iter__(self): - if type(self.obj) not in (str, list, tuple, unicode, bytes, bytearray, dict): - # Get rid of side effects, we won't call custom `__getitem__`s. - return - - for i, part in enumerate(self.obj): - if i > 20: - # Should not go crazy with large iterators - break - yield LazyKnownContext(create(self.evaluator, part)) - - def py__name__(self): - try: - return self._get_class().__name__ - except AttributeError: - return None - - @property - def name(self): - try: - name = self._get_class().__name__ - except AttributeError: - name = repr(self.obj) - return CompiledContextName(self, name) - - def _execute_function(self, params): - from jedi.evaluate import docstrings - if self.type != 'funcdef': - return - for name in self._parse_function_doc()[1].split(): - try: - bltn_obj = getattr(_builtins, name) - except AttributeError: - continue - else: - if bltn_obj is None: - # We want to evaluate everything except None. - # TODO do we? - continue - bltn_obj = create(self.evaluator, bltn_obj) - for result in bltn_obj.execute(params): - yield result - for type_ in docstrings.infer_return_types(self): - yield type_ - - def get_self_attributes(self): - return [] # Instance compatibility - - def get_imports(self): - return [] # Builtins don't have imports - - def dict_values(self): - return ContextSet.from_iterable( - create(self.evaluator, v) for v in self.obj.values() - ) - - -class CompiledName(AbstractNameDefinition): - def __init__(self, evaluator, parent_context, name): - self._evaluator = evaluator - self.parent_context = parent_context - self.string_name = name - - def __repr__(self): - try: - name = self.parent_context.name # __name__ is not defined all the time - except AttributeError: - name = None - return '<%s: (%s).%s>' % (self.__class__.__name__, name, self.string_name) - - @property - def api_type(self): - return next(iter(self.infer())).api_type - - @underscore_memoization - def infer(self): - module = self.parent_context.get_root_context() - return ContextSet(_create_from_name( - self._evaluator, module, self.parent_context, self.string_name - )) - - -class SignatureParamName(AbstractNameDefinition): - api_type = 'param' - - def __init__(self, compiled_obj, signature_param): - self.parent_context = compiled_obj.parent_context - self._signature_param = signature_param - - @property - def string_name(self): - return self._signature_param.name - - def infer(self): - p = self._signature_param - evaluator = self.parent_context.evaluator - contexts = ContextSet() - if p.default is not p.empty: - contexts = ContextSet(create(evaluator, p.default)) - if p.annotation is not p.empty: - annotation = create(evaluator, p.annotation) - contexts |= annotation.execute_evaluated() - return contexts - - -class UnresolvableParamName(AbstractNameDefinition): - api_type = 'param' - - def __init__(self, compiled_obj, name): - self.parent_context = compiled_obj.parent_context - self.string_name = name - - def infer(self): - return ContextSet() - - -class CompiledContextName(ContextNameMixin, AbstractNameDefinition): - def __init__(self, context, name): - self.string_name = name - self._context = context - self.parent_context = context.parent_context - - -class EmptyCompiledName(AbstractNameDefinition): - """ - Accessing some names will raise an exception. To avoid not having any - completions, just give Jedi the option to return this object. It infers to - nothing. - """ - def __init__(self, evaluator, name): - self.parent_context = evaluator.BUILTINS - self.string_name = name - - def infer(self): - return ContextSet() - - -class CompiledObjectFilter(AbstractFilter): - name_class = CompiledName - - def __init__(self, evaluator, compiled_object, is_instance=False): - self._evaluator = evaluator - self._compiled_object = compiled_object - self._is_instance = is_instance - - @memoize_method - def get(self, name): - name = str(name) - obj = self._compiled_object.obj - try: - attr, is_get_descriptor = getattr_static(obj, name) - except AttributeError: - return [] - else: - if is_get_descriptor \ - and not type(attr) in ALLOWED_DESCRIPTOR_ACCESS: - # In case of descriptors that have get methods we cannot return - # it's value, because that would mean code execution. - return [EmptyCompiledName(self._evaluator, name)] - if self._is_instance and name not in dir(obj): - return [] - return [self._create_name(name)] - - def values(self): - obj = self._compiled_object.obj - - names = [] - for name in dir(obj): - names += self.get(name) - - is_instance = self._is_instance or fake.is_class_instance(obj) - # ``dir`` doesn't include the type names. - if not inspect.ismodule(obj) and (obj is not type) and not is_instance: - for filter in create(self._evaluator, type).get_filters(): - names += filter.values() - return names - - def _create_name(self, name): - return self.name_class(self._evaluator, self._compiled_object, name) - - -def dotted_from_fs_path(fs_path, sys_path): - """ - Changes `/usr/lib/python3.4/email/utils.py` to `email.utils`. I.e. - compares the path with sys.path and then returns the dotted_path. If the - path is not in the sys.path, just returns None. - """ - if os.path.basename(fs_path).startswith('__init__.'): - # We are calculating the path. __init__ files are not interesting. - fs_path = os.path.dirname(fs_path) - - # prefer - # - UNIX - # /path/to/pythonX.Y/lib-dynload - # /path/to/pythonX.Y/site-packages - # - Windows - # C:\path\to\DLLs - # C:\path\to\Lib\site-packages - # over - # - UNIX - # /path/to/pythonX.Y - # - Windows - # C:\path\to\Lib - path = '' - for s in sys_path: - if (fs_path.startswith(s) and len(path) < len(s)): - path = s - - # - Window - # X:\path\to\lib-dynload/datetime.pyd => datetime - module_path = fs_path[len(path):].lstrip(os.path.sep).lstrip('/') - # - Window - # Replace like X:\path\to\something/foo/bar.py - return _path_re.sub('', module_path).replace(os.path.sep, '.').replace('/', '.') - - -def load_module(evaluator, path=None, name=None): - sys_path = list(evaluator.project.sys_path) - if path is not None: - dotted_path = dotted_from_fs_path(path, sys_path=sys_path) - else: - dotted_path = name - - temp, sys.path = sys.path, sys_path - try: - __import__(dotted_path) - except RuntimeError: - if 'PySide' in dotted_path or 'PyQt' in dotted_path: - # RuntimeError: the PyQt4.QtCore and PyQt5.QtCore modules both wrap - # the QObject class. - # See https://github.com/davidhalter/jedi/pull/483 - return None - raise - except ImportError: - # If a module is "corrupt" or not really a Python module or whatever. - debug.warning('Module %s not importable in path %s.', dotted_path, path) - return None - finally: - sys.path = temp - - # Just access the cache after import, because of #59 as well as the very - # complicated import structure of Python. - module = sys.modules[dotted_path] - - return create(evaluator, module) - - -docstr_defaults = { - 'floating point number': 'float', - 'character': 'str', - 'integer': 'int', - 'dictionary': 'dict', - 'string': 'str', -} +def builtin_from_name(evaluator, string): + builtins = evaluator.builtins_module + return create_from_name(evaluator, builtins, string) -def _parse_function_doc(doc): +def create_simple_object(evaluator, obj): """ - Takes a function and returns the params and return value as a tuple. - This is nothing more than a docstring parser. - - TODO docstrings like utime(path, (atime, mtime)) and a(b [, b]) -> None - TODO docstrings like 'tuple of integers' + Only allows creations of objects that are easily picklable across Python + versions. """ - # parse round parentheses: def func(a, (b,c)) - try: - count = 0 - start = doc.index('(') - for i, s in enumerate(doc[start:]): - if s == '(': - count += 1 - elif s == ')': - count -= 1 - if count == 0: - end = start + i - break - param_str = doc[start + 1:end] - except (ValueError, UnboundLocalError): - # ValueError for doc.index - # UnboundLocalError for undefined end in last line - debug.dbg('no brackets found - no param') - end = 0 - param_str = '' - else: - # remove square brackets, that show an optional param ( = None) - def change_options(m): - args = m.group(1).split(',') - for i, a in enumerate(args): - if a and '=' not in a: - args[i] += '=None' - return ','.join(args) - - while True: - param_str, changes = re.subn(r' ?\[([^\[\]]+)\]', - change_options, param_str) - if changes == 0: - break - param_str = param_str.replace('-', '_') # see: isinstance.__doc__ - - # parse return value - r = re.search('-[>-]* ', doc[end:end + 7]) - if r is None: - ret = '' - else: - index = end + r.end() - # get result type, which can contain newlines - pattern = re.compile(r'(,\n|[^\n-])+') - ret_str = pattern.match(doc, index).group(0).strip() - # New object -> object() - ret_str = re.sub(r'[nN]ew (.*)', r'\1()', ret_str) - - ret = docstr_defaults.get(ret_str, ret_str) - - return param_str, ret - - -def _create_from_name(evaluator, module, compiled_object, name): - obj = compiled_object.obj - faked = None - try: - faked = fake.get_faked(evaluator, module, obj, parent_context=compiled_object, name=name) - if faked.type == 'funcdef': - from jedi.evaluate.context.function import FunctionContext - return FunctionContext(evaluator, compiled_object, faked) - except fake.FakeDoesNotExist: - pass - - try: - obj = getattr(obj, name) - except AttributeError: - # Happens e.g. in properties of - # PyQt4.QtGui.QStyleOptionComboBox.currentText - # -> just set it to None - obj = None - return create(evaluator, obj, parent_context=compiled_object, faked=faked) - - -def builtin_from_name(evaluator, string): - bltn_obj = getattr(_builtins, string) - return create(evaluator, bltn_obj) - - -def _a_generator(foo): - """Used to have an object to return for generators.""" - yield 42 - yield foo - - -_SPECIAL_OBJECTS = { - 'FUNCTION_CLASS': type(load_module), - 'METHOD_CLASS': type(CompiledObject.is_class), - 'MODULE_CLASS': type(os), - 'GENERATOR_OBJECT': _a_generator(1.0), - 'BUILTINS': _builtins, -} + assert isinstance(obj, (int, float, str, bytes, unicode, slice, complex)) + return create_from_access_path( + evaluator, + evaluator.compiled_subprocess.create_simple_object(obj) + ) def get_special_object(evaluator, identifier): - obj = _SPECIAL_OBJECTS[identifier] - return create(evaluator, obj, parent_context=create(evaluator, _builtins)) + return create_from_access_path( + evaluator, + evaluator.compiled_subprocess.get_special_object(identifier) + ) -def compiled_objects_cache(attribute_name): - def decorator(func): - """ - This decorator caches just the ids, oopposed to caching the object itself. - Caching the id has the advantage that an object doesn't need to be - hashable. - """ - def wrapper(evaluator, obj, parent_context=None, module=None, faked=None): - cache = getattr(evaluator, attribute_name) - # Do a very cheap form of caching here. - key = id(obj), id(parent_context) - try: - return cache[key][0] - except KeyError: - # TODO this whole decorator is way too ugly - result = func(evaluator, obj, parent_context, module, faked) - # Need to cache all of them, otherwise the id could be overwritten. - cache[key] = result, obj, parent_context, module, faked - return result - return wrapper +def get_string_context_set(evaluator): + return builtin_from_name(evaluator, u'str').execute_evaluated() - return decorator - -@compiled_objects_cache('compiled_cache') -def create(evaluator, obj, parent_context=None, module=None, faked=None): - """ - A very weird interface class to this module. The more options provided the - more acurate loading compiled objects is. - """ - if inspect.ismodule(obj): - if parent_context is not None: - # Modules don't have parents, be careful with caching: recurse. - return create(evaluator, obj) - else: - if parent_context is None and obj is not _builtins: - return create(evaluator, obj, create(evaluator, _builtins)) - - try: - faked = fake.get_faked(evaluator, module, obj, parent_context=parent_context) - if faked.type == 'funcdef': - from jedi.evaluate.context.function import FunctionContext - return FunctionContext(evaluator, parent_context, faked) - except fake.FakeDoesNotExist: - pass - - return CompiledObject(evaluator, obj, parent_context, faked) +def load_module(evaluator, **kwargs): + access_path = evaluator.compiled_subprocess.load_module(**kwargs) + if access_path is None: + return None + return create_from_access_path(evaluator, access_path) diff --git a/pythonFiles/jedi/evaluate/compiled/access.py b/pythonFiles/jedi/evaluate/compiled/access.py new file mode 100644 index 000000000000..97a8dd0b5c3d --- /dev/null +++ b/pythonFiles/jedi/evaluate/compiled/access.py @@ -0,0 +1,490 @@ +import inspect +import types +import sys +from textwrap import dedent +import operator as op +from collections import namedtuple + +from jedi import debug +from jedi._compatibility import unicode, is_py3, is_py34, builtins, \ + py_version, force_unicode, print_to_stderr +from jedi.evaluate.compiled.getattr_static import getattr_static +from jedi.evaluate.utils import dotted_from_fs_path + + +MethodDescriptorType = type(str.replace) +# These are not considered classes and access is granted even though they have +# a __class__ attribute. +NOT_CLASS_TYPES = ( + types.BuiltinFunctionType, + types.CodeType, + types.FrameType, + types.FunctionType, + types.GeneratorType, + types.GetSetDescriptorType, + types.LambdaType, + types.MemberDescriptorType, + types.MethodType, + types.ModuleType, + types.TracebackType, + MethodDescriptorType +) + +if is_py3: + NOT_CLASS_TYPES += ( + types.MappingProxyType, + types.SimpleNamespace + ) + if is_py34: + NOT_CLASS_TYPES += (types.DynamicClassAttribute,) + + +# Those types don't exist in typing. +MethodDescriptorType = type(str.replace) +WrapperDescriptorType = type(set.__iter__) +# `object.__subclasshook__` is an already executed descriptor. +object_class_dict = type.__dict__["__dict__"].__get__(object) +ClassMethodDescriptorType = type(object_class_dict['__subclasshook__']) + +def _a_generator(foo): + """Used to have an object to return for generators.""" + yield 42 + yield foo + + +_sentinel = object() + +# Maps Python syntax to the operator module. +COMPARISON_OPERATORS = { + '==': op.eq, + '!=': op.ne, + 'is': op.is_, + 'is not': op.is_not, + '<': op.lt, + '<=': op.le, + '>': op.gt, + '>=': op.ge, +} + +_OPERATORS = { + '+': op.add, + '-': op.sub, +} +_OPERATORS.update(COMPARISON_OPERATORS) + +ALLOWED_DESCRIPTOR_ACCESS = ( + types.FunctionType, + types.GetSetDescriptorType, + types.MemberDescriptorType, + MethodDescriptorType, + WrapperDescriptorType, + ClassMethodDescriptorType, + staticmethod, + classmethod, +) + + +def safe_getattr(obj, name, default=_sentinel): + try: + attr, is_get_descriptor = getattr_static(obj, name) + except AttributeError: + if default is _sentinel: + raise + return default + else: + if type(attr) in ALLOWED_DESCRIPTOR_ACCESS: + # In case of descriptors that have get methods we cannot return + # it's value, because that would mean code execution. + return getattr(obj, name) + return attr + + +SignatureParam = namedtuple( + 'SignatureParam', + 'name has_default default has_annotation annotation kind_name' +) + + +def compiled_objects_cache(attribute_name): + def decorator(func): + """ + This decorator caches just the ids, oopposed to caching the object itself. + Caching the id has the advantage that an object doesn't need to be + hashable. + """ + def wrapper(evaluator, obj, parent_context=None): + cache = getattr(evaluator, attribute_name) + # Do a very cheap form of caching here. + key = id(obj) + try: + cache[key] + return cache[key][0] + except KeyError: + # TODO wuaaaarrghhhhhhhh + if attribute_name == 'mixed_cache': + result = func(evaluator, obj, parent_context) + else: + result = func(evaluator, obj) + # Need to cache all of them, otherwise the id could be overwritten. + cache[key] = result, obj, parent_context + return result + return wrapper + + return decorator + + +def create_access(evaluator, obj): + return evaluator.compiled_subprocess.get_or_create_access_handle(obj) + + +def load_module(evaluator, path=None, name=None, sys_path=None): + if sys_path is None: + sys_path = list(evaluator.get_sys_path()) + if path is not None: + dotted_path = dotted_from_fs_path(path, sys_path=sys_path) + else: + dotted_path = name + + temp, sys.path = sys.path, sys_path + try: + __import__(dotted_path) + except ImportError: + # If a module is "corrupt" or not really a Python module or whatever. + debug.warning('Module %s not importable in path %s.', dotted_path, path) + return None + except Exception: + # Since __import__ pretty much makes code execution possible, just + # catch any error here and print it. + import traceback + print_to_stderr("Cannot import:\n%s" % traceback.format_exc()) + return None + finally: + sys.path = temp + + # Just access the cache after import, because of #59 as well as the very + # complicated import structure of Python. + module = sys.modules[dotted_path] + return create_access_path(evaluator, module) + + +class AccessPath(object): + def __init__(self, accesses): + self.accesses = accesses + + # Writing both of these methods here looks a bit ridiculous. However with + # the differences of Python 2/3 it's actually necessary, because we will + # otherwise have a accesses attribute that is bytes instead of unicode. + def __getstate__(self): + return self.accesses + + def __setstate__(self, value): + self.accesses = value + + +def create_access_path(evaluator, obj): + access = create_access(evaluator, obj) + return AccessPath(access.get_access_path_tuples()) + + +def _force_unicode_decorator(func): + return lambda *args, **kwargs: force_unicode(func(*args, **kwargs)) + + +class DirectObjectAccess(object): + def __init__(self, evaluator, obj): + self._evaluator = evaluator + self._obj = obj + + def __repr__(self): + return '%s(%s)' % (self.__class__.__name__, self.get_repr()) + + def _create_access(self, obj): + return create_access(self._evaluator, obj) + + def _create_access_path(self, obj): + return create_access_path(self._evaluator, obj) + + def py__bool__(self): + return bool(self._obj) + + def py__file__(self): + try: + return self._obj.__file__ + except AttributeError: + return None + + def py__doc__(self, include_call_signature=False): + return force_unicode(inspect.getdoc(self._obj)) or u'' + + def py__name__(self): + if not _is_class_instance(self._obj) or \ + inspect.ismethoddescriptor(self._obj): # slots + cls = self._obj + else: + try: + cls = self._obj.__class__ + except AttributeError: + # happens with numpy.core.umath._UFUNC_API (you get it + # automatically by doing `import numpy`. + return None + + try: + return force_unicode(cls.__name__) + except AttributeError: + return None + + def py__mro__accesses(self): + return tuple(self._create_access_path(cls) for cls in self._obj.__mro__[1:]) + + def py__getitem__(self, index): + if type(self._obj) not in (str, list, tuple, unicode, bytes, bytearray, dict): + # Get rid of side effects, we won't call custom `__getitem__`s. + return None + + return self._create_access_path(self._obj[index]) + + def py__iter__list(self): + if type(self._obj) not in (str, list, tuple, unicode, bytes, bytearray, dict): + # Get rid of side effects, we won't call custom `__getitem__`s. + return [] + + lst = [] + for i, part in enumerate(self._obj): + if i > 20: + # Should not go crazy with large iterators + break + lst.append(self._create_access_path(part)) + return lst + + def py__class__(self): + return self._create_access_path(self._obj.__class__) + + def py__bases__(self): + return [self._create_access_path(base) for base in self._obj.__bases__] + + @_force_unicode_decorator + def get_repr(self): + builtins = 'builtins', '__builtin__' + + if inspect.ismodule(self._obj): + return repr(self._obj) + # Try to avoid execution of the property. + if safe_getattr(self._obj, '__module__', default='') in builtins: + return repr(self._obj) + + type_ = type(self._obj) + if type_ == type: + return type.__repr__(self._obj) + + if safe_getattr(type_, '__module__', default='') in builtins: + # Allow direct execution of repr for builtins. + return repr(self._obj) + return object.__repr__(self._obj) + + def is_class(self): + return inspect.isclass(self._obj) + + def ismethoddescriptor(self): + return inspect.ismethoddescriptor(self._obj) + + def dir(self): + return list(map(force_unicode, dir(self._obj))) + + def has_iter(self): + try: + iter(self._obj) + return True + except TypeError: + return False + + def is_allowed_getattr(self, name): + # TODO this API is ugly. + try: + attr, is_get_descriptor = getattr_static(self._obj, name) + except AttributeError: + return False, False + else: + if is_get_descriptor and type(attr) not in ALLOWED_DESCRIPTOR_ACCESS: + # In case of descriptors that have get methods we cannot return + # it's value, because that would mean code execution. + return True, True + return True, False + + def getattr(self, name, default=_sentinel): + try: + return self._create_access(getattr(self._obj, name)) + except AttributeError: + # Happens e.g. in properties of + # PyQt4.QtGui.QStyleOptionComboBox.currentText + # -> just set it to None + if default is _sentinel: + raise + return self._create_access(default) + + def get_safe_value(self): + if type(self._obj) in (bool, bytes, float, int, str, unicode, slice): + return self._obj + raise ValueError("Object is type %s and not simple" % type(self._obj)) + + def get_api_type(self): + obj = self._obj + if self.is_class(): + return u'class' + elif inspect.ismodule(obj): + return u'module' + elif inspect.isbuiltin(obj) or inspect.ismethod(obj) \ + or inspect.ismethoddescriptor(obj) or inspect.isfunction(obj): + return u'function' + # Everything else... + return u'instance' + + def get_access_path_tuples(self): + accesses = [create_access(self._evaluator, o) for o in self._get_objects_path()] + return [(access.py__name__(), access) for access in accesses] + + def _get_objects_path(self): + def get(): + obj = self._obj + yield obj + try: + obj = obj.__objclass__ + except AttributeError: + pass + else: + yield obj + + try: + # Returns a dotted string path. + imp_plz = obj.__module__ + except AttributeError: + # Unfortunately in some cases like `int` there's no __module__ + if not inspect.ismodule(obj): + yield builtins + else: + if imp_plz is None: + # Happens for example in `(_ for _ in []).send.__module__`. + yield builtins + else: + try: + # TODO use sys.modules, __module__ can be faked. + yield sys.modules[imp_plz] + except KeyError: + # __module__ can be something arbitrary that doesn't exist. + yield builtins + + return list(reversed(list(get()))) + + def execute_operation(self, other_access_handle, operator): + other_access = other_access_handle.access + op = _OPERATORS[operator] + return self._create_access_path(op(self._obj, other_access._obj)) + + def needs_type_completions(self): + return inspect.isclass(self._obj) and self._obj != type + + def get_signature_params(self): + obj = self._obj + if py_version < 33: + raise ValueError("inspect.signature was introduced in 3.3") + if py_version == 34: + # In 3.4 inspect.signature are wrong for str and int. This has + # been fixed in 3.5. The signature of object is returned, + # because no signature was found for str. Here we imitate 3.5 + # logic and just ignore the signature if the magic methods + # don't match object. + # 3.3 doesn't even have the logic and returns nothing for str + # and classes that inherit from object. + user_def = inspect._signature_get_user_defined_method + if (inspect.isclass(obj) + and not user_def(type(obj), '__init__') + and not user_def(type(obj), '__new__') + and (obj.__init__ != object.__init__ + or obj.__new__ != object.__new__)): + raise ValueError + + try: + signature = inspect.signature(obj) + except (RuntimeError, TypeError): + # Reading the code of the function in Python 3.6 implies there are + # at least these errors that might occur if something is wrong with + # the signature. In that case we just want a simple escape for now. + raise ValueError + return [ + SignatureParam( + name=p.name, + has_default=p.default is not p.empty, + default=self._create_access_path(p.default), + has_annotation=p.annotation is not p.empty, + annotation=self._create_access_path(p.annotation), + kind_name=str(p.kind) + ) for p in signature.parameters.values() + ] + + def negate(self): + return self._create_access_path(-self._obj) + + def dict_values(self): + return [self._create_access_path(v) for v in self._obj.values()] + + def is_super_class(self, exception): + return issubclass(exception, self._obj) + + def get_dir_infos(self): + """ + Used to return a couple of infos that are needed when accessing the sub + objects of an objects + """ + # TODO is_allowed_getattr might raise an AttributeError + tuples = dict( + (force_unicode(name), self.is_allowed_getattr(name)) + for name in self.dir() + ) + return self.needs_type_completions(), tuples + + +def _is_class_instance(obj): + """Like inspect.* methods.""" + try: + cls = obj.__class__ + except AttributeError: + return False + else: + return cls != type and not issubclass(cls, NOT_CLASS_TYPES) + + +if py_version >= 35: + exec(compile(dedent(""" + async def _coroutine(): pass + _coroutine = _coroutine() + CoroutineType = type(_coroutine) + _coroutine.close() # Prevent ResourceWarning + """), 'blub', 'exec')) + _coroutine_wrapper = _coroutine.__await__() +else: + _coroutine = None + _coroutine_wrapper = None + +if py_version >= 36: + exec(compile(dedent(""" + async def _async_generator(): + yield + _async_generator = _async_generator() + AsyncGeneratorType = type(_async_generator) + """), 'blub', 'exec')) +else: + _async_generator = None + +class _SPECIAL_OBJECTS(object): + FUNCTION_CLASS = types.FunctionType + METHOD_CLASS = type(DirectObjectAccess.py__bool__) + MODULE_CLASS = types.ModuleType + GENERATOR_OBJECT = _a_generator(1.0) + BUILTINS = builtins + COROUTINE = _coroutine + COROUTINE_WRAPPER = _coroutine_wrapper + ASYNC_GENERATOR = _async_generator + + +def get_special_object(evaluator, identifier): + obj = getattr(_SPECIAL_OBJECTS, identifier) + return create_access_path(evaluator, obj) diff --git a/pythonFiles/jedi/evaluate/compiled/context.py b/pythonFiles/jedi/evaluate/compiled/context.py new file mode 100644 index 000000000000..f81509d73f72 --- /dev/null +++ b/pythonFiles/jedi/evaluate/compiled/context.py @@ -0,0 +1,474 @@ +""" +Imitate the parser representation. +""" +import re +from functools import partial + +from jedi import debug +from jedi._compatibility import force_unicode, Parameter +from jedi.cache import underscore_memoization, memoize_method +from jedi.evaluate.filters import AbstractFilter, AbstractNameDefinition, \ + ContextNameMixin +from jedi.evaluate.base_context import Context, ContextSet +from jedi.evaluate.lazy_context import LazyKnownContext +from jedi.evaluate.compiled.access import _sentinel +from jedi.evaluate.cache import evaluator_function_cache +from . import fake + + +class CheckAttribute(object): + """Raises an AttributeError if the attribute X isn't available.""" + def __init__(self, func): + self.func = func + # Remove the py in front of e.g. py__call__. + self.check_name = force_unicode(func.__name__[2:]) + + def __get__(self, instance, owner): + if instance is None: + return self + + # This might raise an AttributeError. That's wanted. + if self.check_name == '__iter__': + # Python iterators are a bit strange, because there's no need for + # the __iter__ function as long as __getitem__ is defined (it will + # just start with __getitem__(0). This is especially true for + # Python 2 strings, where `str.__iter__` is not even defined. + if not instance.access_handle.has_iter(): + raise AttributeError + else: + instance.access_handle.getattr(self.check_name) + return partial(self.func, instance) + + +class CompiledObject(Context): + def __init__(self, evaluator, access_handle, parent_context=None, faked_class=None): + super(CompiledObject, self).__init__(evaluator, parent_context) + self.access_handle = access_handle + # This attribute will not be set for most classes, except for fakes. + self.tree_node = faked_class + + @CheckAttribute + def py__call__(self, params): + if self.tree_node is not None and self.tree_node.type == 'funcdef': + from jedi.evaluate.context.function import FunctionContext + return FunctionContext( + self.evaluator, + parent_context=self.parent_context, + funcdef=self.tree_node + ).py__call__(params) + if self.access_handle.is_class(): + from jedi.evaluate.context import CompiledInstance + return ContextSet(CompiledInstance(self.evaluator, self.parent_context, self, params)) + else: + return ContextSet.from_iterable(self._execute_function(params)) + + @CheckAttribute + def py__class__(self): + return create_from_access_path(self.evaluator, self.access_handle.py__class__()) + + @CheckAttribute + def py__mro__(self): + return (self,) + tuple( + create_from_access_path(self.evaluator, access) + for access in self.access_handle.py__mro__accesses() + ) + + @CheckAttribute + def py__bases__(self): + return tuple( + create_from_access_path(self.evaluator, access) + for access in self.access_handle.py__bases__() + ) + + def py__bool__(self): + return self.access_handle.py__bool__() + + def py__file__(self): + return self.access_handle.py__file__() + + def is_class(self): + return self.access_handle.is_class() + + def py__doc__(self, include_call_signature=False): + return self.access_handle.py__doc__() + + def get_param_names(self): + try: + signature_params = self.access_handle.get_signature_params() + except ValueError: # Has no signature + params_str, ret = self._parse_function_doc() + tokens = params_str.split(',') + if self.access_handle.ismethoddescriptor(): + tokens.insert(0, 'self') + for p in tokens: + parts = p.strip().split('=') + yield UnresolvableParamName(self, parts[0]) + else: + for signature_param in signature_params: + yield SignatureParamName(self, signature_param) + + def __repr__(self): + return '<%s: %s>' % (self.__class__.__name__, self.access_handle.get_repr()) + + @underscore_memoization + def _parse_function_doc(self): + doc = self.py__doc__() + if doc is None: + return '', '' + + return _parse_function_doc(doc) + + @property + def api_type(self): + return self.access_handle.get_api_type() + + @underscore_memoization + def _cls(self): + """ + We used to limit the lookups for instantiated objects like list(), but + this is not the case anymore. Python itself + """ + # Ensures that a CompiledObject is returned that is not an instance (like list) + return self + + def get_filters(self, search_global=False, is_instance=False, + until_position=None, origin_scope=None): + yield self._ensure_one_filter(is_instance) + + @memoize_method + def _ensure_one_filter(self, is_instance): + """ + search_global shouldn't change the fact that there's one dict, this way + there's only one `object`. + """ + return CompiledObjectFilter(self.evaluator, self, is_instance) + + @CheckAttribute + def py__getitem__(self, index): + access = self.access_handle.py__getitem__(index) + if access is None: + return ContextSet() + + return ContextSet(create_from_access_path(self.evaluator, access)) + + @CheckAttribute + def py__iter__(self): + for access in self.access_handle.py__iter__list(): + yield LazyKnownContext(create_from_access_path(self.evaluator, access)) + + def py__name__(self): + return self.access_handle.py__name__() + + @property + def name(self): + name = self.py__name__() + if name is None: + name = self.access_handle.get_repr() + return CompiledContextName(self, name) + + def _execute_function(self, params): + from jedi.evaluate import docstrings + from jedi.evaluate.compiled import builtin_from_name + if self.api_type != 'function': + return + + for name in self._parse_function_doc()[1].split(): + try: + # TODO wtf is this? this is exactly the same as the thing + # below. It uses getattr as well. + self.evaluator.builtins_module.access_handle.getattr(name) + except AttributeError: + continue + else: + bltn_obj = builtin_from_name(self.evaluator, name) + for result in bltn_obj.execute(params): + yield result + for type_ in docstrings.infer_return_types(self): + yield type_ + + def dict_values(self): + return ContextSet.from_iterable( + create_from_access_path(self.evaluator, access) + for access in self.access_handle.dict_values() + ) + + def get_safe_value(self, default=_sentinel): + try: + return self.access_handle.get_safe_value() + except ValueError: + if default == _sentinel: + raise + return default + + def execute_operation(self, other, operator): + return create_from_access_path( + self.evaluator, + self.access_handle.execute_operation(other.access_handle, operator) + ) + + def negate(self): + return create_from_access_path(self.evaluator, self.access_handle.negate()) + + def is_super_class(self, exception): + return self.access_handle.is_super_class(exception) + + +class CompiledName(AbstractNameDefinition): + def __init__(self, evaluator, parent_context, name): + self._evaluator = evaluator + self.parent_context = parent_context + self.string_name = name + + def __repr__(self): + try: + name = self.parent_context.name # __name__ is not defined all the time + except AttributeError: + name = None + return '<%s: (%s).%s>' % (self.__class__.__name__, name, self.string_name) + + @property + def api_type(self): + return next(iter(self.infer())).api_type + + @underscore_memoization + def infer(self): + return ContextSet(create_from_name( + self._evaluator, self.parent_context, self.string_name + )) + + +class SignatureParamName(AbstractNameDefinition): + api_type = u'param' + + def __init__(self, compiled_obj, signature_param): + self.parent_context = compiled_obj.parent_context + self._signature_param = signature_param + + @property + def string_name(self): + return self._signature_param.name + + def get_kind(self): + return getattr(Parameter, self._signature_param.kind_name) + + def is_keyword_param(self): + return self._signature_param + + def infer(self): + p = self._signature_param + evaluator = self.parent_context.evaluator + contexts = ContextSet() + if p.has_default: + contexts = ContextSet(create_from_access_path(evaluator, p.default)) + if p.has_annotation: + annotation = create_from_access_path(evaluator, p.annotation) + contexts |= annotation.execute_evaluated() + return contexts + + +class UnresolvableParamName(AbstractNameDefinition): + api_type = u'param' + + def __init__(self, compiled_obj, name): + self.parent_context = compiled_obj.parent_context + self.string_name = name + + def get_kind(self): + return Parameter.POSITIONAL_ONLY + + def infer(self): + return ContextSet() + + +class CompiledContextName(ContextNameMixin, AbstractNameDefinition): + def __init__(self, context, name): + self.string_name = name + self._context = context + self.parent_context = context.parent_context + + +class EmptyCompiledName(AbstractNameDefinition): + """ + Accessing some names will raise an exception. To avoid not having any + completions, just give Jedi the option to return this object. It infers to + nothing. + """ + def __init__(self, evaluator, name): + self.parent_context = evaluator.builtins_module + self.string_name = name + + def infer(self): + return ContextSet() + + +class CompiledObjectFilter(AbstractFilter): + name_class = CompiledName + + def __init__(self, evaluator, compiled_object, is_instance=False): + self._evaluator = evaluator + self._compiled_object = compiled_object + self._is_instance = is_instance + + def get(self, name): + return self._get( + name, + lambda: self._compiled_object.access_handle.is_allowed_getattr(name), + lambda: self._compiled_object.access_handle.dir(), + check_has_attribute=True + ) + + def _get(self, name, allowed_getattr_callback, dir_callback, check_has_attribute=False): + """ + To remove quite a few access calls we introduced the callback here. + """ + has_attribute, is_descriptor = allowed_getattr_callback() + if check_has_attribute and not has_attribute: + return [] + + # Always use unicode objects in Python 2 from here. + name = force_unicode(name) + + if is_descriptor or not has_attribute: + return [self._get_cached_name(name, is_empty=True)] + + if self._is_instance and name not in dir_callback(): + return [] + return [self._get_cached_name(name)] + + @memoize_method + def _get_cached_name(self, name, is_empty=False): + if is_empty: + return EmptyCompiledName(self._evaluator, name) + else: + return self._create_name(name) + + def values(self): + from jedi.evaluate.compiled import builtin_from_name + names = [] + needs_type_completions, dir_infos = self._compiled_object.access_handle.get_dir_infos() + for name in dir_infos: + names += self._get( + name, + lambda: dir_infos[name], + lambda: dir_infos.keys(), + ) + + # ``dir`` doesn't include the type names. + if not self._is_instance and needs_type_completions: + for filter in builtin_from_name(self._evaluator, u'type').get_filters(): + names += filter.values() + return names + + def _create_name(self, name): + return self.name_class(self._evaluator, self._compiled_object, name) + + +docstr_defaults = { + 'floating point number': u'float', + 'character': u'str', + 'integer': u'int', + 'dictionary': u'dict', + 'string': u'str', +} + + +def _parse_function_doc(doc): + """ + Takes a function and returns the params and return value as a tuple. + This is nothing more than a docstring parser. + + TODO docstrings like utime(path, (atime, mtime)) and a(b [, b]) -> None + TODO docstrings like 'tuple of integers' + """ + doc = force_unicode(doc) + # parse round parentheses: def func(a, (b,c)) + try: + count = 0 + start = doc.index('(') + for i, s in enumerate(doc[start:]): + if s == '(': + count += 1 + elif s == ')': + count -= 1 + if count == 0: + end = start + i + break + param_str = doc[start + 1:end] + except (ValueError, UnboundLocalError): + # ValueError for doc.index + # UnboundLocalError for undefined end in last line + debug.dbg('no brackets found - no param') + end = 0 + param_str = u'' + else: + # remove square brackets, that show an optional param ( = None) + def change_options(m): + args = m.group(1).split(',') + for i, a in enumerate(args): + if a and '=' not in a: + args[i] += '=None' + return ','.join(args) + + while True: + param_str, changes = re.subn(r' ?\[([^\[\]]+)\]', + change_options, param_str) + if changes == 0: + break + param_str = param_str.replace('-', '_') # see: isinstance.__doc__ + + # parse return value + r = re.search(u'-[>-]* ', doc[end:end + 7]) + if r is None: + ret = u'' + else: + index = end + r.end() + # get result type, which can contain newlines + pattern = re.compile(r'(,\n|[^\n-])+') + ret_str = pattern.match(doc, index).group(0).strip() + # New object -> object() + ret_str = re.sub(r'[nN]ew (.*)', r'\1()', ret_str) + + ret = docstr_defaults.get(ret_str, ret_str) + + return param_str, ret + + +def create_from_name(evaluator, compiled_object, name): + faked = None + try: + faked = fake.get_faked_with_parent_context(compiled_object, name) + except fake.FakeDoesNotExist: + pass + + access = compiled_object.access_handle.getattr(name, default=None) + return create_cached_compiled_object( + evaluator, access, parent_context=compiled_object, faked=faked + ) + + +def _normalize_create_args(func): + """The cache doesn't care about keyword vs. normal args.""" + def wrapper(evaluator, obj, parent_context=None, faked=None): + return func(evaluator, obj, parent_context, faked) + return wrapper + + +def create_from_access_path(evaluator, access_path): + parent_context = None + for name, access in access_path.accesses: + try: + if parent_context is None: + faked = fake.get_faked_module(evaluator, access_path.accesses[0][0]) + else: + faked = fake.get_faked_with_parent_context(parent_context, name) + except fake.FakeDoesNotExist: + faked = None + + parent_context = create_cached_compiled_object(evaluator, access, parent_context, faked) + return parent_context + + +@_normalize_create_args +@evaluator_function_cache() +def create_cached_compiled_object(evaluator, access_handle, parent_context, faked): + return CompiledObject(evaluator, access_handle, parent_context, faked) diff --git a/pythonFiles/jedi/evaluate/compiled/fake.py b/pythonFiles/jedi/evaluate/compiled/fake.py index 60dbefe4acac..ac43ea286b48 100644 --- a/pythonFiles/jedi/evaluate/compiled/fake.py +++ b/pythonFiles/jedi/evaluate/compiled/fake.py @@ -5,73 +5,59 @@ """ import os -import inspect -import types from itertools import chain -from parso.python import tree +from jedi._compatibility import unicode -from jedi._compatibility import is_py3, builtins, unicode, is_py34 +fake_modules = {} -modules = {} +def _get_path_dict(): + path = os.path.dirname(os.path.abspath(__file__)) + base_path = os.path.join(path, 'fake') + dct = {} + for file_name in os.listdir(base_path): + if file_name.endswith('.pym'): + dct[file_name[:-4]] = os.path.join(base_path, file_name) + return dct -MethodDescriptorType = type(str.replace) -# These are not considered classes and access is granted even though they have -# a __class__ attribute. -NOT_CLASS_TYPES = ( - types.BuiltinFunctionType, - types.CodeType, - types.FrameType, - types.FunctionType, - types.GeneratorType, - types.GetSetDescriptorType, - types.LambdaType, - types.MemberDescriptorType, - types.MethodType, - types.ModuleType, - types.TracebackType, - MethodDescriptorType -) -if is_py3: - NOT_CLASS_TYPES += ( - types.MappingProxyType, - types.SimpleNamespace - ) - if is_py34: - NOT_CLASS_TYPES += (types.DynamicClassAttribute,) +_path_dict = _get_path_dict() class FakeDoesNotExist(Exception): pass -def _load_faked_module(grammar, module): - module_name = module.__name__ - if module_name == '__builtin__' and not is_py3: - module_name = 'builtins' +def _load_faked_module(evaluator, module_name): + try: + return fake_modules[module_name] + except KeyError: + pass + + check_module_name = module_name + if module_name == '__builtin__' and evaluator.environment.version_info.major == 2: + check_module_name = 'builtins' try: - return modules[module_name] + path = _path_dict[check_module_name] except KeyError: - path = os.path.dirname(os.path.abspath(__file__)) - try: - with open(os.path.join(path, 'fake', module_name) + '.pym') as f: - source = f.read() - except IOError: - modules[module_name] = None - return - modules[module_name] = m = grammar.parse(unicode(source)) - - if module_name == 'builtins' and not is_py3: - # There are two implementations of `open` for either python 2/3. - # -> Rename the python2 version (`look at fake/builtins.pym`). - open_func = _search_scope(m, 'open') - open_func.children[1].value = 'open_python3' - open_func = _search_scope(m, 'open_python2') - open_func.children[1].value = 'open' - return m + fake_modules[module_name] = None + return + + with open(path) as f: + source = f.read() + + fake_modules[module_name] = m = evaluator.latest_grammar.parse(unicode(source)) + + if check_module_name != module_name: + # There are two implementations of `open` for either python 2/3. + # -> Rename the python2 version (`look at fake/builtins.pym`). + open_func = _search_scope(m, 'open') + open_func.children[1].value = 'open_python3' + open_func = _search_scope(m, 'open_python2') + open_func.children[1].value = 'open' + return m def _search_scope(scope, obj_name): @@ -80,134 +66,17 @@ def _search_scope(scope, obj_name): return s -def get_module(obj): - if inspect.ismodule(obj): - return obj - try: - obj = obj.__objclass__ - except AttributeError: - pass - - try: - imp_plz = obj.__module__ - except AttributeError: - # Unfortunately in some cases like `int` there's no __module__ - return builtins - else: - if imp_plz is None: - # Happens for example in `(_ for _ in []).send.__module__`. - return builtins - else: - try: - return __import__(imp_plz) - except ImportError: - # __module__ can be something arbitrary that doesn't exist. - return builtins - - -def _faked(grammar, module, obj, name): - # Crazy underscore actions to try to escape all the internal madness. - if module is None: - module = get_module(obj) - - faked_mod = _load_faked_module(grammar, module) - if faked_mod is None: - return None, None - - # Having the module as a `parser.python.tree.Module`, we need to scan - # for methods. - if name is None: - if inspect.isbuiltin(obj) or inspect.isclass(obj): - return _search_scope(faked_mod, obj.__name__), faked_mod - elif not inspect.isclass(obj): - # object is a method or descriptor - try: - objclass = obj.__objclass__ - except AttributeError: - return None, None - else: - cls = _search_scope(faked_mod, objclass.__name__) - if cls is None: - return None, None - return _search_scope(cls, obj.__name__), faked_mod - else: - if obj is module: - return _search_scope(faked_mod, name), faked_mod - else: - try: - cls_name = obj.__name__ - except AttributeError: - return None, None - cls = _search_scope(faked_mod, cls_name) - if cls is None: - return None, None - return _search_scope(cls, name), faked_mod - return None, None - - -def memoize_faked(obj): - """ - A typical memoize function that ignores issues with non hashable results. - """ - cache = obj.cache = {} - - def memoizer(*args, **kwargs): - key = (obj, args, frozenset(kwargs.items())) - try: - result = cache[key] - except (TypeError, ValueError): - return obj(*args, **kwargs) - except KeyError: - result = obj(*args, **kwargs) - if result is not None: - cache[key] = obj(*args, **kwargs) - return result - else: - return result - return memoizer - - -@memoize_faked -def _get_faked(grammar, module, obj, name=None): - result, fake_module = _faked(grammar, module, obj, name) - if result is None: - # We're not interested in classes. What we want is functions. - raise FakeDoesNotExist - elif result.type == 'classdef': - return result, fake_module - else: - # Set the docstr which was previously not set (faked modules don't - # contain it). - assert result.type == 'funcdef' - doc = '"""%s"""' % obj.__doc__ # TODO need escapes. - suite = result.children[-1] - string = tree.String(doc, (0, 0), '') - new_line = tree.Newline('\n', (0, 0)) - docstr_node = tree.PythonNode('simple_stmt', [string, new_line]) - suite.children.insert(1, docstr_node) - return result, fake_module - - -def get_faked(evaluator, module, obj, name=None, parent_context=None): - if parent_context and parent_context.tree_node is not None: +def get_faked_with_parent_context(parent_context, name): + if parent_context.tree_node is not None: # Try to search in already clearly defined stuff. found = _search_scope(parent_context.tree_node, name) if found is not None: return found - else: - raise FakeDoesNotExist + raise FakeDoesNotExist - faked, fake_module = _get_faked(evaluator.latest_grammar, module and module.obj, obj, name) - if module is not None: - module.get_used_names = fake_module.get_used_names - return faked - -def is_class_instance(obj): - """Like inspect.* methods.""" - try: - cls = obj.__class__ - except AttributeError: - return False - else: - return cls != type and not issubclass(cls, NOT_CLASS_TYPES) +def get_faked_module(evaluator, string_name): + module = _load_faked_module(evaluator, string_name) + if module is None: + raise FakeDoesNotExist + return module diff --git a/pythonFiles/jedi/evaluate/compiled/fake/builtins.pym b/pythonFiles/jedi/evaluate/compiled/fake/builtins.pym index 1225929c2cb7..46ec619fb420 100644 --- a/pythonFiles/jedi/evaluate/compiled/fake/builtins.pym +++ b/pythonFiles/jedi/evaluate/compiled/fake/builtins.pym @@ -201,10 +201,13 @@ class dict(): # has a strange docstr pass + def __getitem__(self, obj): + return self.__elements[obj] + def get(self, k, d=None): # TODO implement try: - #return self.__elements[k] + return self.__elements[k] pass except KeyError: return d diff --git a/pythonFiles/jedi/evaluate/compiled/getattr_static.py b/pythonFiles/jedi/evaluate/compiled/getattr_static.py index 9f8cd8a838cf..946ac09b6b06 100644 --- a/pythonFiles/jedi/evaluate/compiled/getattr_static.py +++ b/pythonFiles/jedi/evaluate/compiled/getattr_static.py @@ -10,6 +10,7 @@ _sentinel = object() + def _check_instance(obj, attr): instance_dict = {} try: @@ -28,6 +29,7 @@ def _check_class(klass, attr): pass return _sentinel + def _is_type(obj): try: _static_getmro(obj) @@ -87,7 +89,7 @@ class Foo(object): pass return getattr(klass, '__dict__', _sentinel) return _shadowed_dict_newstyle(klass) - class _OldStyleClass(): + class _OldStyleClass: pass _oldstyle_instance_type = type(_OldStyleClass()) @@ -122,7 +124,7 @@ def _safe_hasattr(obj, name): def _safe_is_data_descriptor(obj): - return (_safe_hasattr(obj, '__set__') or _safe_hasattr(obj, '__delete__')) + return _safe_hasattr(obj, '__set__') or _safe_hasattr(obj, '__delete__') def getattr_static(obj, attr, default=_sentinel): @@ -143,8 +145,7 @@ def getattr_static(obj, attr, default=_sentinel): if not _is_type(obj): klass = _get_type(obj) dict_attr = _shadowed_dict(klass) - if (dict_attr is _sentinel or - type(dict_attr) is types.MemberDescriptorType): + if (dict_attr is _sentinel or type(dict_attr) is types.MemberDescriptorType): instance_result = _check_instance(obj, attr) else: klass = obj diff --git a/pythonFiles/jedi/evaluate/compiled/mixed.py b/pythonFiles/jedi/evaluate/compiled/mixed.py index ac0f6dd614db..b8003502e3d7 100644 --- a/pythonFiles/jedi/evaluate/compiled/mixed.py +++ b/pythonFiles/jedi/evaluate/compiled/mixed.py @@ -5,6 +5,8 @@ import inspect import os +from jedi.parser_utils import get_cached_code_lines + from jedi import settings from jedi.evaluate import compiled from jedi.cache import underscore_memoization @@ -13,6 +15,8 @@ from jedi.evaluate.context import ModuleContext from jedi.evaluate.cache import evaluator_function_cache from jedi.evaluate.compiled.getattr_static import getattr_static +from jedi.evaluate.compiled.access import compiled_objects_cache +from jedi.evaluate.compiled.context import create_cached_compiled_object class MixedObject(object): @@ -37,7 +41,7 @@ def __init__(self, evaluator, parent_context, compiled_object, tree_context): self.parent_context = parent_context self.compiled_object = compiled_object self._context = tree_context - self.obj = compiled_object.obj + self.access_handle = compiled_object.access_handle # We have to overwrite everything that has to do with trailers, name # lookups and filters to make it possible to route name lookups towards @@ -49,7 +53,7 @@ def get_filters(self, *args, **kwargs): yield MixedObjectFilter(self.evaluator, self) def __repr__(self): - return '<%s: %s>' % (type(self).__name__, repr(self.obj)) + return '<%s: %s>' % (type(self).__name__, self.access_handle.get_repr()) def __getattr__(self, name): return getattr(self._context, name) @@ -64,7 +68,7 @@ def start_pos(self): contexts = list(self.infer()) if not contexts: # This means a start_pos that doesn't exist (compiled objects). - return (0, 0) + return 0, 0 return contexts[0].name.start_pos @start_pos.setter @@ -74,17 +78,11 @@ def start_pos(self, value): @underscore_memoization def infer(self): - obj = self.parent_context.obj - try: - # TODO use logic from compiled.CompiledObjectFilter - obj = getattr(obj, self.string_name) - except AttributeError: - # Happens e.g. in properties of - # PyQt4.QtGui.QStyleOptionComboBox.currentText - # -> just set it to None - obj = None + access_handle = self.parent_context.access_handle + # TODO use logic from compiled.CompiledObjectFilter + access_handle = access_handle.getattr(self.string_name, default=None) return ContextSet( - _create(self._evaluator, obj, parent_context=self.parent_context) + _create(self._evaluator, access_handle, parent_context=self.parent_context) ) @property @@ -105,17 +103,17 @@ def __init__(self, evaluator, mixed_object, is_instance=False): @evaluator_function_cache() -def _load_module(evaluator, path, python_object): - module = evaluator.grammar.parse( +def _load_module(evaluator, path): + module_node = evaluator.grammar.parse( path=path, cache=True, diff_cache=True, cache_path=settings.cache_directory ).get_root_node() - python_module = inspect.getmodule(python_object) - - evaluator.modules[python_module.__name__] = module - return module + # python_module = inspect.getmodule(python_object) + # TODO we should actually make something like this possible. + #evaluator.modules[python_module.__name__] = module_node + return module_node def _get_object_to_check(python_object): @@ -135,39 +133,43 @@ def _get_object_to_check(python_object): raise TypeError # Prevents computation of `repr` within inspect. -def find_syntax_node_name(evaluator, python_object): +def _find_syntax_node_name(evaluator, access_handle): + # TODO accessing this is bad, but it probably doesn't matter that much, + # because we're working with interpreteters only here. + python_object = access_handle.access._obj try: python_object = _get_object_to_check(python_object) path = inspect.getsourcefile(python_object) except TypeError: # The type might not be known (e.g. class_with_dict.__weakref__) - return None, None + return None if path is None or not os.path.exists(path): # The path might not exist or be e.g. . - return None, None + return None - module = _load_module(evaluator, path, python_object) + module_node = _load_module(evaluator, path) if inspect.ismodule(python_object): # We don't need to check names for modules, because there's not really # a way to write a module in a module in Python (and also __name__ can # be something like ``email.utils``). - return module, path + code_lines = get_cached_code_lines(evaluator.grammar, path) + return module_node, module_node, path, code_lines try: name_str = python_object.__name__ except AttributeError: # Stuff like python_function.__code__. - return None, None + return None if name_str == '': - return None, None # It's too hard to find lambdas. + return None # It's too hard to find lambdas. # Doesn't always work (e.g. os.stat_result) try: - names = module.get_used_names()[name_str] + names = module_node.get_used_names()[name_str] except KeyError: - return None, None + return None names = [n for n in names if n.is_definition()] try: @@ -184,33 +186,40 @@ def find_syntax_node_name(evaluator, python_object): # There's a chance that the object is not available anymore, because # the code has changed in the background. if line_names: - return line_names[-1].parent, path + names = line_names + code_lines = get_cached_code_lines(evaluator.grammar, path) # It's really hard to actually get the right definition, here as a last # resort we just return the last one. This chance might lead to odd # completions at some points but will lead to mostly correct type # inference, because people tend to define a public name in a module only # once. - return names[-1].parent, path + return module_node, names[-1].parent, path, code_lines -@compiled.compiled_objects_cache('mixed_cache') -def _create(evaluator, obj, parent_context=None, *args): - tree_node, path = find_syntax_node_name(evaluator, obj) +@compiled_objects_cache('mixed_cache') +def _create(evaluator, access_handle, parent_context, *args): + compiled_object = create_cached_compiled_object( + evaluator, access_handle, parent_context=parent_context.compiled_object) - compiled_object = compiled.create( - evaluator, obj, parent_context=parent_context.compiled_object) - if tree_node is None: + result = _find_syntax_node_name(evaluator, access_handle) + if result is None: return compiled_object - module_node = tree_node.get_root_node() + module_node, tree_node, path, code_lines = result + if parent_context.tree_node.get_root_node() == module_node: module_context = parent_context.get_root_context() else: - module_context = ModuleContext(evaluator, module_node, path=path) + module_context = ModuleContext( + evaluator, module_node, + path=path, + code_lines=code_lines, + ) # TODO this __name__ is probably wrong. name = compiled_object.get_root_context().py__name__() - imports.add_module(evaluator, name, module_context) + if name is not None: + imports.add_module_to_cache(evaluator, name, module_context) tree_context = module_context.create_context( tree_node, @@ -218,7 +227,7 @@ def _create(evaluator, obj, parent_context=None, *args): node_is_object=True ) if tree_node.type == 'classdef': - if not inspect.isclass(obj): + if not access_handle.is_class(): # Is an instance, not a class. tree_context, = tree_context.execute_evaluated() @@ -228,4 +237,3 @@ def _create(evaluator, obj, parent_context=None, *args): compiled_object, tree_context=tree_context ) - diff --git a/pythonFiles/jedi/evaluate/compiled/subprocess/__init__.py b/pythonFiles/jedi/evaluate/compiled/subprocess/__init__.py new file mode 100644 index 000000000000..9cc8704a457e --- /dev/null +++ b/pythonFiles/jedi/evaluate/compiled/subprocess/__init__.py @@ -0,0 +1,340 @@ +""" +Makes it possible to do the compiled analysis in a subprocess. This has two +goals: + +1. Making it safer - Segfaults and RuntimeErrors as well as stdout/stderr can + be ignored and dealt with. +2. Make it possible to handle different Python versions as well as virtualenvs. +""" + +import os +import sys +import subprocess +import socket +import errno +import weakref +import traceback +from functools import partial + +from jedi._compatibility import queue, is_py3, force_unicode, \ + pickle_dump, pickle_load, GeneralizedPopen +from jedi.cache import memoize_method +from jedi.evaluate.compiled.subprocess import functions +from jedi.evaluate.compiled.access import DirectObjectAccess, AccessPath, \ + SignatureParam +from jedi.api.exceptions import InternalError + +_subprocesses = {} + +_MAIN_PATH = os.path.join(os.path.dirname(__file__), '__main__.py') + + +def get_subprocess(executable): + try: + return _subprocesses[executable] + except KeyError: + sub = _subprocesses[executable] = _CompiledSubprocess(executable) + return sub + + +def _get_function(name): + return getattr(functions, name) + + +class _EvaluatorProcess(object): + def __init__(self, evaluator): + self._evaluator_weakref = weakref.ref(evaluator) + self._evaluator_id = id(evaluator) + self._handles = {} + + def get_or_create_access_handle(self, obj): + id_ = id(obj) + try: + return self.get_access_handle(id_) + except KeyError: + access = DirectObjectAccess(self._evaluator_weakref(), obj) + handle = AccessHandle(self, access, id_) + self.set_access_handle(handle) + return handle + + def get_access_handle(self, id_): + return self._handles[id_] + + def set_access_handle(self, handle): + self._handles[handle.id] = handle + + +class EvaluatorSameProcess(_EvaluatorProcess): + """ + Basically just an easy access to functions.py. It has the same API + as EvaluatorSubprocess and does the same thing without using a subprocess. + This is necessary for the Interpreter process. + """ + def __getattr__(self, name): + return partial(_get_function(name), self._evaluator_weakref()) + + +class EvaluatorSubprocess(_EvaluatorProcess): + def __init__(self, evaluator, compiled_subprocess): + super(EvaluatorSubprocess, self).__init__(evaluator) + self._used = False + self._compiled_subprocess = compiled_subprocess + + def __getattr__(self, name): + func = _get_function(name) + + def wrapper(*args, **kwargs): + self._used = True + + result = self._compiled_subprocess.run( + self._evaluator_weakref(), + func, + args=args, + kwargs=kwargs, + ) + # IMO it should be possible to create a hook in pickle.load to + # mess with the loaded objects. However it's extremely complicated + # to work around this so just do it with this call. ~ dave + return self._convert_access_handles(result) + + return wrapper + + def _convert_access_handles(self, obj): + if isinstance(obj, SignatureParam): + return SignatureParam(*self._convert_access_handles(tuple(obj))) + elif isinstance(obj, tuple): + return tuple(self._convert_access_handles(o) for o in obj) + elif isinstance(obj, list): + return [self._convert_access_handles(o) for o in obj] + elif isinstance(obj, AccessHandle): + try: + # Rewrite the access handle to one we're already having. + obj = self.get_access_handle(obj.id) + except KeyError: + obj.add_subprocess(self) + self.set_access_handle(obj) + elif isinstance(obj, AccessPath): + return AccessPath(self._convert_access_handles(obj.accesses)) + return obj + + def __del__(self): + if self._used: + self._compiled_subprocess.delete_evaluator(self._evaluator_id) + + +class _CompiledSubprocess(object): + _crashed = False + + def __init__(self, executable): + self._executable = executable + self._evaluator_deletion_queue = queue.deque() + + @property + @memoize_method + def _process(self): + parso_path = sys.modules['parso'].__file__ + args = ( + self._executable, + _MAIN_PATH, + os.path.dirname(os.path.dirname(parso_path)) + ) + return GeneralizedPopen( + args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + ) + + def run(self, evaluator, function, args=(), kwargs={}): + # Delete old evaluators. + while True: + try: + evaluator_id = self._evaluator_deletion_queue.pop() + except IndexError: + break + else: + self._send(evaluator_id, None) + + assert callable(function) + return self._send(id(evaluator), function, args, kwargs) + + def get_sys_path(self): + return self._send(None, functions.get_sys_path, (), {}) + + def kill(self): + self._crashed = True + try: + subprocess = _subprocesses[self._executable] + except KeyError: + # Fine it was already removed from the cache. + pass + else: + # In the `!=` case there is already a new subprocess in place + # and we don't need to do anything here anymore. + if subprocess == self: + del _subprocesses[self._executable] + + self._process.kill() + self._process.wait() + + def _send(self, evaluator_id, function, args=(), kwargs={}): + if self._crashed: + raise InternalError("The subprocess %s has crashed." % self._executable) + + if not is_py3: + # Python 2 compatibility + kwargs = {force_unicode(key): value for key, value in kwargs.items()} + + data = evaluator_id, function, args, kwargs + try: + pickle_dump(data, self._process.stdin) + except (socket.error, IOError) as e: + # Once Python2 will be removed we can just use `BrokenPipeError`. + # Also, somehow in windows it returns EINVAL instead of EPIPE if + # the subprocess dies. + if e.errno not in (errno.EPIPE, errno.EINVAL): + # Not a broken pipe + raise + self.kill() + raise InternalError("The subprocess %s was killed. Maybe out of memory?" + % self._executable) + + try: + is_exception, traceback, result = pickle_load(self._process.stdout) + except EOFError: + self.kill() + raise InternalError("The subprocess %s has crashed." % self._executable) + + if is_exception: + # Replace the attribute error message with a the traceback. It's + # way more informative. + result.args = (traceback,) + raise result + return result + + def delete_evaluator(self, evaluator_id): + """ + Currently we are not deleting evalutors instantly. They only get + deleted once the subprocess is used again. It would probably a better + solution to move all of this into a thread. However, the memory usage + of a single evaluator shouldn't be that high. + """ + # With an argument - the evaluator gets deleted. + self._evaluator_deletion_queue.append(evaluator_id) + + +class Listener(object): + def __init__(self): + self._evaluators = {} + # TODO refactor so we don't need to process anymore just handle + # controlling. + self._process = _EvaluatorProcess(Listener) + + def _get_evaluator(self, function, evaluator_id): + from jedi.evaluate import Evaluator + + try: + evaluator = self._evaluators[evaluator_id] + except KeyError: + from jedi.api.environment import InterpreterEnvironment + evaluator = Evaluator( + # The project is not actually needed. Nothing should need to + # access it. + project=None, + environment=InterpreterEnvironment() + ) + self._evaluators[evaluator_id] = evaluator + return evaluator + + def _run(self, evaluator_id, function, args, kwargs): + if evaluator_id is None: + return function(*args, **kwargs) + elif function is None: + del self._evaluators[evaluator_id] + else: + evaluator = self._get_evaluator(function, evaluator_id) + + # Exchange all handles + args = list(args) + for i, arg in enumerate(args): + if isinstance(arg, AccessHandle): + args[i] = evaluator.compiled_subprocess.get_access_handle(arg.id) + for key, value in kwargs.items(): + if isinstance(value, AccessHandle): + kwargs[key] = evaluator.compiled_subprocess.get_access_handle(value.id) + + return function(evaluator, *args, **kwargs) + + def listen(self): + stdout = sys.stdout + # Mute stdout/stderr. Nobody should actually be able to write to those, + # because stdout is used for IPC and stderr will just be annoying if it + # leaks (on module imports). + sys.stdout = open(os.devnull, 'w') + sys.stderr = open(os.devnull, 'w') + stdin = sys.stdin + if sys.version_info[0] > 2: + stdout = stdout.buffer + stdin = stdin.buffer + + while True: + try: + payload = pickle_load(stdin) + except EOFError: + # It looks like the parent process closed. Don't make a big fuss + # here and just exit. + exit(1) + try: + result = False, None, self._run(*payload) + except Exception as e: + result = True, traceback.format_exc(), e + + pickle_dump(result, file=stdout) + + +class AccessHandle(object): + def __init__(self, subprocess, access, id_): + self.access = access + self._subprocess = subprocess + self.id = id_ + + def add_subprocess(self, subprocess): + self._subprocess = subprocess + + def __repr__(self): + try: + detail = self.access + except AttributeError: + detail = '#' + str(self.id) + return '<%s of %s>' % (self.__class__.__name__, detail) + + def __getstate__(self): + return self.id + + def __setstate__(self, state): + self.id = state + + def __getattr__(self, name): + if name in ('id', 'access') or name.startswith('_'): + raise AttributeError("Something went wrong with unpickling") + + #if not is_py3: print >> sys.stderr, name + #print('getattr', name, file=sys.stderr) + return partial(self._workaround, force_unicode(name)) + + def _workaround(self, name, *args, **kwargs): + """ + TODO Currently we're passing slice objects around. This should not + happen. They are also the only unhashable objects that we're passing + around. + """ + if args and isinstance(args[0], slice): + return self._subprocess.get_compiled_method_return(self.id, name, *args, **kwargs) + return self._cached_results(name, *args, **kwargs) + + @memoize_method + def _cached_results(self, name, *args, **kwargs): + #if type(self._subprocess) == EvaluatorSubprocess: + #print(name, args, kwargs, + #self._subprocess.get_compiled_method_return(self.id, name, *args, **kwargs) + #) + return self._subprocess.get_compiled_method_return(self.id, name, *args, **kwargs) diff --git a/pythonFiles/jedi/evaluate/compiled/subprocess/__main__.py b/pythonFiles/jedi/evaluate/compiled/subprocess/__main__.py new file mode 100644 index 000000000000..8a0148ce5040 --- /dev/null +++ b/pythonFiles/jedi/evaluate/compiled/subprocess/__main__.py @@ -0,0 +1,49 @@ +import sys +import os + + +def _get_paths(): + # Get the path to jedi. + _d = os.path.dirname + _jedi_path = _d(_d(_d(_d(_d(__file__))))) + _parso_path = sys.argv[1] + # The paths are the directory that jedi and parso lie in. + return {'jedi': _jedi_path, 'parso': _parso_path} + + +# Remove the first entry, because it's simply a directory entry that equals +# this directory. +del sys.path[0] + +if sys.version_info > (3, 4): + from importlib.machinery import PathFinder + + class _ExactImporter(object): + def __init__(self, path_dct): + self._path_dct = path_dct + + def find_module(self, fullname, path=None): + if path is None and fullname in self._path_dct: + p = self._path_dct[fullname] + loader = PathFinder.find_module(fullname, path=[p]) + return loader + return None + + # Try to import jedi/parso. + sys.meta_path.insert(0, _ExactImporter(_get_paths())) + from jedi.evaluate.compiled import subprocess # NOQA + sys.meta_path.pop(0) +else: + import imp + + def load(name): + paths = list(_get_paths().values()) + fp, pathname, description = imp.find_module(name, paths) + return imp.load_module(name, fp, pathname, description) + + load('parso') + load('jedi') + from jedi.evaluate.compiled import subprocess # NOQA + +# And finally start the client. +subprocess.Listener().listen() diff --git a/pythonFiles/jedi/evaluate/compiled/subprocess/functions.py b/pythonFiles/jedi/evaluate/compiled/subprocess/functions.py new file mode 100644 index 000000000000..593ff91e8caf --- /dev/null +++ b/pythonFiles/jedi/evaluate/compiled/subprocess/functions.py @@ -0,0 +1,113 @@ +import sys +import os + +from jedi._compatibility import find_module, cast_path, force_unicode, \ + iter_modules, all_suffixes, print_to_stderr +from jedi.evaluate.compiled import access +from jedi import parser_utils + + +def get_sys_path(): + return list(map(cast_path, sys.path)) + + +def load_module(evaluator, **kwargs): + return access.load_module(evaluator, **kwargs) + + +def get_compiled_method_return(evaluator, id, attribute, *args, **kwargs): + handle = evaluator.compiled_subprocess.get_access_handle(id) + return getattr(handle.access, attribute)(*args, **kwargs) + + +def get_special_object(evaluator, identifier): + return access.get_special_object(evaluator, identifier) + + +def create_simple_object(evaluator, obj): + return access.create_access_path(evaluator, obj) + + +def get_module_info(evaluator, sys_path=None, full_name=None, **kwargs): + if sys_path is not None: + sys.path, temp = sys_path, sys.path + try: + module_file, module_path, is_pkg = find_module(full_name=full_name, **kwargs) + except ImportError: + return None, None, None + finally: + if sys_path is not None: + sys.path = temp + + code = None + if is_pkg: + # In this case, we don't have a file yet. Search for the + # __init__ file. + if module_path.endswith(('.zip', '.egg')): + code = module_file.loader.get_source(full_name) + else: + module_path = _get_init_path(module_path) + elif module_file: + if module_path.endswith(('.zip', '.egg')): + # Unfortunately we are reading unicode here already, not byes. + # It seems however hard to get bytes, because the zip importer + # logic just unpacks the zip file and returns a file descriptor + # that we cannot as easily access. Therefore we just read it as + # a string. + code = module_file.read() + else: + # Read the code with a binary file, because the binary file + # might not be proper unicode. This is handled by the parser + # wrapper. + with open(module_path, 'rb') as f: + code = f.read() + + module_file.close() + + return code, cast_path(module_path), is_pkg + + +def list_module_names(evaluator, search_path): + return [ + name + for module_loader, name, is_pkg in iter_modules(search_path) + ] + + +def get_builtin_module_names(evaluator): + return list(map(force_unicode, sys.builtin_module_names)) + + +def _test_raise_error(evaluator, exception_type): + """ + Raise an error to simulate certain problems for unit tests. + """ + raise exception_type + + +def _test_print(evaluator, stderr=None, stdout=None): + """ + Force some prints in the subprocesses. This exists for unit tests. + """ + if stderr is not None: + print_to_stderr(stderr) + sys.stderr.flush() + if stdout is not None: + print(stdout) + sys.stdout.flush() + + +def _get_init_path(directory_path): + """ + The __init__ file can be searched in a directory. If found return it, else + None. + """ + for suffix in all_suffixes(): + path = os.path.join(directory_path, '__init__' + suffix) + if os.path.exists(path): + return path + return None + + +def safe_literal_eval(evaluator, value): + return parser_utils.safe_literal_eval(value) diff --git a/pythonFiles/jedi/evaluate/context/asynchronous.py b/pythonFiles/jedi/evaluate/context/asynchronous.py new file mode 100644 index 000000000000..51e59a48261e --- /dev/null +++ b/pythonFiles/jedi/evaluate/context/asynchronous.py @@ -0,0 +1,38 @@ +from jedi.evaluate.filters import publish_method, BuiltinOverwrite +from jedi.evaluate.base_context import ContextSet + + +class AsyncBase(BuiltinOverwrite): + def __init__(self, evaluator, func_execution_context): + super(AsyncBase, self).__init__(evaluator) + self.func_execution_context = func_execution_context + + @property + def name(self): + return self.get_object().name + + def __repr__(self): + return "<%s of %s>" % (type(self).__name__, self.func_execution_context) + + +class Coroutine(AsyncBase): + special_object_identifier = u'COROUTINE' + + @publish_method('__await__') + def _await(self): + return ContextSet(CoroutineWrapper(self.evaluator, self.func_execution_context)) + + +class CoroutineWrapper(AsyncBase): + special_object_identifier = u'COROUTINE_WRAPPER' + + def py__stop_iteration_returns(self): + return self.func_execution_context.get_return_values() + + +class AsyncGenerator(AsyncBase): + """Handling of `yield` functions.""" + special_object_identifier = u'ASYNC_GENERATOR' + + def py__aiter__(self): + return self.func_execution_context.get_yield_lazy_contexts(is_async=True) diff --git a/pythonFiles/jedi/evaluate/context/function.py b/pythonFiles/jedi/evaluate/context/function.py index 0dba9c91d707..2bb3a9b88bc3 100644 --- a/pythonFiles/jedi/evaluate/context/function.py +++ b/pythonFiles/jedi/evaluate/context/function.py @@ -17,17 +17,20 @@ from jedi.evaluate.lazy_context import LazyKnownContexts, LazyKnownContext, \ LazyTreeContext from jedi.evaluate.context import iterable +from jedi.evaluate.context import asynchronous from jedi import parser_utils from jedi.evaluate.parser_cache import get_yield_exprs class LambdaName(AbstractNameDefinition): string_name = '' + api_type = u'function' def __init__(self, lambda_context): self._lambda_context = lambda_context self.parent_context = lambda_context.parent_context + @property def start_pos(self): return self._lambda_context.tree_node.start_pos @@ -39,7 +42,7 @@ class FunctionContext(use_metaclass(CachedMetaClass, TreeContext)): """ Needed because of decorators. Decorators are evaluated here. """ - api_type = 'function' + api_type = u'function' def __init__(self, evaluator, parent_context, funcdef): """ This should not be called directly """ @@ -63,11 +66,23 @@ def infer_function_execution(self, function_execution): """ Created to be used by inheritance. """ - yield_exprs = get_yield_exprs(self.evaluator, self.tree_node) - if yield_exprs: - return ContextSet(iterable.Generator(self.evaluator, function_execution)) + is_coroutine = self.tree_node.parent.type == 'async_stmt' + is_generator = bool(get_yield_exprs(self.evaluator, self.tree_node)) + + if is_coroutine: + if is_generator: + if self.evaluator.environment.version_info < (3, 6): + return NO_CONTEXTS + return ContextSet(asynchronous.AsyncGenerator(self.evaluator, function_execution)) + else: + if self.evaluator.environment.version_info < (3, 5): + return NO_CONTEXTS + return ContextSet(asynchronous.Coroutine(self.evaluator, function_execution)) else: - return function_execution.get_return_values() + if is_generator: + return ContextSet(iterable.Generator(self.evaluator, function_execution)) + else: + return function_execution.get_return_values() def get_function_execution(self, arguments=None): if arguments is None: @@ -83,9 +98,9 @@ def py__class__(self): # This differentiation is only necessary for Python2. Python3 does not # use a different method class. if isinstance(parser_utils.get_parent_scope(self.tree_node), tree.Class): - name = 'METHOD_CLASS' + name = u'METHOD_CLASS' else: - name = 'FUNCTION_CLASS' + name = u'FUNCTION_CLASS' return compiled.get_special_object(self.evaluator, name) @property @@ -122,7 +137,7 @@ def __init__(self, evaluator, parent_context, function_context, var_args): def get_return_values(self, check_yields=False): funcdef = self.tree_node if funcdef.type == 'lambdef': - return self.evaluator.eval_element(self, funcdef.children[-1]) + return self.eval_node(funcdef.children[-1]) if check_yields: context_set = NO_CONTEXTS @@ -140,13 +155,14 @@ def get_return_values(self, check_yields=False): if check_yields: context_set |= ContextSet.from_sets( lazy_context.infer() - for lazy_context in self._eval_yield(r) + for lazy_context in self._get_yield_lazy_context(r) ) else: try: children = r.children except AttributeError: - context_set |= ContextSet(compiled.create(self.evaluator, None)) + ctx = compiled.builtin_from_name(self.evaluator, u'None') + context_set |= ContextSet(ctx) else: context_set |= self.eval_node(children[1]) if check is flow_analysis.REACHABLE: @@ -154,10 +170,11 @@ def get_return_values(self, check_yields=False): break return context_set - def _eval_yield(self, yield_expr): + def _get_yield_lazy_context(self, yield_expr): if yield_expr.type == 'keyword': # `yield` just yields None. - yield LazyKnownContext(compiled.create(self.evaluator, None)) + ctx = compiled.builtin_from_name(self.evaluator, u'None') + yield LazyKnownContext(ctx) return node = yield_expr.children[1] @@ -169,7 +186,8 @@ def _eval_yield(self, yield_expr): yield LazyTreeContext(self, node) @recursion.execution_recursion_decorator(default=iter([])) - def get_yield_values(self): + def get_yield_lazy_contexts(self, is_async=False): + # TODO: if is_async, wrap yield statements in Awaitable/async_generator_asend for_parents = [(y, tree.search_ancestor(y, 'for_stmt', 'funcdef', 'while_stmt', 'if_stmt')) for y in get_yield_exprs(self.evaluator, self.tree_node)] @@ -202,7 +220,7 @@ def get_yield_values(self): if for_stmt is None: # No for_stmt, just normal yields. for yield_ in yields: - for result in self._eval_yield(yield_): + for result in self._get_yield_lazy_context(yield_): yield result else: input_node = for_stmt.get_testlist() @@ -213,7 +231,7 @@ def get_yield_values(self): dct = {str(for_stmt.children[1].value): lazy_context.infer()} with helpers.predefine_names(self, for_stmt, dct): for yield_in_same_for_stmt in yields: - for result in self._eval_yield(yield_in_same_for_stmt): + for result in self._get_yield_lazy_context(yield_in_same_for_stmt): yield result def get_filters(self, search_global, until_position=None, origin_scope=None): diff --git a/pythonFiles/jedi/evaluate/context/instance.py b/pythonFiles/jedi/evaluate/context/instance.py index 2c8d796c9c6d..def5e19a2da9 100644 --- a/pythonFiles/jedi/evaluate/context/instance.py +++ b/pythonFiles/jedi/evaluate/context/instance.py @@ -1,6 +1,5 @@ from abc import abstractproperty -from jedi._compatibility import is_py3 from jedi import debug from jedi.evaluate import compiled from jedi.evaluate import filters @@ -16,30 +15,34 @@ from jedi.parser_utils import get_parent_scope +class BaseInstanceFunctionExecution(FunctionExecutionContext): + def __init__(self, instance, *args, **kwargs): + self.instance = instance + super(BaseInstanceFunctionExecution, self).__init__( + instance.evaluator, *args, **kwargs) + -class InstanceFunctionExecution(FunctionExecutionContext): +class InstanceFunctionExecution(BaseInstanceFunctionExecution): def __init__(self, instance, parent_context, function_context, var_args): - self.instance = instance var_args = InstanceVarArgs(self, var_args) super(InstanceFunctionExecution, self).__init__( - instance.evaluator, parent_context, function_context, var_args) + instance, parent_context, function_context, var_args) -class AnonymousInstanceFunctionExecution(FunctionExecutionContext): +class AnonymousInstanceFunctionExecution(BaseInstanceFunctionExecution): function_execution_filter = filters.AnonymousInstanceFunctionExecutionFilter def __init__(self, instance, parent_context, function_context, var_args): - self.instance = instance super(AnonymousInstanceFunctionExecution, self).__init__( - instance.evaluator, parent_context, function_context, var_args) + instance, parent_context, function_context, var_args) class AbstractInstanceContext(Context): """ This class is used to evaluate instances. """ - api_type = 'instance' + api_type = u'instance' function_execution_cls = InstanceFunctionExecution def __init__(self, evaluator, parent_context, class_context, var_args): @@ -54,7 +57,7 @@ def is_class(self): @property def py__call__(self): - names = self.get_function_slot_names('__call__') + names = self.get_function_slot_names(u'__call__') if not names: # Means the Instance is not callable. raise AttributeError @@ -90,12 +93,12 @@ def execute_function_slots(self, names, *evaluated_args): def py__get__(self, obj): # Arguments in __get__ descriptors are obj, class. # `method` is the new parent of the array, don't know if that's good. - names = self.get_function_slot_names('__get__') + names = self.get_function_slot_names(u'__get__') if names: if isinstance(obj, AbstractInstanceContext): return self.execute_function_slots(names, obj, obj.class_context) else: - none_obj = compiled.create(self.evaluator, None) + none_obj = compiled.builtin_from_name(self.evaluator, u'None') return self.execute_function_slots(names, none_obj, obj) else: return ContextSet(self) @@ -104,14 +107,12 @@ def get_filters(self, search_global=None, until_position=None, origin_scope=None, include_self_names=True): if include_self_names: for cls in self.class_context.py__mro__(): - if isinstance(cls, compiled.CompiledObject): - if cls.tree_node is not None: - # In this case we're talking about a fake object, it - # doesn't make sense for normal compiled objects to - # search for self variables. - yield SelfNameFilter(self.evaluator, self, cls, origin_scope) - else: - yield SelfNameFilter(self.evaluator, self, cls, origin_scope) + if not isinstance(cls, compiled.CompiledObject) \ + or cls.tree_node is not None: + # In this case we're excluding compiled objects that are + # not fake objects. It doesn't make sense for normal + # compiled objects to search for self variables. + yield SelfAttributeFilter(self.evaluator, self, cls, origin_scope) for cls in self.class_context.py__mro__(): if isinstance(cls, compiled.CompiledObject): @@ -121,16 +122,16 @@ def get_filters(self, search_global=None, until_position=None, def py__getitem__(self, index): try: - names = self.get_function_slot_names('__getitem__') + names = self.get_function_slot_names(u'__getitem__') except KeyError: debug.warning('No __getitem__, cannot access the array.') return NO_CONTEXTS else: - index_obj = compiled.create(self.evaluator, index) + index_obj = compiled.create_simple_object(self.evaluator, index) return self.execute_function_slots(names, index_obj) def py__iter__(self): - iter_slot_names = self.get_function_slot_names('__iter__') + iter_slot_names = self.get_function_slot_names(u'__iter__') if not iter_slot_names: debug.warning('No __iter__ on %s.' % self) return @@ -138,7 +139,10 @@ def py__iter__(self): for generator in self.execute_function_slots(iter_slot_names): if isinstance(generator, AbstractInstanceContext): # `__next__` logic. - name = '__next__' if is_py3 else 'next' + if self.evaluator.environment.version_info.major == 2: + name = u'next' + else: + name = u'__next__' iter_slot_names = generator.get_function_slot_names(name) if iter_slot_names: yield LazyKnownContexts( @@ -166,8 +170,8 @@ def _create_init_execution(self, class_context, func_node): ) def create_init_executions(self): - for name in self.get_function_slot_names('__init__'): - if isinstance(name, LazyInstanceName): + for name in self.get_function_slot_names(u'__init__'): + if isinstance(name, SelfName): yield self._create_init_execution(name.class_context, name.tree_name.parent) @evaluator_method_cache() @@ -189,7 +193,7 @@ def create_instance_context(self, class_context, node): ) return bound_method.get_function_execution() elif scope.type == 'classdef': - class_context = ClassContext(self.evaluator, scope, parent_context) + class_context = ClassContext(self.evaluator, parent_context, scope) return class_context elif scope.type == 'comp_for': # Comprehensions currently don't have a special scope in Jedi. @@ -208,8 +212,10 @@ def __init__(self, *args, **kwargs): super(CompiledInstance, self).__init__(*args, **kwargs) # I don't think that dynamic append lookups should happen here. That # sounds more like something that should go to py__iter__. + self._original_var_args = self.var_args + if self.class_context.name.string_name in ['list', 'set'] \ - and self.parent_context.get_root_context() == self.evaluator.BUILTINS: + and self.parent_context.get_root_context() == self.evaluator.builtins_module: # compare the module path with the builtin name. self.var_args = iterable.get_dynamic_array_instance(self) @@ -223,6 +229,13 @@ def create_instance_context(self, class_context, node): else: return super(CompiledInstance, self).create_instance_context(class_context, node) + def get_first_non_keyword_argument_contexts(self): + key, lazy_context = next(self._original_var_args.unpack(), ('', None)) + if key is not None: + return NO_CONTEXTS + + return lazy_context.infer() + class TreeInstance(AbstractInstanceContext): def __init__(self, evaluator, parent_context, class_context, var_args): @@ -255,7 +268,8 @@ def __init__(self, evaluator, instance, parent_context, name): @iterator_to_context_set def infer(self): for result_context in super(CompiledInstanceName, self).infer(): - if isinstance(result_context, FunctionContext): + is_function = result_context.api_type == 'function' + if result_context.tree_node is not None and is_function: parent_context = result_context.parent_context while parent_context.is_class(): parent_context = parent_context.parent_context @@ -265,7 +279,7 @@ def infer(self): parent_context, result_context.tree_node ) else: - if result_context.api_type == 'function': + if is_function: yield CompiledBoundMethod(result_context) else: yield result_context @@ -306,7 +320,7 @@ def get_function_execution(self, arguments=None): class CompiledBoundMethod(compiled.CompiledObject): def __init__(self, func): super(CompiledBoundMethod, self).__init__( - func.evaluator, func.obj, func.parent_context, func.tree_node) + func.evaluator, func.access_handle, func.parent_context, func.tree_node) def get_param_names(self): return list(super(CompiledBoundMethod, self).get_param_names())[1:] @@ -317,7 +331,7 @@ def infer(self): return super(InstanceNameDefinition, self).infer() -class LazyInstanceName(filters.TreeNameDefinition): +class SelfName(filters.TreeNameDefinition): """ This name calculates the parent_context lazily. """ @@ -331,7 +345,7 @@ def parent_context(self): return self._instance.create_instance_context(self.class_context, self.tree_name) -class LazyInstanceClassName(LazyInstanceName): +class LazyInstanceClassName(SelfName): @iterator_to_context_set def infer(self): for result_context in super(LazyInstanceClassName, self).infer(): @@ -384,8 +398,11 @@ def _convert_names(self, names): return [self.name_class(self.context, self._class_context, name) for name in names] -class SelfNameFilter(InstanceClassFilter): - name_class = LazyInstanceName +class SelfAttributeFilter(InstanceClassFilter): + """ + This class basically filters all the use cases where `self.*` was assigned. + """ + name_class = SelfName def _filter(self, names): names = self._filter_self_names(names) diff --git a/pythonFiles/jedi/evaluate/context/iterable.py b/pythonFiles/jedi/evaluate/context/iterable.py index d0f468e43425..df012f50dd8a 100644 --- a/pythonFiles/jedi/evaluate/context/iterable.py +++ b/pythonFiles/jedi/evaluate/context/iterable.py @@ -22,74 +22,56 @@ """ from jedi import debug from jedi import settings +from jedi._compatibility import force_unicode, is_py3 +from jedi.cache import memoize_method from jedi.evaluate import compiled from jedi.evaluate import analysis from jedi.evaluate import recursion from jedi.evaluate.lazy_context import LazyKnownContext, LazyKnownContexts, \ LazyTreeContext -from jedi.evaluate.helpers import is_string, predefine_names, evaluate_call_of_leaf +from jedi.evaluate.helpers import get_int_or_none, is_string, \ + predefine_names, evaluate_call_of_leaf from jedi.evaluate.utils import safe_property from jedi.evaluate.utils import to_list from jedi.evaluate.cache import evaluator_method_cache -from jedi.evaluate.filters import ParserTreeFilter, has_builtin_methods, \ - register_builtin_method, SpecialMethodFilter +from jedi.evaluate.filters import ParserTreeFilter, BuiltinOverwrite, \ + publish_method from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS, Context, \ TreeContext, ContextualizedNode from jedi.parser_utils import get_comp_fors -class AbstractIterable(Context): - builtin_methods = {} - api_type = 'instance' +class IterableMixin(object): + def py__stop_iteration_returns(self): + return ContextSet(compiled.builtin_from_name(self.evaluator, u'None')) - def __init__(self, evaluator): - super(AbstractIterable, self).__init__(evaluator, evaluator.BUILTINS) - def get_filters(self, search_global, until_position=None, origin_scope=None): - raise NotImplementedError - - @property - def name(self): - return compiled.CompiledContextName(self, self.array_type) - - -@has_builtin_methods -class GeneratorMixin(object): +class GeneratorBase(BuiltinOverwrite, IterableMixin): array_type = None + special_object_identifier = u'GENERATOR_OBJECT' - @register_builtin_method('send') - @register_builtin_method('next', python_version_match=2) - @register_builtin_method('__next__', python_version_match=3) + @publish_method('send') + @publish_method('next', python_version_match=2) + @publish_method('__next__', python_version_match=3) def py__next__(self): - # TODO add TypeError if params are given. return ContextSet.from_sets(lazy_context.infer() for lazy_context in self.py__iter__()) - def get_filters(self, search_global, until_position=None, origin_scope=None): - gen_obj = compiled.get_special_object(self.evaluator, 'GENERATOR_OBJECT') - yield SpecialMethodFilter(self, self.builtin_methods, gen_obj) - for filter in gen_obj.get_filters(search_global): - yield filter - - def py__bool__(self): - return True - - def py__class__(self): - gen_obj = compiled.get_special_object(self.evaluator, 'GENERATOR_OBJECT') - return gen_obj.py__class__() - @property def name(self): return compiled.CompiledContextName(self, 'generator') -class Generator(GeneratorMixin, Context): +class Generator(GeneratorBase): """Handling of `yield` functions.""" def __init__(self, evaluator, func_execution_context): - super(Generator, self).__init__(evaluator, parent_context=evaluator.BUILTINS) + super(Generator, self).__init__(evaluator) self._func_execution_context = func_execution_context def py__iter__(self): - return self._func_execution_context.get_yield_values() + return self._func_execution_context.get_yield_lazy_contexts() + + def py__stop_iteration_returns(self): + return self._func_execution_context.get_return_values() def __repr__(self): return "<%s of %s>" % (type(self).__name__, self._func_execution_context) @@ -111,32 +93,33 @@ def get_filters(self, search_global, until_position=None, origin_scope=None): yield ParserTreeFilter(self.evaluator, self) -class Comprehension(AbstractIterable): - @staticmethod - def from_atom(evaluator, context, atom): - bracket = atom.children[0] - if bracket == '{': - if atom.children[1].children[1] == ':': - cls = DictComprehension - else: - cls = SetComprehension - elif bracket == '(': - cls = GeneratorComprehension - elif bracket == '[': - cls = ListComprehension - return cls(evaluator, context, atom) +def comprehension_from_atom(evaluator, context, atom): + bracket = atom.children[0] + if bracket == '{': + if atom.children[1].children[1] == ':': + cls = DictComprehension + else: + cls = SetComprehension + elif bracket == '(': + cls = GeneratorComprehension + elif bracket == '[': + cls = ListComprehension + return cls(evaluator, context, atom) + +class ComprehensionMixin(object): def __init__(self, evaluator, defining_context, atom): - super(Comprehension, self).__init__(evaluator) + super(ComprehensionMixin, self).__init__(evaluator) self._defining_context = defining_context self._atom = atom def _get_comprehension(self): + "return 'a for a in b'" # The atom contains a testlist_comp return self._atom.children[1] def _get_comp_for(self): - # The atom contains a testlist_comp + "return CompFor('for a in b')" return self._get_comprehension().children[1] def _eval_node(self, index=0): @@ -154,13 +137,17 @@ def _get_comp_for_context(self, parent_context, comp_for): def _nested(self, comp_fors, parent_context=None): comp_for = comp_fors[0] - input_node = comp_for.children[3] + + is_async = 'async' == comp_for.children[comp_for.children.index('for') - 1] + + input_node = comp_for.children[comp_for.children.index('in') + 1] parent_context = parent_context or self._defining_context input_types = parent_context.eval_node(input_node) + # TODO: simulate await if self.is_async cn = ContextualizedNode(parent_context, input_node) - iterated = input_types.iterate(cn) - exprlist = comp_for.children[1] + iterated = input_types.iterate(cn, is_async=is_async) + exprlist = comp_for.children[comp_for.children.index('for') + 1] for i, lazy_context in enumerate(iterated): types = lazy_context.infer() dct = unpack_tuple_to_dict(parent_context, types, exprlist) @@ -194,14 +181,18 @@ def __repr__(self): return "<%s of %s>" % (type(self).__name__, self._atom) -class ArrayMixin(object): - def get_filters(self, search_global, until_position=None, origin_scope=None): - # `array.type` is a string with the type, e.g. 'list'. +class Sequence(BuiltinOverwrite, IterableMixin): + api_type = u'instance' + + @property + def name(self): + return compiled.CompiledContextName(self, self.array_type) + + @memoize_method + def get_object(self): compiled_obj = compiled.builtin_from_name(self.evaluator, self.array_type) - yield SpecialMethodFilter(self, self.builtin_methods, compiled_obj) - for typ in compiled_obj.execute_evaluated(self): - for filter in typ.get_filters(): - yield filter + only_obj, = compiled_obj.execute_evaluated(self) + return only_obj def py__bool__(self): return None # We don't know the length, because of appends. @@ -211,7 +202,7 @@ def py__class__(self): @safe_property def parent(self): - return self.evaluator.BUILTINS + return self.evaluator.builtins_module def dict_values(self): return ContextSet.from_sets( @@ -220,8 +211,8 @@ def dict_values(self): ) -class ListComprehension(ArrayMixin, Comprehension): - array_type = 'list' +class ListComprehension(ComprehensionMixin, Sequence): + array_type = u'list' def py__getitem__(self, index): if isinstance(index, slice): @@ -231,13 +222,12 @@ def py__getitem__(self, index): return all_types[index].infer() -class SetComprehension(ArrayMixin, Comprehension): - array_type = 'set' +class SetComprehension(ComprehensionMixin, Sequence): + array_type = u'set' -@has_builtin_methods -class DictComprehension(ArrayMixin, Comprehension): - array_type = 'dict' +class DictComprehension(ComprehensionMixin, Sequence): + array_type = u'dict' def _get_comp_for(self): return self._get_comprehension().children[3] @@ -250,38 +240,38 @@ def py__getitem__(self, index): for keys, values in self._iterate(): for k in keys: if isinstance(k, compiled.CompiledObject): - if k.obj == index: + if k.get_safe_value(default=object()) == index: return values return self.dict_values() def dict_values(self): return ContextSet.from_sets(values for keys, values in self._iterate()) - @register_builtin_method('values') + @publish_method('values') def _imitate_values(self): lazy_context = LazyKnownContexts(self.dict_values()) - return ContextSet(FakeSequence(self.evaluator, 'list', [lazy_context])) + return ContextSet(FakeSequence(self.evaluator, u'list', [lazy_context])) - @register_builtin_method('items') + @publish_method('items') def _imitate_items(self): items = ContextSet.from_iterable( FakeSequence( - self.evaluator, 'tuple' + self.evaluator, u'tuple' (LazyKnownContexts(keys), LazyKnownContexts(values)) ) for keys, values in self._iterate() ) - return create_evaluated_sequence_set(self.evaluator, items, sequence_type='list') + return create_evaluated_sequence_set(self.evaluator, items, sequence_type=u'list') -class GeneratorComprehension(GeneratorMixin, Comprehension): +class GeneratorComprehension(ComprehensionMixin, GeneratorBase): pass -class SequenceLiteralContext(ArrayMixin, AbstractIterable): - mapping = {'(': 'tuple', - '[': 'list', - '{': 'set'} +class SequenceLiteralContext(Sequence): + mapping = {'(': u'tuple', + '[': u'list', + '{': u'set'} def __init__(self, evaluator, defining_context, atom): super(SequenceLiteralContext, self).__init__(evaluator) @@ -289,18 +279,19 @@ def __init__(self, evaluator, defining_context, atom): self._defining_context = defining_context if self.atom.type in ('testlist_star_expr', 'testlist'): - self.array_type = 'tuple' + self.array_type = u'tuple' else: self.array_type = SequenceLiteralContext.mapping[atom.children[0]] """The builtin name of the array (list, set, tuple or dict).""" def py__getitem__(self, index): """Here the index is an int/str. Raises IndexError/KeyError.""" - if self.array_type == 'dict': + if self.array_type == u'dict': + compiled_obj_index = compiled.create_simple_object(self.evaluator, index) for key, value in self._items(): for k in self._defining_context.eval_node(key): if isinstance(k, compiled.CompiledObject) \ - and index == k.obj: + and k.execute_operation(compiled_obj_index, u'==').get_safe_value(): return self._defining_context.eval_node(value) raise KeyError('No key found in dictionary %s.' % self) @@ -315,7 +306,7 @@ def py__iter__(self): While values returns the possible values for any array field, this function returns the value for a certain index. """ - if self.array_type == 'dict': + if self.array_type == u'dict': # Get keys. types = ContextSet() for k, _ in self._items(): @@ -333,7 +324,7 @@ def py__iter__(self): def _values(self): """Returns a list of a list of node.""" - if self.array_type == 'dict': + if self.array_type == u'dict': return ContextSet.from_sets(v for k, v in self._items()) else: return self._items() @@ -373,37 +364,36 @@ def exact_key_items(self): for key_node, value in self._items(): for key in self._defining_context.eval_node(key_node): if is_string(key): - yield key.obj, LazyTreeContext(self._defining_context, value) + yield key.get_safe_value(), LazyTreeContext(self._defining_context, value) def __repr__(self): return "<%s of %s>" % (self.__class__.__name__, self.atom) -@has_builtin_methods class DictLiteralContext(SequenceLiteralContext): - array_type = 'dict' + array_type = u'dict' def __init__(self, evaluator, defining_context, atom): super(SequenceLiteralContext, self).__init__(evaluator) self._defining_context = defining_context self.atom = atom - @register_builtin_method('values') + @publish_method('values') def _imitate_values(self): lazy_context = LazyKnownContexts(self.dict_values()) - return ContextSet(FakeSequence(self.evaluator, 'list', [lazy_context])) + return ContextSet(FakeSequence(self.evaluator, u'list', [lazy_context])) - @register_builtin_method('items') + @publish_method('items') def _imitate_items(self): lazy_contexts = [ LazyKnownContext(FakeSequence( - self.evaluator, 'tuple', + self.evaluator, u'tuple', (LazyTreeContext(self._defining_context, key_node), LazyTreeContext(self._defining_context, value_node)) )) for key_node, value_node in self._items() ] - return ContextSet(FakeSequence(self.evaluator, 'list', lazy_contexts)) + return ContextSet(FakeSequence(self.evaluator, u'list', lazy_contexts)) class _FakeArray(SequenceLiteralContext): @@ -437,16 +427,38 @@ def __repr__(self): class FakeDict(_FakeArray): def __init__(self, evaluator, dct): - super(FakeDict, self).__init__(evaluator, dct, 'dict') + super(FakeDict, self).__init__(evaluator, dct, u'dict') self._dct = dct def py__iter__(self): for key in self._dct: - yield LazyKnownContext(compiled.create(self.evaluator, key)) + yield LazyKnownContext(compiled.create_simple_object(self.evaluator, key)) def py__getitem__(self, index): + if is_py3 and self.evaluator.environment.version_info.major == 2: + # In Python 2 bytes and unicode compare. + if isinstance(index, bytes): + index_unicode = force_unicode(index) + try: + return self._dct[index_unicode].infer() + except KeyError: + pass + elif isinstance(index, str): + index_bytes = index.encode('utf-8') + try: + return self._dct[index_bytes].infer() + except KeyError: + pass + return self._dct[index].infer() + @publish_method('values') + def _values(self): + return ContextSet(FakeSequence( + self.evaluator, u'tuple', + [LazyKnownContexts(self.dict_values())] + )) + def dict_values(self): return ContextSet.from_sets(lazy_context.infer() for lazy_context in self._dct.values()) @@ -649,7 +661,7 @@ def py__iter__(self): for addition in additions: yield addition - def iterate(self, contextualized_node=None): + def iterate(self, contextualized_node=None, is_async=False): return self.py__iter__() @@ -657,7 +669,7 @@ class Slice(Context): def __init__(self, context, start, stop, step): super(Slice, self).__init__( context.evaluator, - parent_context=context.evaluator.BUILTINS + parent_context=context.evaluator.builtins_module ) self._context = context # all of them are either a Precedence or None. @@ -680,10 +692,9 @@ def get(element): # For simplicity, we want slices to be clear defined with just # one type. Otherwise we will return an empty slice object. raise IndexError - try: - return list(result)[0].obj - except AttributeError: - return None + + context, = result + return get_int_or_none(context) try: return slice(get(self._start), get(self._stop), get(self._step)) diff --git a/pythonFiles/jedi/evaluate/context/klass.py b/pythonFiles/jedi/evaluate/context/klass.py index b7d61d3e16bf..3157250161e2 100644 --- a/pythonFiles/jedi/evaluate/context/klass.py +++ b/pythonFiles/jedi/evaluate/context/klass.py @@ -89,7 +89,7 @@ class ClassContext(use_metaclass(CachedMetaClass, TreeContext)): This class is not only important to extend `tree.Class`, it is also a important for descriptors (if the descriptor methods are evaluated or not). """ - api_type = 'class' + api_type = u'class' def __init__(self, evaluator, parent_context, classdef): super(ClassContext, self).__init__(evaluator, parent_context=parent_context) @@ -136,17 +136,17 @@ def py__bases__(self): arglist = self.tree_node.get_super_arglist() if arglist: from jedi.evaluate import arguments - args = arguments.TreeArguments(self.evaluator, self, arglist) + args = arguments.TreeArguments(self.evaluator, self.parent_context, arglist) return [value for key, value in args.unpack() if key is None] else: - return [LazyKnownContext(compiled.create(self.evaluator, object))] + return [LazyKnownContext(compiled.builtin_from_name(self.evaluator, u'object'))] def py__call__(self, params): from jedi.evaluate.context import TreeInstance return ContextSet(TreeInstance(self.evaluator, self.parent_context, self, params)) def py__class__(self): - return compiled.create(self.evaluator, type) + return compiled.builtin_from_name(self.evaluator, u'type') def get_params(self): from jedi.evaluate.context import AnonymousInstance @@ -182,7 +182,7 @@ def get_function_slot_names(self, name): return [] def get_param_names(self): - for name in self.get_function_slot_names('__init__'): + for name in self.get_function_slot_names(u'__init__'): for context_ in name.infer(): try: method = context_.get_param_names diff --git a/pythonFiles/jedi/evaluate/context/module.py b/pythonFiles/jedi/evaluate/context/module.py index 5ba92cdb1c3e..8d4da11bc91a 100644 --- a/pythonFiles/jedi/evaluate/context/module.py +++ b/pythonFiles/jedi/evaluate/context/module.py @@ -1,14 +1,12 @@ -import pkgutil -import imp import re import os from parso import python_bytes_to_unicode -from jedi._compatibility import use_metaclass -from jedi.evaluate.cache import CachedMetaClass, evaluator_method_cache +from jedi.evaluate.cache import evaluator_method_cache +from jedi._compatibility import iter_modules, all_suffixes from jedi.evaluate.filters import GlobalNameFilter, ContextNameMixin, \ - AbstractNameDefinition, ParserTreeFilter, DictFilter + AbstractNameDefinition, ParserTreeFilter, DictFilter, MergedFilter from jedi.evaluate import compiled from jedi.evaluate.base_context import TreeContext from jedi.evaluate.imports import SubModuleName, infer_import @@ -18,14 +16,14 @@ class _ModuleAttributeName(AbstractNameDefinition): """ For module attributes like __file__, __str__ and so on. """ - api_type = 'instance' + api_type = u'instance' def __init__(self, parent_module, string_name): self.parent_context = parent_module self.string_name = string_name def infer(self): - return compiled.create(self.parent_context.evaluator, str).execute_evaluated() + return compiled.get_string_context_set(self.parent_context.evaluator) class ModuleName(ContextNameMixin, AbstractNameDefinition): @@ -40,23 +38,26 @@ def string_name(self): return self._name -class ModuleContext(use_metaclass(CachedMetaClass, TreeContext)): - api_type = 'module' +class ModuleContext(TreeContext): + api_type = u'module' parent_context = None - def __init__(self, evaluator, module_node, path): + def __init__(self, evaluator, module_node, path, code_lines): super(ModuleContext, self).__init__(evaluator, parent_context=None) self.tree_node = module_node self._path = path + self.code_lines = code_lines def get_filters(self, search_global, until_position=None, origin_scope=None): - yield ParserTreeFilter( - self.evaluator, - context=self, - until_position=until_position, - origin_scope=origin_scope + yield MergedFilter( + ParserTreeFilter( + self.evaluator, + context=self, + until_position=until_position, + origin_scope=origin_scope + ), + GlobalNameFilter(self, self.tree_node), ) - yield GlobalNameFilter(self, self.tree_node) yield DictFilter(self._sub_modules_dict()) yield DictFilter(self._module_attributes_dict()) for star_module in self.star_imports(): @@ -64,7 +65,7 @@ def get_filters(self, search_global, until_position=None, origin_scope=None): # I'm not sure if the star import cache is really that effective anymore # with all the other really fast import caches. Recheck. Also we would need - # to push the star imports into Evaluator.modules, if we reenable this. + # to push the star imports into Evaluator.module_cache, if we reenable this. @evaluator_method_cache([]) def star_imports(self): modules = [] @@ -93,7 +94,7 @@ def _string_name(self): sep = (re.escape(os.path.sep),) * 2 r = re.search(r'([^%s]*?)(%s__init__)?(\.py|\.so)?$' % sep, self._path) # Remove PEP 3149 names - return re.sub('\.[a-z]+-\d{2}[mud]{0,3}$', '', r.group(1)) + return re.sub(r'\.[a-z]+-\d{2}[mud]{0,3}$', '', r.group(1)) @property @evaluator_method_cache() @@ -105,7 +106,7 @@ def _get_init_directory(self): :return: The path to the directory of a package. None in case it's not a package. """ - for suffix, _, _ in imp.get_suffixes(): + for suffix in all_suffixes(): ending = '__init__' + suffix py__file__ = self.py__file__() if py__file__ is not None and py__file__.endswith(ending): @@ -114,7 +115,7 @@ def _get_init_directory(self): return None def py__name__(self): - for name, module in self.evaluator.modules.items(): + for name, module in self.evaluator.module_cache.iterate_modules_with_names(): if module == self and name != '': return name @@ -131,12 +132,12 @@ def py__file__(self): def py__package__(self): if self._get_init_directory() is None: - return re.sub(r'\.?[^\.]+$', '', self.py__name__()) + return re.sub(r'\.?[^.]+$', '', self.py__name__()) else: return self.py__name__() def _py__path__(self): - search_path = self.evaluator.project.sys_path + search_path = self.evaluator.get_sys_path() init_path = self.py__file__() if os.path.basename(init_path) == '__init__.py': with open(init_path, 'rb') as f: @@ -188,7 +189,7 @@ def _sub_modules_dict(self): path = self._path names = {} if path is not None and path.endswith(os.path.sep + '__init__.py'): - mods = pkgutil.iter_modules([os.path.dirname(path)]) + mods = iter_modules([os.path.dirname(path)]) for module_loader, name, is_pkg in mods: # It's obviously a relative import to the current module. names[name] = SubModuleName(self, name) @@ -203,11 +204,9 @@ def _sub_modules_dict(self): return names def py__class__(self): - return compiled.get_special_object(self.evaluator, 'MODULE_CLASS') + return compiled.get_special_object(self.evaluator, u'MODULE_CLASS') def __repr__(self): return "<%s: %s@%s-%s>" % ( self.__class__.__name__, self._string_name, self.tree_node.start_pos[0], self.tree_node.end_pos[0]) - - diff --git a/pythonFiles/jedi/evaluate/context/namespace.py b/pythonFiles/jedi/evaluate/context/namespace.py index e40c23a5e8ca..380d7c2270b9 100644 --- a/pythonFiles/jedi/evaluate/context/namespace.py +++ b/pythonFiles/jedi/evaluate/context/namespace.py @@ -1,11 +1,10 @@ import os from itertools import chain -from jedi._compatibility import use_metaclass -from jedi.evaluate.cache import evaluator_method_cache, CachedMetaClass +from jedi.evaluate.cache import evaluator_method_cache from jedi.evaluate import imports from jedi.evaluate.filters import DictFilter, AbstractNameDefinition -from jedi.evaluate.base_context import NO_CONTEXTS, TreeContext +from jedi.evaluate.base_context import TreeContext, ContextSet class ImplicitNSName(AbstractNameDefinition): @@ -14,27 +13,31 @@ class ImplicitNSName(AbstractNameDefinition): This object will prevent Jedi from raising exceptions """ def __init__(self, implicit_ns_context, string_name): - self.implicit_ns_context = implicit_ns_context + self.parent_context = implicit_ns_context self.string_name = string_name def infer(self): - return NO_CONTEXTS + return ContextSet(self.parent_context) def get_root_context(self): - return self.implicit_ns_context + return self.parent_context -class ImplicitNamespaceContext(use_metaclass(CachedMetaClass, TreeContext)): +class ImplicitNamespaceContext(TreeContext): """ Provides support for implicit namespace packages """ - api_type = 'module' + # Is a module like every other module, because if you import an empty + # folder foobar it will be available as an object: + # . + api_type = u'module' parent_context = None - def __init__(self, evaluator, fullname): + def __init__(self, evaluator, fullname, paths): super(ImplicitNamespaceContext, self).__init__(evaluator, parent_context=None) self.evaluator = evaluator - self.fullname = fullname + self._fullname = fullname + self.paths = paths def get_filters(self, search_global, until_position=None, origin_scope=None): yield DictFilter(self._sub_modules_dict()) @@ -51,7 +54,7 @@ def py__file__(self): def py__package__(self): """Return the fullname """ - return self.fullname + return self._fullname @property def py__path__(self): @@ -61,8 +64,7 @@ def py__path__(self): def _sub_modules_dict(self): names = {} - paths = self.paths - file_names = chain.from_iterable(os.listdir(path) for path in paths) + file_names = chain.from_iterable(os.listdir(path) for path in self.paths) mods = [ file_name.rpartition('.')[0] if '.' in file_name else file_name for file_name in file_names diff --git a/pythonFiles/jedi/evaluate/docstrings.py b/pythonFiles/jedi/evaluate/docstrings.py index f9c1141226e9..a927abd09028 100644 --- a/pythonFiles/jedi/evaluate/docstrings.py +++ b/pythonFiles/jedi/evaluate/docstrings.py @@ -18,7 +18,7 @@ import re from textwrap import dedent -from parso import parse +from parso import parse, ParserSyntaxError from jedi._compatibility import u from jedi.evaluate.utils import indent_block @@ -42,49 +42,59 @@ REST_ROLE_PATTERN = re.compile(r':[^`]+:`([^`]+)`') -try: - from numpydoc.docscrape import NumpyDocString -except ImportError: - def _search_param_in_numpydocstr(docstr, param_str): - return [] +_numpy_doc_string_cache = None - def _search_return_in_numpydocstr(docstr): - return [] -else: - def _search_param_in_numpydocstr(docstr, param_str): - """Search `docstr` (in numpydoc format) for type(-s) of `param_str`.""" - try: - # This is a non-public API. If it ever changes we should be - # prepared and return gracefully. - params = NumpyDocString(docstr)._parsed_data['Parameters'] - except (KeyError, AttributeError): - return [] - for p_name, p_type, p_descr in params: - if p_name == param_str: - m = re.match('([^,]+(,[^,]+)*?)(,[ ]*optional)?$', p_type) - if m: - p_type = m.group(1) - return list(_expand_typestr(p_type)) + +def _get_numpy_doc_string_cls(): + global _numpy_doc_string_cache + try: + from numpydoc.docscrape import NumpyDocString + _numpy_doc_string_cache = NumpyDocString + except ImportError as e: + _numpy_doc_string_cache = e + if isinstance(_numpy_doc_string_cache, ImportError): + raise _numpy_doc_string_cache + return _numpy_doc_string_cache + + +def _search_param_in_numpydocstr(docstr, param_str): + """Search `docstr` (in numpydoc format) for type(-s) of `param_str`.""" + try: + # This is a non-public API. If it ever changes we should be + # prepared and return gracefully. + params = _get_numpy_doc_string_cls()(docstr)._parsed_data['Parameters'] + except (KeyError, AttributeError, ImportError): return [] + for p_name, p_type, p_descr in params: + if p_name == param_str: + m = re.match('([^,]+(,[^,]+)*?)(,[ ]*optional)?$', p_type) + if m: + p_type = m.group(1) + return list(_expand_typestr(p_type)) + return [] - def _search_return_in_numpydocstr(docstr): - """ - Search `docstr` (in numpydoc format) for type(-s) of function returns. - """ - doc = NumpyDocString(docstr) - try: - # This is a non-public API. If it ever changes we should be - # prepared and return gracefully. - returns = doc._parsed_data['Returns'] - returns += doc._parsed_data['Yields'] - except (KeyError, AttributeError): - raise StopIteration - for r_name, r_type, r_descr in returns: - #Return names are optional and if so the type is in the name - if not r_type: - r_type = r_name - for type_ in _expand_typestr(r_type): - yield type_ + +def _search_return_in_numpydocstr(docstr): + """ + Search `docstr` (in numpydoc format) for type(-s) of function returns. + """ + try: + doc = _get_numpy_doc_string_cls()(docstr) + except ImportError: + return + try: + # This is a non-public API. If it ever changes we should be + # prepared and return gracefully. + returns = doc._parsed_data['Returns'] + returns += doc._parsed_data['Yields'] + except (KeyError, AttributeError): + return + for r_name, r_type, r_descr in returns: + # Return names are optional and if so the type is in the name + if not r_type: + r_type = r_name + for type_ in _expand_typestr(r_type): + yield type_ def _expand_typestr(type_str): @@ -145,8 +155,7 @@ def _search_param_in_docstr(docstr, param_str): if match: return [_strip_rst_role(match.group(1))] - return (_search_param_in_numpydocstr(docstr, param_str) or - []) + return _search_param_in_numpydocstr(docstr, param_str) def _strip_rst_role(type_str): @@ -179,7 +188,7 @@ def pseudo_docstring_stuff(): Need this docstring so that if the below part is not valid Python this is still a function. ''' - {0} + {} """)) if string is None: return [] @@ -193,7 +202,10 @@ def pseudo_docstring_stuff(): # will be impossible to use `...` (Ellipsis) as a token. Docstring types # don't need to conform with the current grammar. grammar = module_context.evaluator.latest_grammar - module = grammar.parse(code.format(indent_block(string))) + try: + module = grammar.parse(code.format(indent_block(string)), error_recovery=False) + except ParserSyntaxError: + return [] try: funcdef = next(module.iter_funcdefs()) # First pick suite, then simple_stmt and then the node, @@ -243,7 +255,7 @@ def _execute_array_values(evaluator, array): for typ in lazy_context.infer() ) values.append(LazyKnownContexts(objects)) - return set([FakeSequence(evaluator, array.array_type, values)]) + return {FakeSequence(evaluator, array.array_type, values)} else: return array.execute_evaluated() diff --git a/pythonFiles/jedi/evaluate/dynamic.py b/pythonFiles/jedi/evaluate/dynamic.py index 7d05000dc9d5..9e8d57144bdb 100644 --- a/pythonFiles/jedi/evaluate/dynamic.py +++ b/pythonFiles/jedi/evaluate/dynamic.py @@ -73,24 +73,33 @@ def search_params(evaluator, execution_context, funcdef): # you will see the slowdown, especially in 3.6. return create_default_params(execution_context, funcdef) - debug.dbg('Dynamic param search in %s.', funcdef.name.value, color='MAGENTA') - - module_context = execution_context.get_root_context() - function_executions = _search_function_executions( - evaluator, - module_context, - funcdef - ) - if function_executions: - zipped_params = zip(*list( - function_execution.get_params() - for function_execution in function_executions - )) - params = [MergedExecutedParams(executed_params) for executed_params in zipped_params] - # Evaluate the ExecutedParams to types. + if funcdef.type == 'lambdef': + string_name = _get_lambda_name(funcdef) + if string_name is None: + return create_default_params(execution_context, funcdef) else: - return create_default_params(execution_context, funcdef) - debug.dbg('Dynamic param result finished', color='MAGENTA') + string_name = funcdef.name.value + debug.dbg('Dynamic param search in %s.', string_name, color='MAGENTA') + + try: + module_context = execution_context.get_root_context() + function_executions = _search_function_executions( + evaluator, + module_context, + funcdef, + string_name=string_name, + ) + if function_executions: + zipped_params = zip(*list( + function_execution.get_params() + for function_execution in function_executions + )) + params = [MergedExecutedParams(executed_params) for executed_params in zipped_params] + # Evaluate the ExecutedParams to types. + else: + return create_default_params(execution_context, funcdef) + finally: + debug.dbg('Dynamic param result finished', color='MAGENTA') return params finally: evaluator.dynamic_params_depth -= 1 @@ -98,25 +107,24 @@ def search_params(evaluator, execution_context, funcdef): @evaluator_function_cache(default=None) @to_list -def _search_function_executions(evaluator, module_context, funcdef): +def _search_function_executions(evaluator, module_context, funcdef, string_name): """ Returns a list of param names. """ - func_string_name = funcdef.name.value compare_node = funcdef - if func_string_name == '__init__': + if string_name == '__init__': cls = get_parent_scope(funcdef) if isinstance(cls, tree.Class): - func_string_name = cls.name.value + string_name = cls.name.value compare_node = cls found_executions = False i = 0 for for_mod_context in imports.get_modules_containing_name( - evaluator, [module_context], func_string_name): + evaluator, [module_context], string_name): if not isinstance(module_context, ModuleContext): return - for name, trailer in _get_possible_nodes(for_mod_context, func_string_name): + for name, trailer in _get_possible_nodes(for_mod_context, string_name): i += 1 # This is a simple way to stop Jedi's dynamic param recursion @@ -137,6 +145,18 @@ def _search_function_executions(evaluator, module_context, funcdef): return +def _get_lambda_name(node): + stmt = node.parent + if stmt.type == 'expr_stmt': + first_operator = next(stmt.yield_operators(), None) + if first_operator == '=': + first = stmt.children[0] + if first.type == 'name': + return first.value + + return None + + def _get_possible_nodes(module_context, func_string_name): try: names = module_context.tree_node.get_used_names()[func_string_name] @@ -156,11 +176,9 @@ def _check_name_for_execution(evaluator, context, compare_node, name, trailer): def create_func_excs(): arglist = trailer.children[1] if arglist == ')': - arglist = () + arglist = None args = TreeArguments(evaluator, context, arglist, trailer) - if value_node.type == 'funcdef': - yield value.get_function_execution(args) - else: + if value_node.type == 'classdef': created_instance = instance.TreeInstance( evaluator, value.parent_context, @@ -169,6 +187,8 @@ def create_func_excs(): ) for execution in created_instance.create_init_executions(): yield execution + else: + yield value.get_function_execution(args) for value in evaluator.goto_definitions(context, name): value_node = value.tree_node diff --git a/pythonFiles/jedi/evaluate/filters.py b/pythonFiles/jedi/evaluate/filters.py index 35dff9dace65..4294f2a60004 100644 --- a/pythonFiles/jedi/evaluate/filters.py +++ b/pythonFiles/jedi/evaluate/filters.py @@ -6,7 +6,8 @@ from parso.tree import search_ancestor -from jedi._compatibility import is_py3 +from jedi._compatibility import use_metaclass, Parameter +from jedi.cache import memoize_method from jedi.evaluate import flow_analysis from jedi.evaluate.base_context import ContextSet, Context from jedi.parser_utils import get_parent_scope @@ -27,7 +28,7 @@ def infer(self): def goto(self): # Typically names are already definitions and therefore a goto on that # name will always result on itself. - return set([self]) + return {self} def get_root_context(self): return self.parent_context.get_root_context() @@ -43,6 +44,9 @@ def execute(self, arguments): def execute_evaluated(self, *args, **kwargs): return self.infer().execute_evaluated(*args, **kwargs) + def is_import(self): + return False + @property def api_type(self): return self.parent_context.api_type @@ -56,6 +60,10 @@ def __init__(self, parent_context, tree_name): def goto(self): return self.parent_context.evaluator.goto(self.parent_context, self.tree_name) + def is_import(self): + imp = search_ancestor(self.tree_name, 'import_from', 'import_name') + return imp is not None + @property def string_name(self): return self.tree_name.value @@ -108,12 +116,28 @@ def api_type(self): class ParamName(AbstractTreeName): - api_type = 'param' + api_type = u'param' def __init__(self, parent_context, tree_name): self.parent_context = parent_context self.tree_name = tree_name + def get_kind(self): + tree_param = search_ancestor(self.tree_name, 'param') + if tree_param.star_count == 1: # *args + return Parameter.VAR_POSITIONAL + if tree_param.star_count == 2: # **kwargs + return Parameter.VAR_KEYWORD + + parent = tree_param.parent + for p in parent.children: + if p.type == 'param': + if p.star_count: + return Parameter.KEYWORD_ONLY + if p == tree_param: + break + return Parameter.POSITIONAL_OR_KEYWORD + def infer(self): return self.get_param().infer() @@ -163,7 +187,7 @@ def __init__(self, context, parser_scope): def get(self, name): try: - names = self._used_names[str(name)] + names = self._used_names[name] except KeyError: return [] @@ -213,7 +237,10 @@ def _is_name_reachable(self, name): def _check_flows(self, names): for name in sorted(names, key=lambda name: name.start_pos, reverse=True): check = flow_analysis.reachability_check( - self._node_context, self._parser_scope, name, self._origin_scope + context=self._node_context, + context_scope=self._parser_scope, + node=name, + origin_scope=self._origin_scope ) if check is not flow_analysis.UNREACHABLE: yield name @@ -266,22 +293,42 @@ def __init__(self, dct): def get(self, name): try: - value = self._convert(name, self._dct[str(name)]) + value = self._convert(name, self._dct[name]) except KeyError: return [] - - return list(self._filter([value])) + else: + return list(self._filter([value])) def values(self): - return self._filter(self._convert(*item) for item in self._dct.items()) + def yielder(): + for item in self._dct.items(): + try: + yield self._convert(*item) + except KeyError: + pass + return self._filter(yielder()) def _convert(self, name, value): return value +class MergedFilter(object): + def __init__(self, *filters): + self._filters = filters + + def get(self, name): + return [n for filter in self._filters for n in filter.get(name)] + + def values(self): + return [n for filter in self._filters for n in filter.values()] + + def __repr__(self): + return '%s(%s)' % (self.__class__.__name__, ', '.join(str(f) for f in self._filters)) + + class _BuiltinMappedMethod(Context): """``Generator.__next__`` ``dict.values`` methods and so on.""" - api_type = 'function' + api_type = u'function' def __init__(self, builtin_context, method, builtin_func): super(_BuiltinMappedMethod, self).__init__( @@ -292,6 +339,7 @@ def __init__(self, builtin_context, method, builtin_func): self._builtin_func = builtin_func def py__call__(self, params): + # TODO add TypeError if params are given/or not correct. return self._method(self.parent_context) def __getattr__(self, name): @@ -304,21 +352,33 @@ class SpecialMethodFilter(DictFilter): classes like Generator (for __next__, etc). """ class SpecialMethodName(AbstractNameDefinition): - api_type = 'function' + api_type = u'function' + + def __init__(self, parent_context, string_name, value, builtin_context): + callable_, python_version = value + if python_version is not None and \ + python_version != parent_context.evaluator.environment.version_info.major: + raise KeyError - def __init__(self, parent_context, string_name, callable_, builtin_context): self.parent_context = parent_context self.string_name = string_name self._callable = callable_ self._builtin_context = builtin_context def infer(self): - filter = next(self._builtin_context.get_filters()) - # We can take the first index, because on builtin methods there's - # always only going to be one name. The same is true for the - # inferred values. - builtin_func = next(iter(filter.get(self.string_name)[0].infer())) - return ContextSet(_BuiltinMappedMethod(self.parent_context, self._callable, builtin_func)) + for filter in self._builtin_context.get_filters(): + # We can take the first index, because on builtin methods there's + # always only going to be one name. The same is true for the + # inferred values. + for name in filter.get(self.string_name): + builtin_func = next(iter(name.infer())) + break + else: + continue + break + return ContextSet( + _BuiltinMappedMethod(self.parent_context, self._callable, builtin_func) + ) def __init__(self, context, dct, builtin_context): super(SpecialMethodFilter, self).__init__(dct) @@ -335,34 +395,58 @@ def _convert(self, name, value): return self.SpecialMethodName(self.context, name, value, self._builtin_context) -def has_builtin_methods(cls): - base_dct = {} - # Need to care properly about inheritance. Builtin Methods should not get - # lost, just because they are not mentioned in a class. - for base_cls in reversed(cls.__bases__): - try: - base_dct.update(base_cls.builtin_methods) - except AttributeError: - pass +class _OverwriteMeta(type): + def __init__(cls, name, bases, dct): + super(_OverwriteMeta, cls).__init__(name, bases, dct) - cls.builtin_methods = base_dct - for func in cls.__dict__.values(): - try: - cls.builtin_methods.update(func.registered_builtin_methods) - except AttributeError: - pass - return cls - - -def register_builtin_method(method_name, python_version_match=None): - def wrapper(func): - if python_version_match and python_version_match != 2 + int(is_py3): - # Some functions do only apply to certain versions. - return func - dct = func.__dict__.setdefault('registered_builtin_methods', {}) - dct[method_name] = func + base_dct = {} + for base_cls in reversed(cls.__bases__): + try: + base_dct.update(base_cls.overwritten_methods) + except AttributeError: + pass + + for func in cls.__dict__.values(): + try: + base_dct.update(func.registered_overwritten_methods) + except AttributeError: + pass + cls.overwritten_methods = base_dct + + +class AbstractObjectOverwrite(use_metaclass(_OverwriteMeta, object)): + def get_object(self): + raise NotImplementedError + + def get_filters(self, search_global, *args, **kwargs): + yield SpecialMethodFilter(self, self.overwritten_methods, self.get_object()) + + for filter in self.get_object().get_filters(search_global): + yield filter + + +class BuiltinOverwrite(Context, AbstractObjectOverwrite): + special_object_identifier = None + + def __init__(self, evaluator): + super(BuiltinOverwrite, self).__init__(evaluator, evaluator.builtins_module) + + @memoize_method + def get_object(self): + from jedi.evaluate import compiled + assert self.special_object_identifier + return compiled.get_special_object(self.evaluator, self.special_object_identifier) + + def py__class__(self): + return self.get_object().py__class__() + + +def publish_method(method_name, python_version_match=None): + def decorator(func): + dct = func.__dict__.setdefault('registered_overwritten_methods', {}) + dct[method_name] = func, python_version_match return func - return wrapper + return decorator def get_global_filters(evaluator, context, until_position, origin_scope): @@ -379,40 +463,37 @@ def get_global_filters(evaluator, context, until_position, origin_scope): ... def func(): ... y = None ... ''')) - >>> module_node = script._get_module_node() + >>> module_node = script._module_node >>> scope = next(module_node.iter_funcdefs()) >>> scope >>> context = script._get_module().create_context(scope) >>> filters = list(get_global_filters(context.evaluator, context, (4, 0), None)) - First we get the names names from the function scope. + First we get the names from the function scope. - >>> no_unicode_pprint(filters[0]) - > + >>> no_unicode_pprint(filters[0]) #doctest: +ELLIPSIS + MergedFilter(, ) >>> sorted(str(n) for n in filters[0].values()) ['', ''] - >>> filters[0]._until_position + >>> filters[0]._filters[0]._until_position (4, 0) + >>> filters[0]._filters[1]._until_position Then it yields the names from one level "lower". In this example, this is - the module scope. As a side note, you can see, that the position in the - filter is now None, because typically the whole module is loaded before the - function is called. + the module scope (including globals). + As a side note, you can see, that the position in the filter is None on the + globals filter, because there the whole module is searched. - >>> filters[1].values() # global names -> there are none in our example. - [] - >>> list(filters[2].values()) # package modules -> Also empty. + >>> list(filters[1].values()) # package modules -> Also empty. [] - >>> sorted(name.string_name for name in filters[3].values()) # Module attributes + >>> sorted(name.string_name for name in filters[2].values()) # Module attributes ['__doc__', '__file__', '__name__', '__package__'] - >>> print(filters[1]._until_position) - None Finally, it yields the builtin filter, if `include_builtin` is true (default). - >>> filters[4].values() #doctest: +ELLIPSIS + >>> filters[3].values() #doctest: +ELLIPSIS [, ...] """ from jedi.evaluate.context.function import FunctionExecutionContext @@ -430,5 +511,5 @@ def get_global_filters(evaluator, context, until_position, origin_scope): context = context.parent_context # Add builtins to the global scope. - for filter in evaluator.BUILTINS.get_filters(search_global=True): + for filter in evaluator.builtins_module.get_filters(search_global=True): yield filter diff --git a/pythonFiles/jedi/evaluate/finder.py b/pythonFiles/jedi/evaluate/finder.py index 96032ae9b792..5e7043f79600 100644 --- a/pythonFiles/jedi/evaluate/finder.py +++ b/pythonFiles/jedi/evaluate/finder.py @@ -56,7 +56,10 @@ def find(self, filters, attribute_lookup): names = self.filter_name(filters) if self._found_predefined_types is not None and names: check = flow_analysis.reachability_check( - self._context, self._context.tree_node, self._name) + context=self._context, + context_scope=self._context.tree_node, + node=self._name, + ) if check is flow_analysis.UNREACHABLE: return ContextSet() return self._found_predefined_types @@ -92,7 +95,26 @@ def _get_origin_scope(self): def get_filters(self, search_global=False): origin_scope = self._get_origin_scope() if search_global: - return get_global_filters(self._evaluator, self._context, self._position, origin_scope) + position = self._position + + # For functions and classes the defaults don't belong to the + # function and get evaluated in the context before the function. So + # make sure to exclude the function/class name. + if origin_scope is not None: + ancestor = search_ancestor(origin_scope, 'funcdef', 'classdef', 'lambdef') + lambdef = None + if ancestor == 'lambdef': + # For lambdas it's even more complicated since parts will + # be evaluated later. + lambdef = ancestor + ancestor = search_ancestor(origin_scope, 'funcdef', 'classdef') + if ancestor is not None: + colon = ancestor.children[-2] + if position < colon.start_pos: + if lambdef is None or position < lambdef.children[-2].start_pos: + position = ancestor.start_pos + + return get_global_filters(self._evaluator, self._context, position, origin_scope) else: return self._context.get_filters(search_global, self._position, origin_scope=origin_scope) @@ -102,8 +124,7 @@ def filter_name(self, filters): ``filters``), until a name fits. """ names = [] - if self._context.predefined_names: - # TODO is this ok? node might not always be a tree.Name + if self._context.predefined_names and isinstance(self._name, tree.Name): node = self._name while node is not None and not is_scope(node): node = node.parent @@ -133,14 +154,14 @@ def filter_name(self, filters): continue break - debug.dbg('finder.filter_name "%s" in (%s): %s@%s', self._string_name, - self._context, names, self._position) + debug.dbg('finder.filter_name %s in (%s): %s@%s', + self._string_name, self._context, names, self._position) return list(names) def _check_getattr(self, inst): """Checks for both __getattr__ and __getattribute__ methods""" # str is important, because it shouldn't be `Name`! - name = compiled.create(self._evaluator, self._string_name) + name = compiled.create_simple_object(self._evaluator, self._string_name) # This is a little bit special. `__getattribute__` is in Python # executed before `__getattr__`. But: I know no use case, where @@ -149,8 +170,8 @@ def _check_getattr(self, inst): # We are inversing this, because a hand-crafted `__getattribute__` # could still call another hand-crafted `__getattr__`, but not the # other way around. - names = (inst.get_function_slot_names('__getattr__') or - inst.get_function_slot_names('__getattribute__')) + names = (inst.get_function_slot_names(u'__getattr__') or + inst.get_function_slot_names(u'__getattribute__')) return inst.execute_function_slots(names, name) def _names_to_types(self, names, attribute_lookup): @@ -248,8 +269,7 @@ def _check_isinstance_type(context, element, search_name): context_set = ContextSet() for cls_or_tup in lazy_context_cls.infer(): - if isinstance(cls_or_tup, iterable.AbstractIterable) and \ - cls_or_tup.array_type == 'tuple': + if isinstance(cls_or_tup, iterable.Sequence) and cls_or_tup.array_type == 'tuple': for lazy_context in cls_or_tup.py__iter__(): for context in lazy_context.infer(): context_set |= context.execute_evaluated() diff --git a/pythonFiles/jedi/evaluate/flow_analysis.py b/pythonFiles/jedi/evaluate/flow_analysis.py index 670b7a71934c..946f5a2ecaf0 100644 --- a/pythonFiles/jedi/evaluate/flow_analysis.py +++ b/pythonFiles/jedi/evaluate/flow_analysis.py @@ -1,4 +1,5 @@ from jedi.parser_utils import get_flow_branch_keyword, is_scope, get_parent_scope +from jedi.evaluate.recursion import execution_allowed class Status(object): @@ -104,9 +105,13 @@ def _break_check(context, context_scope, flow_scope, node): def _check_if(context, node): - types = context.eval_node(node) - values = set(x.py__bool__() for x in types) - if len(values) == 1: - return Status.lookup_table[values.pop()] - else: - return UNSURE + with execution_allowed(context.evaluator, node) as allowed: + if not allowed: + return UNSURE + + types = context.eval_node(node) + values = set(x.py__bool__() for x in types) + if len(values) == 1: + return Status.lookup_table[values.pop()] + else: + return UNSURE diff --git a/pythonFiles/jedi/evaluate/helpers.py b/pythonFiles/jedi/evaluate/helpers.py index 3b21e01bda9e..c6226cde2b70 100644 --- a/pythonFiles/jedi/evaluate/helpers.py +++ b/pythonFiles/jedi/evaluate/helpers.py @@ -176,8 +176,6 @@ def get_module_names(module, all_scopes): @contextmanager def predefine_names(context, flow_scope, dct): predefined = context.predefined_names - if flow_scope in predefined: - raise NotImplementedError('Why does this happen?') predefined[flow_scope] = dct try: yield @@ -190,12 +188,27 @@ def is_compiled(context): def is_string(context): - return is_compiled(context) and isinstance(context.obj, (str, unicode)) + if context.evaluator.environment.version_info.major == 2: + str_classes = (unicode, bytes) + else: + str_classes = (unicode,) + return is_compiled(context) and isinstance(context.get_safe_value(default=None), str_classes) def is_literal(context): return is_number(context) or is_string(context) +def _get_safe_value_or_none(context, accept): + if is_compiled(context): + value = context.get_safe_value(default=None) + if isinstance(value, accept): + return value + + +def get_int_or_none(context): + return _get_safe_value_or_none(context, int) + + def is_number(context): - return is_compiled(context) and isinstance(context.obj, (int, float)) + return _get_safe_value_or_none(context, (int, float)) is not None diff --git a/pythonFiles/jedi/evaluate/imports.py b/pythonFiles/jedi/evaluate/imports.py index ecf656b1a676..bcd3bdc74a4d 100644 --- a/pythonFiles/jedi/evaluate/imports.py +++ b/pythonFiles/jedi/evaluate/imports.py @@ -9,31 +9,48 @@ correct implementation is delegated to _compatibility. This module also supports import autocompletion, which means to complete -statements like ``from datetim`` (curser at the end would return ``datetime``). +statements like ``from datetim`` (cursor at the end would return ``datetime``). """ -import imp import os -import pkgutil -import sys from parso.python import tree from parso.tree import search_ancestor -from parso.cache import parser_cache from parso import python_bytes_to_unicode -from jedi._compatibility import find_module, unicode, ImplicitNSInfo +from jedi._compatibility import unicode, ImplicitNSInfo, force_unicode from jedi import debug from jedi import settings +from jedi.parser_utils import get_cached_code_lines from jedi.evaluate import sys_path from jedi.evaluate import helpers from jedi.evaluate import compiled from jedi.evaluate import analysis -from jedi.evaluate.utils import unite +from jedi.evaluate.utils import unite, dotted_from_fs_path from jedi.evaluate.cache import evaluator_method_cache from jedi.evaluate.filters import AbstractNameDefinition from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS +class ModuleCache(object): + def __init__(self): + self._path_cache = {} + self._name_cache = {} + + def add(self, module, name): + path = module.py__file__() + self._path_cache[path] = module + self._name_cache[name] = module + + def iterate_modules_with_names(self): + return self._name_cache.items() + + def get(self, name): + return self._name_cache[name] + + def get_from_path(self, path): + return self._path_cache[path] + + # This memoization is needed, because otherwise we will infinitely loop on # certain imports. @evaluator_method_cache(default=NO_CONTEXTS) @@ -130,20 +147,13 @@ def __repr__(self): def _add_error(context, name, message=None): # Should be a name, not a string! + if message is None: + name_str = str(name.value) if isinstance(name, tree.Name) else name + message = 'No module named ' + name_str if hasattr(name, 'parent'): analysis.add(context, 'import-error', name, message) - - -def get_init_path(directory_path): - """ - The __init__ file can be searched in a directory. If found return it, else - None. - """ - for suffix, _, _ in imp.get_suffixes(): - path = os.path.join(directory_path, '__init__' + suffix) - if os.path.exists(path): - return path - return None + else: + debug.warning('ImportError without origin: ' + message) class ImportName(AbstractNameDefinition): @@ -204,7 +214,7 @@ def __init__(self, evaluator, import_path, module_context, level=0): if level: base = module_context.py__package__().split('.') - if base == ['']: + if base == [''] or base == ['__main__']: base = [] if level > len(base): path = module_context.py__file__() @@ -226,10 +236,11 @@ def __init__(self, evaluator, import_path, module_context, level=0): else: import_path.insert(0, dir_name) else: - _add_error(module_context, import_path[-1]) + _add_error( + module_context, import_path[-1], + message='Attempted relative import beyond top-level package.' + ) import_path = [] - # TODO add import error. - debug.warning('Attempted relative import beyond top-level package.') # If no path is defined in the module we have no ideas where we # are in the file system. Therefore we cannot know what to do. # In this case we just let the path there and ignore that it's @@ -248,27 +259,19 @@ def str_import_path(self): """Returns the import path as pure strings instead of `Name`.""" return tuple( name.value if isinstance(name, tree.Name) else name - for name in self.import_path) + for name in self.import_path + ) def sys_path_with_modifications(self): - in_path = [] - sys_path_mod = self._evaluator.project.sys_path \ + sys_path_mod = self._evaluator.get_sys_path() \ + sys_path.check_sys_path_modifications(self.module_context) - if self.file_path is not None: - # If you edit e.g. gunicorn, there will be imports like this: - # `from gunicorn import something`. But gunicorn is not in the - # sys.path. Therefore look if gunicorn is a parent directory, #56. - if self.import_path: # TODO is this check really needed? - for path in sys_path.traverse_parents(self.file_path): - if os.path.basename(path) == self.str_import_path[0]: - in_path.append(os.path.dirname(path)) - - # Since we know nothing about the call location of the sys.path, - # it's a possibility that the current directory is the origin of - # the Python execution. - sys_path_mod.insert(0, os.path.dirname(self.file_path)) - - return in_path + sys_path_mod + + if self.import_path and self.file_path is not None \ + and self._evaluator.environment.version_info.major == 2: + # Python2 uses an old strange way of importing relative imports. + sys_path_mod.append(force_unicode(os.path.dirname(self.file_path))) + + return sys_path_mod def follow(self): if not self.import_path: @@ -280,7 +283,7 @@ def _do_import(self, import_path, sys_path): This method is very similar to importlib's `_gcd_import`. """ import_parts = [ - i.value if isinstance(i, tree.Name) else i + force_unicode(i.value if isinstance(i, tree.Name) else i) for i in import_path ] @@ -298,7 +301,7 @@ def _do_import(self, import_path, sys_path): module_name = '.'.join(import_parts) try: - return ContextSet(self._evaluator.modules[module_name]) + return ContextSet(self._evaluator.module_cache.get(module_name)) except KeyError: pass @@ -332,62 +335,43 @@ def _do_import(self, import_path, sys_path): for path in paths: # At the moment we are only using one path. So this is # not important to be correct. - try: - if not isinstance(path, list): - path = [path] - module_file, module_path, is_pkg = \ - find_module(import_parts[-1], path, fullname=module_name) + if not isinstance(path, list): + path = [path] + code, module_path, is_pkg = self._evaluator.compiled_subprocess.get_module_info( + string=import_parts[-1], + path=path, + full_name=module_name + ) + if module_path is not None: break - except ImportError: - module_path = None - if module_path is None: + else: _add_error(self.module_context, import_path[-1]) return NO_CONTEXTS else: - parent_module = None - try: - debug.dbg('search_module %s in %s', import_parts[-1], self.file_path) - # Override the sys.path. It works only good that way. - # Injecting the path directly into `find_module` did not work. - sys.path, temp = sys_path, sys.path - try: - module_file, module_path, is_pkg = \ - find_module(import_parts[-1], fullname=module_name) - finally: - sys.path = temp - except ImportError: + debug.dbg('search_module %s in %s', import_parts[-1], self.file_path) + # Override the sys.path. It works only good that way. + # Injecting the path directly into `find_module` did not work. + code, module_path, is_pkg = self._evaluator.compiled_subprocess.get_module_info( + string=import_parts[-1], + full_name=module_name, + sys_path=sys_path, + ) + if module_path is None: # The module is not a package. _add_error(self.module_context, import_path[-1]) return NO_CONTEXTS - code = None - if is_pkg: - # In this case, we don't have a file yet. Search for the - # __init__ file. - if module_path.endswith(('.zip', '.egg')): - code = module_file.loader.get_source(module_name) - else: - module_path = get_init_path(module_path) - elif module_file: - code = module_file.read() - module_file.close() - - if isinstance(module_path, ImplicitNSInfo): - from jedi.evaluate.context.namespace import ImplicitNamespaceContext - fullname, paths = module_path.name, module_path.paths - module = ImplicitNamespaceContext(self._evaluator, fullname=fullname) - module.paths = paths - elif module_file is None and not module_path.endswith(('.py', '.zip', '.egg')): - module = compiled.load_module(self._evaluator, module_path) - else: - module = _load_module(self._evaluator, module_path, code, sys_path, parent_module) + module = _load_module( + self._evaluator, module_path, code, sys_path, + module_name=module_name, + safe_module_name=True, + ) if module is None: # The file might raise an ImportError e.g. and therefore not be # importable. return NO_CONTEXTS - self._evaluator.modules[module_name] = module return ContextSet(module) def _generate_name(self, name, in_module=None): @@ -401,15 +385,17 @@ def _get_module_names(self, search_path=None, in_module=None): Get the names of all modules in the search_path. This means file names and not names defined in the files. """ + sub = self._evaluator.compiled_subprocess names = [] # add builtin module names if search_path is None and in_module is None: - names += [self._generate_name(name) for name in sys.builtin_module_names] + names += [self._generate_name(name) for name in sub.get_builtin_module_names()] if search_path is None: search_path = self.sys_path_with_modifications() - for module_loader, name, is_pkg in pkgutil.iter_modules(search_path): + + for name in sub.list_module_names(search_path): names.append(self._generate_name(name, in_module=in_module)) return names @@ -448,7 +434,7 @@ def completion_names(self, evaluator, only_modules=False): # implicit namespace packages elif isinstance(context, ImplicitNamespaceContext): paths = context.paths - names += self._get_module_names(paths) + names += self._get_module_names(paths, in_module=context) if only_modules: # In the case of an import like `from x.` we don't need to @@ -476,38 +462,65 @@ def completion_names(self, evaluator, only_modules=False): return names -def _load_module(evaluator, path=None, code=None, sys_path=None, parent_module=None): - if sys_path is None: - sys_path = evaluator.project.sys_path +def _load_module(evaluator, path=None, code=None, sys_path=None, + module_name=None, safe_module_name=False): + try: + return evaluator.module_cache.get(module_name) + except KeyError: + pass + try: + return evaluator.module_cache.get_from_path(path) + except KeyError: + pass - dotted_path = path and compiled.dotted_from_fs_path(path, sys_path) - if path is not None and path.endswith(('.py', '.zip', '.egg')) \ - and dotted_path not in settings.auto_import_modules: + if isinstance(path, ImplicitNSInfo): + from jedi.evaluate.context.namespace import ImplicitNamespaceContext + module = ImplicitNamespaceContext( + evaluator, + fullname=path.name, + paths=path.paths, + ) + else: + if sys_path is None: + sys_path = evaluator.get_sys_path() + + dotted_path = path and dotted_from_fs_path(path, sys_path) + if path is not None and path.endswith(('.py', '.zip', '.egg')) \ + and dotted_path not in settings.auto_import_modules: + + module_node = evaluator.parse( + code=code, path=path, cache=True, diff_cache=True, + cache_path=settings.cache_directory) + + from jedi.evaluate.context import ModuleContext + module = ModuleContext( + evaluator, module_node, + path=path, + code_lines=get_cached_code_lines(evaluator.grammar, path), + ) + else: + module = compiled.load_module(evaluator, path=path, sys_path=sys_path) - module_node = evaluator.grammar.parse( - code=code, path=path, cache=True, diff_cache=True, - cache_path=settings.cache_directory) + if module is not None and module_name is not None: + add_module_to_cache(evaluator, module_name, module, safe=safe_module_name) - from jedi.evaluate.context import ModuleContext - return ModuleContext(evaluator, module_node, path=path) - else: - return compiled.load_module(evaluator, path) + return module -def add_module(evaluator, module_name, module): - if '.' not in module_name: +def add_module_to_cache(evaluator, module_name, module, safe=False): + if not safe and '.' not in module_name: # We cannot add paths with dots, because that would collide with # the sepatator dots for nested packages. Therefore we return # `__main__` in ModuleWrapper.py__name__(), which is similar to # Python behavior. - evaluator.modules[module_name] = module + return + evaluator.module_cache.add(module, module_name) def get_modules_containing_name(evaluator, modules, name): """ Search a name in the directories of modules. """ - from jedi.evaluate.context import ModuleContext def check_directories(paths): for p in paths: if p is not None: @@ -519,28 +532,16 @@ def check_directories(paths): if file_name.endswith('.py'): yield path - def check_python_file(path): - try: - # TODO I don't think we should use the cache here?! - node_cache_item = parser_cache[evaluator.grammar._hashed][path] - except KeyError: - try: - return check_fs(path) - except IOError: - return None - else: - module_node = node_cache_item.node - return ModuleContext(evaluator, module_node, path=path) - def check_fs(path): with open(path, 'rb') as f: code = python_bytes_to_unicode(f.read(), errors='replace') if name in code: - module = _load_module(evaluator, path, code) - - module_name = sys_path.dotted_path_in_sys_path(evaluator.project.sys_path, path) - if module_name is not None: - add_module(evaluator, module_name, module) + e_sys_path = evaluator.get_sys_path() + module_name = sys_path.dotted_path_in_sys_path(e_sys_path, path) + module = _load_module( + evaluator, path, code, + sys_path=e_sys_path, module_name=module_name + ) return module # skip non python modules @@ -565,6 +566,6 @@ def check_fs(path): # Sort here to make issues less random. for p in sorted(paths): # make testing easier, sort it - same results on every interpreter - m = check_python_file(p) + m = check_fs(p) if m is not None and not isinstance(m, compiled.CompiledObject): yield m diff --git a/pythonFiles/jedi/evaluate/param.py b/pythonFiles/jedi/evaluate/param.py index a46394ce8a81..1445ef0c8ff6 100644 --- a/pythonFiles/jedi/evaluate/param.py +++ b/pythonFiles/jedi/evaluate/param.py @@ -97,7 +97,7 @@ def get_params(execution_context, var_args): var_arg_iterator.push_back((key, argument)) break lazy_context_list.append(argument) - seq = iterable.FakeSequence(execution_context.evaluator, 'tuple', lazy_context_list) + seq = iterable.FakeSequence(execution_context.evaluator, u'tuple', lazy_context_list) result_arg = LazyKnownContext(seq) elif param.star_count == 2: # **kwargs param @@ -176,7 +176,7 @@ def _error_argument_count(funcdef, actual_count): def _create_default_param(execution_context, param): if param.star_count == 1: result_arg = LazyKnownContext( - iterable.FakeSequence(execution_context.evaluator, 'tuple', []) + iterable.FakeSequence(execution_context.evaluator, u'tuple', []) ) elif param.star_count == 2: result_arg = LazyKnownContext( @@ -192,4 +192,3 @@ def _create_default_param(execution_context, param): def create_default_params(execution_context, funcdef): return [_create_default_param(execution_context, p) for p in funcdef.get_params()] - diff --git a/pythonFiles/jedi/evaluate/pep0484.py b/pythonFiles/jedi/evaluate/pep0484.py index 820f112c54e0..f23943e1a8a5 100644 --- a/pythonFiles/jedi/evaluate/pep0484.py +++ b/pythonFiles/jedi/evaluate/pep0484.py @@ -22,16 +22,17 @@ import os import re -from parso import ParserSyntaxError +from parso import ParserSyntaxError, parse, split_lines from parso.python import tree +from jedi._compatibility import unicode, force_unicode from jedi.evaluate.cache import evaluator_method_cache from jedi.evaluate import compiled from jedi.evaluate.base_context import NO_CONTEXTS, ContextSet from jedi.evaluate.lazy_context import LazyTreeContext from jedi.evaluate.context import ModuleContext +from jedi.evaluate.helpers import is_string from jedi import debug -from jedi import _compatibility from jedi import parser_utils @@ -41,17 +42,23 @@ def _evaluate_for_annotation(context, annotation, index=None): If index is not None, the annotation is expected to be a tuple and we're interested in that index """ - if annotation is not None: - context_set = context.eval_node(_fix_forward_reference(context, annotation)) - if index is not None: - context_set = context_set.filter( - lambda context: context.array_type == 'tuple' \ - and len(list(context.py__iter__())) >= index - ).py__getitem__(index) - return context_set.execute_evaluated() - else: + context_set = context.eval_node(_fix_forward_reference(context, annotation)) + return context_set.execute_evaluated() + + +def _evaluate_annotation_string(context, string, index=None): + node = _get_forward_reference_node(context, string) + if node is None: return NO_CONTEXTS + context_set = context.eval_node(node) + if index is not None: + context_set = context_set.filter( + lambda context: context.array_type == u'tuple' + and len(list(context.py__iter__())) >= index + ).py__getitem__(index) + return context_set.execute_evaluated() + def _fix_forward_reference(context, node): evaled_nodes = context.eval_node(node) @@ -59,30 +66,111 @@ def _fix_forward_reference(context, node): debug.warning("Eval'ed typing index %s should lead to 1 object, " " not %s" % (node, evaled_nodes)) return node - evaled_node = list(evaled_nodes)[0] - if isinstance(evaled_node, compiled.CompiledObject) and \ - isinstance(evaled_node.obj, str): - try: - new_node = context.evaluator.grammar.parse( - _compatibility.unicode(evaled_node.obj), - start_symbol='eval_input', - error_recovery=False - ) - except ParserSyntaxError: - debug.warning('Annotation not parsed: %s' % evaled_node.obj) - return node - else: - module = node.get_root_node() - parser_utils.move(new_node, module.end_pos[0]) - new_node.parent = context.tree_node - return new_node + + evaled_context = list(evaled_nodes)[0] + if is_string(evaled_context): + result = _get_forward_reference_node(context, evaled_context.get_safe_value()) + if result is not None: + return result + + return node + + +def _get_forward_reference_node(context, string): + try: + new_node = context.evaluator.grammar.parse( + force_unicode(string), + start_symbol='eval_input', + error_recovery=False + ) + except ParserSyntaxError: + debug.warning('Annotation not parsed: %s' % string) + return None else: - return node + module = context.tree_node.get_root_node() + parser_utils.move(new_node, module.end_pos[0]) + new_node.parent = context.tree_node + return new_node + + +def _split_comment_param_declaration(decl_text): + """ + Split decl_text on commas, but group generic expressions + together. + + For example, given "foo, Bar[baz, biz]" we return + ['foo', 'Bar[baz, biz]']. + + """ + try: + node = parse(decl_text, error_recovery=False).children[0] + except ParserSyntaxError: + debug.warning('Comment annotation is not valid Python: %s' % decl_text) + return [] + + if node.type == 'name': + return [node.get_code().strip()] + + params = [] + try: + children = node.children + except AttributeError: + return [] + else: + for child in children: + if child.type in ['name', 'atom_expr', 'power']: + params.append(child.get_code().strip()) + + return params @evaluator_method_cache() def infer_param(execution_context, param): + """ + Infers the type of a function parameter, using type annotations. + """ annotation = param.annotation + if annotation is None: + # If no Python 3-style annotation, look for a Python 2-style comment + # annotation. + # Identify parameters to function in the same sequence as they would + # appear in a type comment. + all_params = [child for child in param.parent.children + if child.type == 'param'] + + node = param.parent.parent + comment = parser_utils.get_following_comment_same_line(node) + if comment is None: + return NO_CONTEXTS + + match = re.match(r"^#\s*type:\s*\(([^#]*)\)\s*->", comment) + if not match: + return NO_CONTEXTS + params_comments = _split_comment_param_declaration(match.group(1)) + + # Find the specific param being investigated + index = all_params.index(param) + # If the number of parameters doesn't match length of type comment, + # ignore first parameter (assume it's self). + if len(params_comments) != len(all_params): + debug.warning( + "Comments length != Params length %s %s", + params_comments, all_params + ) + from jedi.evaluate.context.instance import BaseInstanceFunctionExecution + if isinstance(execution_context, BaseInstanceFunctionExecution): + if index == 0: + # Assume it's self, which is already handled + return NO_CONTEXTS + index -= 1 + if index >= len(params_comments): + return NO_CONTEXTS + + param_comment = params_comments[index] + return _evaluate_annotation_string( + execution_context.get_root_context(), + param_comment + ) module_context = execution_context.get_root_context() return _evaluate_for_annotation(module_context, annotation) @@ -102,12 +190,33 @@ def py__annotations__(funcdef): @evaluator_method_cache() def infer_return_types(function_context): + """ + Infers the type of a function's return value, + according to type annotations. + """ annotation = py__annotations__(function_context.tree_node).get("return", None) + if annotation is None: + # If there is no Python 3-type annotation, look for a Python 2-type annotation + node = function_context.tree_node + comment = parser_utils.get_following_comment_same_line(node) + if comment is None: + return NO_CONTEXTS + + match = re.match(r"^#\s*type:\s*\([^#]*\)\s*->\s*([^#]*)", comment) + if not match: + return NO_CONTEXTS + + return _evaluate_annotation_string( + function_context.get_root_context(), + match.group(1).strip() + ) + module_context = function_context.get_root_context() return _evaluate_for_annotation(module_context, annotation) _typing_module = None +_typing_module_code_lines = None def _get_typing_replacement_module(grammar): @@ -115,14 +224,15 @@ def _get_typing_replacement_module(grammar): The idea is to return our jedi replacement for the PEP-0484 typing module as discussed at https://github.com/davidhalter/jedi/issues/663 """ - global _typing_module + global _typing_module, _typing_module_code_lines if _typing_module is None: typing_path = \ os.path.abspath(os.path.join(__file__, "../jedi_typing.py")) with open(typing_path) as f: - code = _compatibility.unicode(f.read()) + code = unicode(f.read()) _typing_module = grammar.parse(code) - return _typing_module + _typing_module_code_lines = split_lines(code, keepends=True) + return _typing_module, _typing_module_code_lines def py__getitem__(context, typ, node): @@ -152,10 +262,12 @@ def py__getitem__(context, typ, node): # check for the instance typing._Optional (Python 3.6). return context.eval_node(nodes[0]) + module_node, code_lines = _get_typing_replacement_module(context.evaluator.latest_grammar) typing = ModuleContext( context.evaluator, - module_node=_get_typing_replacement_module(context.evaluator.latest_grammar), - path=None + module_node=module_node, + path=None, + code_lines=code_lines, ) factories = typing.py__getattribute__("factory") assert len(factories) == 1 @@ -167,12 +279,12 @@ def py__getitem__(context, typ, node): if isinstance(child, tree.Class)) if type_name not in valid_classnames: return None - compiled_classname = compiled.create(context.evaluator, type_name) + compiled_classname = compiled.create_simple_object(context.evaluator, type_name) from jedi.evaluate.context.iterable import FakeSequence args = FakeSequence( context.evaluator, - "tuple", + u'tuple', [LazyTreeContext(context, n) for n in nodes] ) @@ -213,10 +325,6 @@ def _find_type_from_comment_hint(context, node, varlist, name): if comment is None: return [] match = re.match(r"^#\s*type:\s*([^#]*)", comment) - if not match: + if match is None: return [] - annotation = tree.String( - repr(str(match.group(1).strip())), - node.start_pos) - annotation.parent = node.parent - return _evaluate_for_annotation(context, annotation, index) + return _evaluate_annotation_string(context, match.group(1).strip(), index) diff --git a/pythonFiles/jedi/evaluate/project.py b/pythonFiles/jedi/evaluate/project.py deleted file mode 100644 index b90f0f0c3be8..000000000000 --- a/pythonFiles/jedi/evaluate/project.py +++ /dev/null @@ -1,40 +0,0 @@ -import os -import sys - -from jedi.evaluate.sys_path import get_venv_path, detect_additional_paths -from jedi.cache import underscore_memoization - - -class Project(object): - def __init__(self, sys_path=None): - if sys_path is not None: - self._sys_path = sys_path - - venv = os.getenv('VIRTUAL_ENV') - if venv: - sys_path = get_venv_path(venv) - - if sys_path is None: - sys_path = sys.path - - base_sys_path = list(sys_path) - try: - base_sys_path.remove('') - except ValueError: - pass - - self._base_sys_path = base_sys_path - - def add_script_path(self, script_path): - self._script_path = script_path - - def add_evaluator(self, evaluator): - self._evaluator = evaluator - - @property - @underscore_memoization - def sys_path(self): - if self._script_path is None: - return self._base_sys_path - - return self._base_sys_path + detect_additional_paths(self._evaluator, self._script_path) diff --git a/pythonFiles/jedi/evaluate/recursion.py b/pythonFiles/jedi/evaluate/recursion.py index e2f34a4a9a06..5be3f8be4d29 100644 --- a/pythonFiles/jedi/evaluate/recursion.py +++ b/pythonFiles/jedi/evaluate/recursion.py @@ -49,6 +49,7 @@ A function may not be executed more than this number of times recursively. """ + class RecursionDetector(object): def __init__(self): self.pushed_nodes = [] @@ -67,9 +68,11 @@ def execution_allowed(evaluator, node): node.start_pos) yield False else: - pushed_nodes.append(node) - yield True - pushed_nodes.pop() + try: + pushed_nodes.append(node) + yield True + finally: + pushed_nodes.pop() def execution_recursion_decorator(default=NO_CONTEXTS): @@ -113,7 +116,7 @@ def push_execution(self, execution): self._parent_execution_funcs.append(funcdef) module = execution.get_root_context() - if module == self._evaluator.BUILTINS: + if module == self._evaluator.builtins_module: # We have control over builtins so we know they are not recursing # like crazy. Therefore we just let them execute always, because # they usually just help a lot with getting good results. diff --git a/pythonFiles/jedi/evaluate/site.py b/pythonFiles/jedi/evaluate/site.py deleted file mode 100644 index bf884faefaaf..000000000000 --- a/pythonFiles/jedi/evaluate/site.py +++ /dev/null @@ -1,110 +0,0 @@ -"""An adapted copy of relevant site-packages functionality from Python stdlib. - -This file contains some functions related to handling site-packages in Python -with jedi-specific modifications: - -- the functions operate on sys_path argument rather than global sys.path - -- in .pth files "import ..." lines that allow execution of arbitrary code are - skipped to prevent code injection into jedi interpreter - -""" - -# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, -# 2011, 2012, 2013, 2014, 2015 Python Software Foundation; All Rights Reserved - -from __future__ import print_function - -import sys -import os - - -def makepath(*paths): - dir = os.path.join(*paths) - try: - dir = os.path.abspath(dir) - except OSError: - pass - return dir, os.path.normcase(dir) - - -def _init_pathinfo(sys_path): - """Return a set containing all existing directory entries from sys_path""" - d = set() - for dir in sys_path: - try: - if os.path.isdir(dir): - dir, dircase = makepath(dir) - d.add(dircase) - except TypeError: - continue - return d - - -def addpackage(sys_path, sitedir, name, known_paths): - """Process a .pth file within the site-packages directory: - For each line in the file, either combine it with sitedir to a path - and add that to known_paths, or execute it if it starts with 'import '. - """ - if known_paths is None: - known_paths = _init_pathinfo(sys_path) - reset = 1 - else: - reset = 0 - fullname = os.path.join(sitedir, name) - try: - f = open(fullname, "r") - except OSError: - return - with f: - for n, line in enumerate(f): - if line.startswith("#"): - continue - try: - if line.startswith(("import ", "import\t")): - # Change by immerrr: don't evaluate import lines to prevent - # code injection into jedi through pth files. - # - # exec(line) - continue - line = line.rstrip() - dir, dircase = makepath(sitedir, line) - if not dircase in known_paths and os.path.exists(dir): - sys_path.append(dir) - known_paths.add(dircase) - except Exception: - print("Error processing line {:d} of {}:\n".format(n+1, fullname), - file=sys.stderr) - import traceback - for record in traceback.format_exception(*sys.exc_info()): - for line in record.splitlines(): - print(' '+line, file=sys.stderr) - print("\nRemainder of file ignored", file=sys.stderr) - break - if reset: - known_paths = None - return known_paths - - -def addsitedir(sys_path, sitedir, known_paths=None): - """Add 'sitedir' argument to sys_path if missing and handle .pth files in - 'sitedir'""" - if known_paths is None: - known_paths = _init_pathinfo(sys_path) - reset = 1 - else: - reset = 0 - sitedir, sitedircase = makepath(sitedir) - if not sitedircase in known_paths: - sys_path.append(sitedir) # Add path component - known_paths.add(sitedircase) - try: - names = os.listdir(sitedir) - except OSError: - return - names = [name for name in names if name.endswith(".pth")] - for name in sorted(names): - addpackage(sys_path, sitedir, name, known_paths) - if reset: - known_paths = None - return known_paths diff --git a/pythonFiles/jedi/evaluate/stdlib.py b/pythonFiles/jedi/evaluate/stdlib.py index 259d692733db..06296d938183 100644 --- a/pythonFiles/jedi/evaluate/stdlib.py +++ b/pythonFiles/jedi/evaluate/stdlib.py @@ -9,9 +9,11 @@ the standard library. The usual way to understand the standard library is the compiled module that returns the types for C-builtins. """ -import collections import re +import parso + +from jedi._compatibility import force_unicode from jedi import debug from jedi.evaluate.arguments import ValuesArguments from jedi.evaluate import analysis @@ -57,7 +59,7 @@ def execute(evaluator, obj, arguments): except AttributeError: pass else: - if obj.parent_context == evaluator.BUILTINS: + if obj.parent_context == evaluator.builtins_module: module_name = 'builtins' elif isinstance(obj.parent_context, ModuleContext): module_name = obj.parent_context.name.string_name @@ -107,10 +109,11 @@ def argument_clinic(string, want_obj=False, want_context=False, want_arguments=F def f(func): def wrapper(evaluator, obj, arguments): debug.dbg('builtin start %s' % obj, color='MAGENTA') + result = NO_CONTEXTS try: lst = list(arguments.eval_argument_clinic(clinic_args)) except ValueError: - return NO_CONTEXTS + pass else: kwargs = {} if want_context: @@ -119,9 +122,10 @@ def wrapper(evaluator, obj, arguments): kwargs['obj'] = obj if want_arguments: kwargs['arguments'] = arguments - return func(evaluator, *lst, **kwargs) + result = func(evaluator, *lst, **kwargs) finally: - debug.dbg('builtin end', color='MAGENTA') + debug.dbg('builtin end: %s', result, color='MAGENTA') + return result return wrapper return f @@ -133,7 +137,7 @@ def builtins_next(evaluator, iterators, defaults): TODO this function is currently not used. It's a stab at implementing next in a different way than fake objects. This would be a bit more flexible. """ - if evaluator.python_version[0] == 2: + if evaluator.environment.version_info.major == 2: name = 'next' else: name = '__next__' @@ -157,7 +161,7 @@ def builtins_getattr(evaluator, objects, names, defaults=None): for obj in objects: for name in names: if is_string(name): - return obj.py__getattribute__(name.obj) + return obj.py__getattribute__(force_unicode(name.get_safe_value())) else: debug.warning('getattr called without str') continue @@ -207,22 +211,24 @@ def builtins_reversed(evaluator, sequences, obj, arguments): # necessary, because `reversed` is a function and autocompletion # would fail in certain cases like `reversed(x).__iter__` if we # just returned the result directly. - seq = iterable.FakeSequence(evaluator, 'list', rev) + seq = iterable.FakeSequence(evaluator, u'list', rev) arguments = ValuesArguments([ContextSet(seq)]) - return ContextSet(CompiledInstance(evaluator, evaluator.BUILTINS, obj, arguments)) + return ContextSet(CompiledInstance(evaluator, evaluator.builtins_module, obj, arguments)) @argument_clinic('obj, type, /', want_arguments=True) def builtins_isinstance(evaluator, objects, types, arguments): bool_results = set() for o in objects: + cls = o.py__class__() try: - mro_func = o.py__class__().py__mro__ + mro_func = cls.py__mro__ except AttributeError: # This is temporary. Everything should have a class attribute in # Python?! Maybe we'll leave it here, because some numpy objects or # whatever might not. - return ContextSet(compiled.create(True), compiled.create(False)) + bool_results = set([True, False]) + break mro = mro_func() @@ -230,7 +236,7 @@ def builtins_isinstance(evaluator, objects, types, arguments): if cls_or_tup.is_class(): bool_results.add(cls_or_tup in mro) elif cls_or_tup.name.string_name == 'tuple' \ - and cls_or_tup.get_root_context() == evaluator.BUILTINS: + and cls_or_tup.get_root_context() == evaluator.builtins_module: # Check for tuples. classes = ContextSet.from_sets( lazy_context.infer() @@ -246,7 +252,10 @@ def builtins_isinstance(evaluator, objects, types, arguments): 'not %s.' % cls_or_tup analysis.add(lazy_context._context, 'type-error-isinstance', node, message) - return ContextSet.from_iterable(compiled.create(evaluator, x) for x in bool_results) + return ContextSet.from_iterable( + compiled.builtin_from_name(evaluator, force_unicode(str(b))) + for b in bool_results + ) def collections_namedtuple(evaluator, obj, arguments): @@ -256,45 +265,54 @@ def collections_namedtuple(evaluator, obj, arguments): This has to be done by processing the namedtuple class template and evaluating the result. - .. note:: |jedi| only supports namedtuples on Python >2.6. - """ - # Namedtuples are not supported on Python 2.6 - if not hasattr(collections, '_class_template'): + collections_context = obj.parent_context + _class_template_set = collections_context.py__getattribute__(u'_class_template') + if not _class_template_set: + # Namedtuples are not supported on Python 2.6, early 2.7, because the + # _class_template variable is not defined, there. return NO_CONTEXTS # Process arguments # TODO here we only use one of the types, we should use all. - name = list(_follow_param(evaluator, arguments, 0))[0].obj + # TODO this is buggy, doesn't need to be a string + name = list(_follow_param(evaluator, arguments, 0))[0].get_safe_value() _fields = list(_follow_param(evaluator, arguments, 1))[0] if isinstance(_fields, compiled.CompiledObject): - fields = _fields.obj.replace(',', ' ').split() - elif isinstance(_fields, iterable.AbstractIterable): + fields = _fields.get_safe_value().replace(',', ' ').split() + elif isinstance(_fields, iterable.Sequence): fields = [ - v.obj + v.get_safe_value() for lazy_context in _fields.py__iter__() - for v in lazy_context.infer() if hasattr(v, 'obj') + for v in lazy_context.infer() if is_string(v) ] else: return NO_CONTEXTS - base = collections._class_template + def get_var(name): + x, = collections_context.py__getattribute__(name) + return x.get_safe_value() + + base = next(iter(_class_template_set)).get_safe_value() base += _NAMEDTUPLE_INIT - # Build source - source = base.format( + # Build source code + code = base.format( typename=name, field_names=tuple(fields), num_fields=len(fields), - arg_list = repr(tuple(fields)).replace("'", "")[1:-1], - repr_fmt=', '.join(collections._repr_template.format(name=name) for name in fields), - field_defs='\n'.join(collections._field_template.format(index=index, name=name) + arg_list=repr(tuple(fields)).replace("u'", "").replace("'", "")[1:-1], + repr_fmt=', '.join(get_var(u'_repr_template').format(name=name) for name in fields), + field_defs='\n'.join(get_var(u'_field_template').format(index=index, name=name) for index, name in enumerate(fields)) ) - # Parse source - module = evaluator.grammar.parse(source) + # Parse source code + module = evaluator.grammar.parse(code) generated_class = next(module.iter_classdefs()) - parent_context = ModuleContext(evaluator, module, '') + parent_context = ModuleContext( + evaluator, module, None, + code_lines=parso.split_lines(code, keepends=True), + ) return ContextSet(ClassContext(evaluator, parent_context, generated_class)) diff --git a/pythonFiles/jedi/evaluate/syntax_tree.py b/pythonFiles/jedi/evaluate/syntax_tree.py index 1d847a4960de..4efe845d476d 100644 --- a/pythonFiles/jedi/evaluate/syntax_tree.py +++ b/pythonFiles/jedi/evaluate/syntax_tree.py @@ -2,10 +2,10 @@ Functions evaluating the syntax tree. """ import copy -import operator as op from parso.python import tree +from jedi._compatibility import force_unicode, unicode from jedi import debug from jedi import parser_utils from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS, ContextualizedNode, \ @@ -17,11 +17,13 @@ from jedi.evaluate import analysis from jedi.evaluate import imports from jedi.evaluate import arguments +from jedi.evaluate.pep0484 import _evaluate_for_annotation from jedi.evaluate.context import ClassContext, FunctionContext from jedi.evaluate.context import iterable from jedi.evaluate.context import TreeInstance, CompiledInstance from jedi.evaluate.finder import NameFinder from jedi.evaluate.helpers import is_string, is_literal, is_number, is_compiled +from jedi.evaluate.compiled.access import COMPARISON_OPERATORS def _limit_context_infers(func): @@ -48,13 +50,25 @@ def wrapper(context, *args, **kwargs): return wrapper +def _py__stop_iteration_returns(generators): + results = ContextSet() + for generator in generators: + try: + method = generator.py__stop_iteration_returns + except AttributeError: + debug.warning('%s is not actually a generator', generator) + else: + results |= method() + return results + + @debug.increase_indent @_limit_context_infers def eval_node(context, element): - debug.dbg('eval_element %s@%s', element, element.start_pos) + debug.dbg('eval_node %s@%s', element, element.start_pos) evaluator = context.evaluator typ = element.type - if typ in ('name', 'number', 'string', 'atom'): + if typ in ('name', 'number', 'string', 'atom', 'strings'): return eval_atom(context, element) elif typ == 'keyword': # For False/True/None @@ -68,22 +82,33 @@ def eval_node(context, element): return eval_expr_stmt(context, element) elif typ in ('power', 'atom_expr'): first_child = element.children[0] - if not (first_child.type == 'keyword' and first_child.value == 'await'): - context_set = eval_atom(context, first_child) - for trailer in element.children[1:]: - if trailer == '**': # has a power operation. - right = evaluator.eval_element(context, element.children[2]) - context_set = _eval_comparison( - evaluator, - context, - context_set, - trailer, - right - ) - break - context_set = eval_trailer(context, context_set, trailer) - return context_set - return NO_CONTEXTS + children = element.children[1:] + had_await = False + if first_child.type == 'keyword' and first_child.value == 'await': + had_await = True + first_child = children.pop(0) + + context_set = eval_atom(context, first_child) + for trailer in children: + if trailer == '**': # has a power operation. + right = context.eval_node(children[1]) + context_set = _eval_comparison( + evaluator, + context, + context_set, + trailer, + right + ) + break + context_set = eval_trailer(context, context_set, trailer) + + if had_await: + await_context_set = context_set.py__getattribute__(u"__await__") + if not await_context_set: + debug.warning('Tried to run py__await__ on context %s', context) + context_set = ContextSet() + return _py__stop_iteration_returns(await_context_set.execute_evaluated()) + return context_set elif typ in ('testlist_star_expr', 'testlist',): # The implicit tuple in statements. return ContextSet(iterable.SequenceLiteralContext(evaluator, context, element)) @@ -100,8 +125,10 @@ def eval_node(context, element): # Must be an ellipsis, other operators are not evaluated. # In Python 2 ellipsis is coded as three single dot tokens, not # as one token 3 dot token. - assert element.value in ('.', '...') - return ContextSet(compiled.create(evaluator, Ellipsis)) + if element.value not in ('.', '...'): + origin = element.parent + raise AssertionError("unhandled operator %s in %s " % (repr(element.value), origin)) + return ContextSet(compiled.builtin_from_name(evaluator, u'Ellipsis')) elif typ == 'dotted_name': context_set = eval_atom(context, element.children[0]) for next_name in element.children[2::2]: @@ -112,6 +139,15 @@ def eval_node(context, element): return eval_node(context, element.children[0]) elif typ == 'annassign': return pep0484._evaluate_for_annotation(context, element.children[1]) + elif typ == 'yield_expr': + if len(element.children) and element.children[1].type == 'yield_arg': + # Implies that it's a yield from. + element = element.children[1].children[1] + generators = context.eval_node(element) + return _py__stop_iteration_returns(generators) + + # Generator.send() is not implemented. + return NO_CONTEXTS else: return eval_or_test(context, element) @@ -119,7 +155,7 @@ def eval_node(context, element): def eval_trailer(context, base_contexts, trailer): trailer_op, node = trailer.children[:2] if node == ')': # `arglist` is optional. - node = () + node = None if trailer_op == '[': trailer_op, node, _ = trailer.children @@ -148,7 +184,7 @@ def eval_trailer(context, base_contexts, trailer): name_or_str=node ) else: - assert trailer_op == '(' + assert trailer_op == '(', 'trailer_op is actually %s' % trailer_op args = arguments.TreeArguments(context.evaluator, context, node, trailer) return base_contexts.execute(args) @@ -173,19 +209,19 @@ def eval_atom(context, atom): ) elif isinstance(atom, tree.Literal): - string = parser_utils.safe_literal_eval(atom.value) - return ContextSet(compiled.create(context.evaluator, string)) + string = context.evaluator.compiled_subprocess.safe_literal_eval(atom.value) + return ContextSet(compiled.create_simple_object(context.evaluator, string)) + elif atom.type == 'strings': + # Will be multiple string. + context_set = eval_atom(context, atom.children[0]) + for string in atom.children[1:]: + right = eval_atom(context, string) + context_set = _eval_comparison(context.evaluator, context, context_set, u'+', right) + return context_set else: c = atom.children - if c[0].type == 'string': - # Will be one string. - context_set = eval_atom(context, c[0]) - for string in c[1:]: - right = eval_atom(context, string) - context_set = _eval_comparison(context.evaluator, context, context_set, '+', right) - return context_set # Parentheses without commas are not tuples. - elif c[0] == '(' and not len(c) == 2 \ + if c[0] == '(' and not len(c) == 2 \ and not(c[1].type == 'testlist_comp' and len(c[1].children) > 1): return context.eval_node(c[1]) @@ -203,7 +239,9 @@ def eval_atom(context, atom): pass if comp_for.type == 'comp_for': - return ContextSet(iterable.Comprehension.from_atom(context.evaluator, context, atom)) + return ContextSet(iterable.comprehension_from_atom( + context.evaluator, context, atom + )) # It's a dict/list/tuple literal. array_node = c[1] @@ -221,7 +259,21 @@ def eval_atom(context, atom): @_limit_context_infers def eval_expr_stmt(context, stmt, seek_name=None): with recursion.execution_allowed(context.evaluator, stmt) as allowed: - if allowed or context.get_root_context() == context.evaluator.BUILTINS: + # Here we allow list/set to recurse under certain conditions. To make + # it possible to resolve stuff like list(set(list(x))), this is + # necessary. + if not allowed and context.get_root_context() == context.evaluator.builtins_module: + try: + instance = context.instance + except AttributeError: + pass + else: + if instance.name.string_name in ('list', 'set'): + c = instance.get_first_non_keyword_argument_contexts() + if instance not in c: + allowed = True + + if allowed: return _eval_expr_stmt(context, stmt, seek_name) return NO_CONTEXTS @@ -286,16 +338,16 @@ def eval_or_test(context, or_test): # handle lazy evaluation of and/or here. if operator in ('and', 'or'): left_bools = set(left.py__bool__() for left in types) - if left_bools == set([True]): + if left_bools == {True}: if operator == 'and': types = context.eval_node(right) - elif left_bools == set([False]): + elif left_bools == {False}: if operator != 'and': types = context.eval_node(right) # Otherwise continue, because of uncertainty. else: types = _eval_comparison(context.evaluator, context, types, operator, - context.eval_node(right)) + context.eval_node(right)) debug.dbg('eval_or_test types %s', types) return types @@ -308,29 +360,16 @@ def eval_factor(context_set, operator): for context in context_set: if operator == '-': if is_number(context): - yield compiled.create(context.evaluator, -context.obj) + yield context.negate() elif operator == 'not': value = context.py__bool__() if value is None: # Uncertainty. return - yield compiled.create(context.evaluator, not value) + yield compiled.create_simple_object(context.evaluator, not value) else: yield context -# Maps Python syntax to the operator module. -COMPARISON_OPERATORS = { - '==': op.eq, - '!=': op.ne, - 'is': op.is_, - 'is not': op.is_not, - '<': op.lt, - '<=': op.le, - '>': op.gt, - '>=': op.ge, -} - - def _literals_to_types(evaluator, result): # Changes literals ('a', 1, 1.0, etc) to its type instances (str(), # int(), float(), etc). @@ -366,49 +405,59 @@ def _eval_comparison(evaluator, context, left_contexts, operator, right_contexts def _is_tuple(context): - return isinstance(context, iterable.AbstractIterable) and context.array_type == 'tuple' + return isinstance(context, iterable.Sequence) and context.array_type == 'tuple' def _is_list(context): - return isinstance(context, iterable.AbstractIterable) and context.array_type == 'list' + return isinstance(context, iterable.Sequence) and context.array_type == 'list' + + +def _bool_to_context(evaluator, bool_): + return compiled.builtin_from_name(evaluator, force_unicode(str(bool_))) def _eval_comparison_part(evaluator, context, left, operator, right): l_is_num = is_number(left) r_is_num = is_number(right) - if operator == '*': + if isinstance(operator, unicode): + str_operator = operator + else: + str_operator = force_unicode(str(operator.value)) + + if str_operator == '*': # for iterables, ignore * operations - if isinstance(left, iterable.AbstractIterable) or is_string(left): + if isinstance(left, iterable.Sequence) or is_string(left): return ContextSet(left) - elif isinstance(right, iterable.AbstractIterable) or is_string(right): + elif isinstance(right, iterable.Sequence) or is_string(right): return ContextSet(right) - elif operator == '+': + elif str_operator == '+': if l_is_num and r_is_num or is_string(left) and is_string(right): - return ContextSet(compiled.create(evaluator, left.obj + right.obj)) + return ContextSet(left.execute_operation(right, str_operator)) elif _is_tuple(left) and _is_tuple(right) or _is_list(left) and _is_list(right): return ContextSet(iterable.MergedArray(evaluator, (left, right))) - elif operator == '-': + elif str_operator == '-': if l_is_num and r_is_num: - return ContextSet(compiled.create(evaluator, left.obj - right.obj)) - elif operator == '%': + return ContextSet(left.execute_operation(right, str_operator)) + elif str_operator == '%': # With strings and numbers the left type typically remains. Except for # `int() % float()`. return ContextSet(left) - elif operator in COMPARISON_OPERATORS: - operation = COMPARISON_OPERATORS[operator] + elif str_operator in COMPARISON_OPERATORS: if is_compiled(left) and is_compiled(right): # Possible, because the return is not an option. Just compare. - left = left.obj - right = right.obj - - try: - result = operation(left, right) - except TypeError: - # Could be True or False. - return ContextSet(compiled.create(evaluator, True), compiled.create(evaluator, False)) + try: + return ContextSet(left.execute_operation(right, str_operator)) + except TypeError: + # Could be True or False. + pass else: - return ContextSet(compiled.create(evaluator, result)) - elif operator == 'in': + if str_operator in ('is', '!=', '==', 'is not'): + operation = COMPARISON_OPERATORS[str_operator] + bool_ = operation(left, right) + return ContextSet(_bool_to_context(evaluator, bool_)) + + return ContextSet(_bool_to_context(evaluator, True), _bool_to_context(evaluator, False)) + elif str_operator == 'in': return NO_CONTEXTS def check(obj): @@ -417,7 +466,7 @@ def check(obj): obj.name.string_name in ('int', 'float') # Static analysis, one is a number, the other one is not. - if operator in ('+', '-') and l_is_num != r_is_num \ + if str_operator in ('+', '-') and l_is_num != r_is_num \ and not (check(left) or check(right)): message = "TypeError: unsupported operand type(s) for +: %s and %s" analysis.add(context, 'type-error-operation', operator, @@ -442,6 +491,22 @@ def _remove_statements(evaluator, context, stmt, name): def tree_name_to_contexts(evaluator, context, tree_name): + + context_set = ContextSet() + module_node = context.get_root_context().tree_node + if module_node is not None: + names = module_node.get_used_names().get(tree_name.value, []) + for name in names: + expr_stmt = name.parent + + correct_scope = parser_utils.get_parent_scope(name) == context.tree_node + + if expr_stmt.type == "expr_stmt" and expr_stmt.children[1].type == "annassign" and correct_scope: + context_set |= _evaluate_for_annotation(context, expr_stmt.children[1].children[1]) + + if context_set: + return context_set + types = [] node = tree_name.get_definition(import_name_always=True) if node is None: @@ -455,7 +520,7 @@ def tree_name_to_contexts(evaluator, context, tree_name): filters = [next(filters)] return finder.find(filters, attribute_lookup=False) elif node.type not in ('import_from', 'import_name'): - raise ValueError("Should not happen.") + raise ValueError("Should not happen. type: %s", node.type) typ = node.type if typ == 'for_stmt': @@ -472,14 +537,18 @@ def tree_name_to_contexts(evaluator, context, tree_name): types = context.predefined_names[node][tree_name.value] except KeyError: cn = ContextualizedNode(context, node.children[3]) - for_types = iterate_contexts(cn.infer(), cn) + for_types = iterate_contexts( + cn.infer(), + contextualized_node=cn, + is_async=node.parent.type == 'async_stmt', + ) c_node = ContextualizedName(context, tree_name) types = check_tuple_assignments(evaluator, c_node, for_types) elif typ == 'expr_stmt': types = _remove_statements(evaluator, context, node, tree_name) elif typ == 'with_stmt': context_managers = context.eval_node(node.get_test_node_from_name(tree_name)) - enter_methods = context_managers.py__getattribute__('__enter__') + enter_methods = context_managers.py__getattribute__(u'__enter__') return enter_methods.execute_evaluated() elif typ in ('import_from', 'import_name'): types = imports.infer_import(context, tree_name) @@ -492,7 +561,7 @@ def tree_name_to_contexts(evaluator, context, tree_name): exceptions = context.eval_node(tree_name.get_previous_sibling().get_previous_sibling()) types = exceptions.execute_evaluated() else: - raise ValueError("Should not happen.") + raise ValueError("Should not happen. type: %s" % typ) return types @@ -583,6 +652,8 @@ def eval_subscript_list(evaluator, context, index): result += [None] * (3 - len(result)) return ContextSet(iterable.Slice(context, *result)) + elif index.type == 'subscriptlist': + return NO_CONTEXTS # No slices return context.eval_node(index) diff --git a/pythonFiles/jedi/evaluate/sys_path.py b/pythonFiles/jedi/evaluate/sys_path.py index 82e5e9df9ceb..d765a6653c02 100644 --- a/pythonFiles/jedi/evaluate/sys_path.py +++ b/pythonFiles/jedi/evaluate/sys_path.py @@ -1,85 +1,27 @@ -import glob import os -import sys -import imp -from jedi.evaluate.site import addsitedir -from jedi._compatibility import unicode +from jedi._compatibility import unicode, force_unicode, all_suffixes from jedi.evaluate.cache import evaluator_method_cache from jedi.evaluate.base_context import ContextualizedNode from jedi.evaluate.helpers import is_string +from jedi.common.utils import traverse_parents +from jedi.parser_utils import get_cached_code_lines from jedi import settings from jedi import debug -from jedi.evaluate.utils import ignored - - -def get_venv_path(venv): - """Get sys.path for specified virtual environment.""" - sys_path = _get_venv_path_dirs(venv) - with ignored(ValueError): - sys_path.remove('') - sys_path = _get_sys_path_with_egglinks(sys_path) - # As of now, get_venv_path_dirs does not scan built-in pythonpath and - # user-local site-packages, let's approximate them using path from Jedi - # interpreter. - return sys_path + sys.path - - -def _get_sys_path_with_egglinks(sys_path): - """Find all paths including those referenced by egg-links. - - Egg-link-referenced directories are inserted into path immediately before - the directory on which their links were found. Such directories are not - taken into consideration by normal import mechanism, but they are traversed - when doing pkg_resources.require. - """ - result = [] - for p in sys_path: - # pkg_resources does not define a specific order for egg-link files - # using os.listdir to enumerate them, we're sorting them to have - # reproducible tests. - for egg_link in sorted(glob.glob(os.path.join(p, '*.egg-link'))): - with open(egg_link) as fd: - for line in fd: - line = line.strip() - if line: - result.append(os.path.join(p, line)) - # pkg_resources package only interprets the first - # non-empty line in egg-link files. - break - result.append(p) - return result - - -def _get_venv_path_dirs(venv): - """Get sys.path for venv without starting up the interpreter.""" - venv = os.path.abspath(venv) - sitedir = _get_venv_sitepackages(venv) - sys_path = [] - addsitedir(sys_path, sitedir) - return sys_path - - -def _get_venv_sitepackages(venv): - if os.name == 'nt': - p = os.path.join(venv, 'lib', 'site-packages') - else: - p = os.path.join(venv, 'lib', 'python%d.%d' % sys.version_info[:2], - 'site-packages') - return p def _abs_path(module_context, path): - module_path = module_context.py__file__() if os.path.isabs(path): return path + module_path = module_context.py__file__() if module_path is None: # In this case we have no idea where we actually are in the file # system. return None base_dir = os.path.dirname(module_path) + path = force_unicode(path) return os.path.abspath(os.path.join(base_dir, path)) @@ -87,7 +29,7 @@ def _paths_from_assignment(module_context, expr_stmt): """ Extracts the assigned strings from an assignment that looks as follows:: - >>> sys.path[0:0] = ['module/path', 'another/module/path'] + sys.path[0:0] = ['module/path', 'another/module/path'] This function is in general pretty tolerant (and therefore 'buggy'). However, it's not a big issue usually to add more paths to Jedi's sys_path, @@ -121,7 +63,7 @@ def _paths_from_assignment(module_context, expr_stmt): for lazy_context in cn.infer().iterate(cn): for context in lazy_context.infer(): if is_string(context): - abs_path = _abs_path(module_context, context.obj) + abs_path = _abs_path(module_context, context.get_safe_value()) if abs_path is not None: yield abs_path @@ -144,7 +86,7 @@ def _paths_from_list_modifications(module_context, trailer1, trailer2): for context in module_context.create_context(arg).eval_node(arg): if is_string(context): - abs_path = _abs_path(module_context, context.obj) + abs_path = _abs_path(module_context, context.get_safe_value()) if abs_path is not None: yield abs_path @@ -187,24 +129,19 @@ def get_sys_path_powers(names): return added -def sys_path_with_modifications(evaluator, module_context): - return evaluator.project.sys_path + check_sys_path_modifications(module_context) - - -def detect_additional_paths(evaluator, script_path): - django_paths = _detect_django_path(script_path) +def discover_buildout_paths(evaluator, script_path): buildout_script_paths = set() for buildout_script_path in _get_buildout_script_paths(script_path): for path in _get_paths_from_buildout_script(evaluator, buildout_script_path): buildout_script_paths.add(path) - return django_paths + list(buildout_script_paths) + return buildout_script_paths def _get_paths_from_buildout_script(evaluator, buildout_script_path): try: - module_node = evaluator.grammar.parse( + module_node = evaluator.parse( path=buildout_script_path, cache=True, cache_path=settings.cache_directory @@ -214,20 +151,14 @@ def _get_paths_from_buildout_script(evaluator, buildout_script_path): return from jedi.evaluate.context import ModuleContext - module = ModuleContext(evaluator, module_node, buildout_script_path) + module = ModuleContext( + evaluator, module_node, buildout_script_path, + code_lines=get_cached_code_lines(evaluator.grammar, buildout_script_path), + ) for path in check_sys_path_modifications(module): yield path -def traverse_parents(path): - while True: - new = os.path.dirname(path) - if new == path: - return - path = new - yield path - - def _get_parent_dir_with_file(path, filename): for parent in traverse_parents(path): if os.path.isfile(os.path.join(parent, filename)): @@ -235,47 +166,34 @@ def _get_parent_dir_with_file(path, filename): return None -def _detect_django_path(module_path): - """ Detects the path of the very well known Django library (if used) """ - result = [] - - for parent in traverse_parents(module_path): - with ignored(IOError): - with open(parent + os.path.sep + 'manage.py'): - debug.dbg('Found django path: %s', module_path) - result.append(parent) - return result - - -def _get_buildout_script_paths(module_path): +def _get_buildout_script_paths(search_path): """ if there is a 'buildout.cfg' file in one of the parent directories of the given module it will return a list of all files in the buildout bin directory that look like python files. - :param module_path: absolute path to the module. - :type module_path: str + :param search_path: absolute path to the module. + :type search_path: str """ - project_root = _get_parent_dir_with_file(module_path, 'buildout.cfg') + project_root = _get_parent_dir_with_file(search_path, 'buildout.cfg') if not project_root: - return [] + return bin_path = os.path.join(project_root, 'bin') if not os.path.exists(bin_path): - return [] - extra_module_paths = [] + return + for filename in os.listdir(bin_path): try: filepath = os.path.join(bin_path, filename) with open(filepath, 'r') as f: firstline = f.readline() if firstline.startswith('#!') and 'python' in firstline: - extra_module_paths.append(filepath) + yield filepath except (UnicodeDecodeError, IOError) as e: - # Probably a binary file; permission error or race cond. because file got deleted - # ignore + # Probably a binary file; permission error or race cond. because + # file got deleted. Ignore it. debug.warning(unicode(e)) continue - return extra_module_paths def dotted_path_in_sys_path(sys_path, module_path): @@ -283,7 +201,7 @@ def dotted_path_in_sys_path(sys_path, module_path): Returns the dotted path inside a sys.path. """ # First remove the suffix. - for suffix, _, _ in imp.get_suffixes(): + for suffix in all_suffixes(): if module_path.endswith(suffix): module_path = module_path[:-len(suffix)] break diff --git a/pythonFiles/jedi/evaluate/utils.py b/pythonFiles/jedi/evaluate/utils.py index 7fc1c246de0d..e00e477441de 100644 --- a/pythonFiles/jedi/evaluate/utils.py +++ b/pythonFiles/jedi/evaluate/utils.py @@ -2,10 +2,19 @@ import sys import contextlib import functools +import re +import os from jedi._compatibility import reraise +_sep = os.path.sep +if os.path.altsep is not None: + _sep += os.path.altsep +_path_re = re.compile('(?:\.[^{0}]+|[{0}]__init__\.py)$'.format(re.escape(_sep))) +del _sep + + def to_list(func): def wrapper(*args, **kwargs): return list(func(*args, **kwargs)) @@ -108,3 +117,38 @@ def indent_block(text, indention=' '): text = text[:-1] lines = text.split('\n') return '\n'.join(map(lambda s: indention + s, lines)) + temp + + +def dotted_from_fs_path(fs_path, sys_path): + """ + Changes `/usr/lib/python3.4/email/utils.py` to `email.utils`. I.e. + compares the path with sys.path and then returns the dotted_path. If the + path is not in the sys.path, just returns None. + """ + if os.path.basename(fs_path).startswith('__init__.'): + # We are calculating the path. __init__ files are not interesting. + fs_path = os.path.dirname(fs_path) + + # prefer + # - UNIX + # /path/to/pythonX.Y/lib-dynload + # /path/to/pythonX.Y/site-packages + # - Windows + # C:\path\to\DLLs + # C:\path\to\Lib\site-packages + # over + # - UNIX + # /path/to/pythonX.Y + # - Windows + # C:\path\to\Lib + path = '' + for s in sys_path: + if (fs_path.startswith(s) and len(path) < len(s)): + path = s + + # - Window + # X:\path\to\lib-dynload/datetime.pyd => datetime + module_path = fs_path[len(path):].lstrip(os.path.sep).lstrip('/') + # - Window + # Replace like X:\path\to\something/foo/bar.py + return _path_re.sub('', module_path).replace(os.path.sep, '.').replace('/', '.') diff --git a/pythonFiles/jedi/parser_utils.py b/pythonFiles/jedi/parser_utils.py index 59c6408ea1c6..e630265314e4 100644 --- a/pythonFiles/jedi/parser_utils.py +++ b/pythonFiles/jedi/parser_utils.py @@ -1,14 +1,15 @@ import textwrap from inspect import cleandoc -from jedi._compatibility import literal_eval, is_py3 from parso.python import tree +from parso.cache import parser_cache -_EXECUTE_NODES = set([ - 'funcdef', 'classdef', 'import_from', 'import_name', 'test', 'or_test', - 'and_test', 'not_test', 'comparison', 'expr', 'xor_expr', 'and_expr', - 'shift_expr', 'arith_expr', 'atom_expr', 'term', 'factor', 'power', 'atom' -]) +from jedi._compatibility import literal_eval, force_unicode + +_EXECUTE_NODES = {'funcdef', 'classdef', 'import_from', 'import_name', 'test', + 'or_test', 'and_test', 'not_test', 'comparison', 'expr', + 'xor_expr', 'and_expr', 'shift_expr', 'arith_expr', + 'atom_expr', 'term', 'factor', 'power', 'atom'} _FLOW_KEYWORDS = ( 'try', 'except', 'finally', 'else', 'if', 'elif', 'with', 'for', 'while' @@ -112,10 +113,7 @@ def clean_scope_docstring(scope_node): cleaned = cleandoc(safe_literal_eval(node.value)) # Since we want the docstr output to be always unicode, just # force it. - if is_py3 or isinstance(cleaned, unicode): - return cleaned - else: - return unicode(cleaned, 'UTF-8', 'replace') + return force_unicode(cleaned) return '' @@ -205,6 +203,9 @@ def get_following_comment_same_line(node): whitespace = node.children[5].get_first_leaf().prefix elif node.type == 'with_stmt': whitespace = node.children[3].get_first_leaf().prefix + elif node.type == 'funcdef': + # actually on the next line + whitespace = node.children[4].get_first_leaf().get_next_leaf().prefix else: whitespace = node.get_last_leaf().get_next_leaf().prefix except AttributeError: @@ -239,3 +240,11 @@ def get_parent_scope(node, include_flows=False): break scope = scope.parent return scope + + +def get_cached_code_lines(grammar, path): + """ + Basically access the cached code lines in parso. This is not the nicest way + to do this, but we avoid splitting all the lines again. + """ + return parser_cache[grammar._hashed][path].lines diff --git a/pythonFiles/jedi/refactoring.py b/pythonFiles/jedi/refactoring.py index ee938427fcee..6c1d74d1bdb8 100644 --- a/pythonFiles/jedi/refactoring.py +++ b/pythonFiles/jedi/refactoring.py @@ -1,11 +1,14 @@ """ +THIS is not in active development, please check +https://github.com/davidhalter/jedi/issues/667 first before editing. + Introduce some basic refactoring functions to |jedi|. This module is still in a very early development stage and needs much testing and improvement. .. warning:: I won't do too much here, but if anyone wants to step in, please do. Refactoring is none of my priorities -It uses the |jedi| `API `_ and supports currently the +It uses the |jedi| `API `_ and supports currently the following functions (sometimes bug-prone): - rename @@ -50,9 +53,8 @@ def diff(self): def rename(script, new_name): """ The `args` / `kwargs` params are the same as in `api.Script`. - :param operation: The refactoring operation to execute. - :type operation: str - :type source: str + :param new_name: The new name of the script. + :param script: The source Script object. :return: list of changed lines/changed files """ return Refactoring(_rename(script.usages(), new_name)) @@ -105,11 +107,12 @@ def extract(script, new_name): user_stmt = script._parser.user_stmt() - # TODO care for multiline extracts + # TODO care for multi-line extracts dct = {} if user_stmt: pos = script._pos line_index = pos[0] - 1 + # Be careful here. 'array_for_pos' does not exist in 'helpers'. arr, index = helpers.array_for_pos(user_stmt, pos) if arr is not None: start_pos = arr[index].start_pos @@ -120,7 +123,7 @@ def extract(script, new_name): start_line = new_lines[start_pos[0] - 1] text = start_line[start_pos[1]:e] for l in range(start_pos[0], end_pos[0] - 1): - text += '\n' + l + text += '\n' + str(l) if e is None: end_line = new_lines[end_pos[0] - 1] text += '\n' + end_line[:end_pos[1]] @@ -140,7 +143,7 @@ def extract(script, new_name): new_lines[start_pos[0] - 1] = start_line new_lines[start_pos[0]:end_pos[0] - 1] = [] - # add parentheses in multiline case + # add parentheses in multi-line case open_brackets = ['(', '[', '{'] close_brackets = [')', ']', '}'] if '\n' in text and not (text[0] in open_brackets and text[-1] == @@ -172,7 +175,7 @@ def inline(script): inlines = sorted(inlines, key=lambda x: (x.module_path, x.line, x.column), reverse=True) expression_list = stmt.expression_list() - # don't allow multiline refactorings for now. + # don't allow multi-line refactorings for now. assert stmt.start_pos[0] == stmt.end_pos[0] index = stmt.start_pos[0] - 1 diff --git a/pythonFiles/jedi/utils.py b/pythonFiles/jedi/utils.py index 177524c50168..0f42e7d55858 100644 --- a/pythonFiles/jedi/utils.py +++ b/pythonFiles/jedi/utils.py @@ -89,12 +89,13 @@ def complete(self, text, state): lines = split_lines(text) position = (len(lines), len(lines[-1])) name = get_on_completion_name( - interpreter._get_module_node(), + interpreter._module_node, lines, position ) before = text[:len(text) - len(name)] completions = interpreter.completions() + logging.debug("REPL completions: %s", completions) except: logging.error("REPL Completion error:\n" + traceback.format_exc()) raise @@ -108,6 +109,11 @@ def complete(self, text, state): return None try: + # Need to import this one as well to make sure it's executed before + # this code. This didn't use to be an issue until 3.3. Starting with + # 3.4 this is different, it always overwrites the completer if it's not + # already imported here. + import rlcompleter import readline except ImportError: print("Jedi: Module readline not available.") diff --git a/pythonFiles/parso/__init__.py b/pythonFiles/parso/__init__.py index f0a0fc4f5015..c4cce53ea690 100644 --- a/pythonFiles/parso/__init__.py +++ b/pythonFiles/parso/__init__.py @@ -43,7 +43,7 @@ from parso.utils import split_lines, python_bytes_to_unicode -__version__ = '0.1.1' +__version__ = '0.2.0' def parse(code=None, **kwargs): diff --git a/pythonFiles/parso/_compatibility.py b/pythonFiles/parso/_compatibility.py index 9ddf23dc6786..db411eebf981 100644 --- a/pythonFiles/parso/_compatibility.py +++ b/pythonFiles/parso/_compatibility.py @@ -36,7 +36,7 @@ def use_metaclass(meta, *bases): def u(string): """Cast to unicode DAMMIT! Written because Python2 repr always implicitly casts to a string, so we - have to cast back to a unicode (and we now that we always deal with valid + have to cast back to a unicode (and we know that we always deal with valid unicode, because we check that in the beginning). """ if py_version >= 30: diff --git a/pythonFiles/parso/grammar.py b/pythonFiles/parso/grammar.py index 2cf26d77fb27..c825b5554c0e 100644 --- a/pythonFiles/parso/grammar.py +++ b/pythonFiles/parso/grammar.py @@ -12,7 +12,6 @@ from parso.python.parser import Parser as PythonParser from parso.python.errors import ErrorFinderConfig from parso.python import pep8 -from parso.python import fstring _loaded_grammars = {} @@ -73,7 +72,7 @@ def parse(self, code=None, **kwargs): :py:class:`parso.python.tree.Module`. """ if 'start_pos' in kwargs: - raise TypeError("parse() got an unexpected keyworda argument.") + raise TypeError("parse() got an unexpected keyword argument.") return self._parse(code=code, **kwargs) def _parse(self, code=None, error_recovery=True, path=None, @@ -186,7 +185,6 @@ def _get_normalizer_issues(self, node, normalizer_config=None): normalizer.walk(node) return normalizer.issues - def __repr__(self): labels = self._pgen_grammar.number2symbol.values() txt = ' '.join(list(labels)[:3]) + ' ...' @@ -215,34 +213,6 @@ def _tokenize(self, code): return tokenize(code, self.version_info) -class PythonFStringGrammar(Grammar): - _token_namespace = fstring.TokenNamespace - _start_symbol = 'fstring' - - def __init__(self): - super(PythonFStringGrammar, self).__init__( - text=fstring.GRAMMAR, - tokenizer=fstring.tokenize, - parser=fstring.Parser - ) - - def parse(self, code, **kwargs): - return self._parse(code, **kwargs) - - def _parse(self, code, error_recovery=True, start_pos=(1, 0)): - tokens = self._tokenizer(code, start_pos=start_pos) - p = self._parser( - self._pgen_grammar, - error_recovery=error_recovery, - start_symbol=self._start_symbol, - ) - return p.parse(tokens=tokens) - - def parse_leaf(self, leaf, error_recovery=True): - code = leaf._get_payload() - return self.parse(code, error_recovery=True, start_pos=leaf.start_pos) - - def load_grammar(**kwargs): """ Loads a :py:class:`parso.Grammar`. The default version is the current Python @@ -273,10 +243,6 @@ def load_grammar(language='python', version=None): except FileNotFoundError: message = "Python version %s is currently not supported." % version raise NotImplementedError(message) - elif language == 'python-f-string': - if version is not None: - raise NotImplementedError("Currently different versions are not supported.") - return PythonFStringGrammar() else: raise NotImplementedError("No support for language %s." % language) diff --git a/pythonFiles/parso/pgen2/pgen.py b/pythonFiles/parso/pgen2/pgen.py index 10ef6ffd1532..a3e39fa5fe74 100644 --- a/pythonFiles/parso/pgen2/pgen.py +++ b/pythonFiles/parso/pgen2/pgen.py @@ -28,6 +28,7 @@ def make_grammar(self): c = grammar.Grammar(self._bnf_text) names = list(self.dfas.keys()) names.sort() + # TODO do we still need this? names.remove(self.startsymbol) names.insert(0, self.startsymbol) for name in names: @@ -316,8 +317,8 @@ def _parse_atom(self): def _expect(self, type): if self.type != type: - self._raise_error("expected %s, got %s(%s)", - type, self.type, self.value) + self._raise_error("expected %s(%s), got %s(%s)", + type, token.tok_name[type], self.type, self.value) value = self.value self._gettoken() return value diff --git a/pythonFiles/parso/python/diff.py b/pythonFiles/parso/python/diff.py index c2e44fd3cb21..96c6e5f2ca41 100644 --- a/pythonFiles/parso/python/diff.py +++ b/pythonFiles/parso/python/diff.py @@ -133,7 +133,7 @@ def update(self, old_lines, new_lines): LOG.debug('diff: line_lengths old: %s, new: %s' % (len(old_lines), line_length)) for operation, i1, i2, j1, j2 in opcodes: - LOG.debug('diff %s old[%s:%s] new[%s:%s]', + LOG.debug('diff code[%s] old[%s:%s] new[%s:%s]', operation, i1 + 1, i2, j1 + 1, j2) if j2 == line_length and new_lines[-1] == '': @@ -454,7 +454,7 @@ def _remove_endmarker(self, tree_nodes): self._last_prefix = '' if is_endmarker: try: - separation = last_leaf.prefix.rindex('\n') + separation = last_leaf.prefix.rindex('\n') + 1 except ValueError: pass else: @@ -462,7 +462,7 @@ def _remove_endmarker(self, tree_nodes): # That is not relevant if parentheses were opened. Always parse # until the end of a line. last_leaf.prefix, self._last_prefix = \ - last_leaf.prefix[:separation + 1], last_leaf.prefix[separation + 1:] + last_leaf.prefix[:separation], last_leaf.prefix[separation:] first_leaf = tree_nodes[0].get_first_leaf() first_leaf.prefix = self.prefix + first_leaf.prefix @@ -472,7 +472,6 @@ def _remove_endmarker(self, tree_nodes): self.prefix = last_leaf.prefix tree_nodes = tree_nodes[:-1] - return tree_nodes def copy_nodes(self, tree_nodes, until_line, line_offset): @@ -492,6 +491,13 @@ def _copy_nodes(self, tos, nodes, until_line, line_offset): new_tos = tos for node in nodes: if node.type == 'endmarker': + # We basically removed the endmarker, but we are not allowed to + # remove the newline at the end of the line, otherwise it's + # going to be missing. + try: + self.prefix = node.prefix[:node.prefix.rindex('\n') + 1] + except ValueError: + pass # Endmarkers just distort all the checks below. Remove them. break diff --git a/pythonFiles/parso/python/errors.py b/pythonFiles/parso/python/errors.py index 65296568b54c..cfb8380ea743 100644 --- a/pythonFiles/parso/python/errors.py +++ b/pythonFiles/parso/python/errors.py @@ -563,7 +563,8 @@ def is_issue(self, leaf): and self._normalizer.version == (3, 5): self.add_issue(self.get_node(leaf), message=self.message_async_yield) -@ErrorFinder.register_rule(type='atom') + +@ErrorFinder.register_rule(type='strings') class _BytesAndStringMix(SyntaxRule): # e.g. 's' b'' message = "cannot mix bytes and nonbytes literals" @@ -744,7 +745,12 @@ def is_issue(self, node): @ErrorFinder.register_rule(type='arglist') class _ArglistRule(SyntaxRule): - message = "Generator expression must be parenthesized if not sole argument" + @property + def message(self): + if self._normalizer.version < (3, 7): + return "Generator expression must be parenthesized if not sole argument" + else: + return "Generator expression must be parenthesized" def is_issue(self, node): first_arg = node.children[0] @@ -837,101 +843,36 @@ def is_issue(self, try_stmt): self.add_issue(default_except, message=self.message) -@ErrorFinder.register_rule(type='string') +@ErrorFinder.register_rule(type='fstring') class _FStringRule(SyntaxRule): _fstring_grammar = None - message_empty = "f-string: empty expression not allowed" # f'{}' - message_single_closing = "f-string: single '}' is not allowed" # f'}' message_nested = "f-string: expressions nested too deeply" - message_backslash = "f-string expression part cannot include a backslash" # f'{"\"}' or f'{"\\"}' - message_comment = "f-string expression part cannot include '#'" # f'{#}' - message_unterminated_string = "f-string: unterminated string" # f'{"}' message_conversion = "f-string: invalid conversion character: expected 's', 'r', or 'a'" - message_incomplete = "f-string: expecting '}'" # f'{' - message_syntax = "invalid syntax" - @classmethod - def _load_grammar(cls): - import parso + def _check_format_spec(self, format_spec, depth): + self._check_fstring_contents(format_spec.children[1:], depth) - if cls._fstring_grammar is None: - cls._fstring_grammar = parso.load_grammar(language='python-f-string') - return cls._fstring_grammar + def _check_fstring_expr(self, fstring_expr, depth): + if depth >= 2: + self.add_issue(fstring_expr, message=self.message_nested) - def is_issue(self, fstring): - if 'f' not in fstring.string_prefix.lower(): - return + conversion = fstring_expr.children[2] + if conversion.type == 'fstring_conversion': + name = conversion.children[1] + if name.value not in ('s', 'r', 'a'): + self.add_issue(name, message=self.message_conversion) - parsed = self._load_grammar().parse_leaf(fstring) - for child in parsed.children: - if child.type == 'expression': - self._check_expression(child) - elif child.type == 'error_node': - next_ = child.get_next_leaf() - if next_.type == 'error_leaf' and next_.original_type == 'unterminated_string': - self.add_issue(next_, message=self.message_unterminated_string) - # At this point nothing more is comming except the error - # leaf that we've already checked here. - break - self.add_issue(child, message=self.message_incomplete) - elif child.type == 'error_leaf': - self.add_issue(child, message=self.message_single_closing) - - def _check_python_expr(self, python_expr): - value = python_expr.value - if '\\' in value: - self.add_issue(python_expr, message=self.message_backslash) - return - if '#' in value: - self.add_issue(python_expr, message=self.message_comment) - return - if re.match('\s*$', value) is not None: - self.add_issue(python_expr, message=self.message_empty) - return - - # This is now nested parsing. We parsed the fstring and now - # we're parsing Python again. - try: - # CPython has a bit of a special ways to parse Python code within - # f-strings. It wraps the code in brackets to make sure that - # whitespace doesn't make problems (indentation/newlines). - # Just use that algorithm as well here and adapt start positions. - start_pos = python_expr.start_pos - start_pos = start_pos[0], start_pos[1] - 1 - eval_input = self._normalizer.grammar._parse( - '(%s)' % value, - start_symbol='eval_input', - start_pos=start_pos, - error_recovery=False - ) - except ParserSyntaxError as e: - self.add_issue(e.error_leaf, message=self.message_syntax) - return + format_spec = fstring_expr.children[-2] + if format_spec.type == 'fstring_format_spec': + self._check_format_spec(format_spec, depth + 1) - issues = self._normalizer.grammar.iter_errors(eval_input) - self._normalizer.issues += issues - - def _check_format_spec(self, format_spec): - for expression in format_spec.children[1:]: - nested_format_spec = expression.children[-2] - if nested_format_spec.type == 'format_spec': - if len(nested_format_spec.children) > 1: - self.add_issue( - nested_format_spec.children[1], - message=self.message_nested - ) - - self._check_expression(expression) + def is_issue(self, fstring): + self._check_fstring_contents(fstring.children[1:-1]) - def _check_expression(self, expression): - for c in expression.children: - if c.type == 'python_expr': - self._check_python_expr(c) - elif c.type == 'conversion': - if c.value not in ('s', 'r', 'a'): - self.add_issue(c, message=self.message_conversion) - elif c.type == 'format_spec': - self._check_format_spec(c) + def _check_fstring_contents(self, children, depth=0): + for fstring_content in children: + if fstring_content.type == 'fstring_expr': + self._check_fstring_expr(fstring_content, depth) class _CheckAssignmentRule(SyntaxRule): @@ -944,7 +885,7 @@ def _check_assignment(self, node, is_deletion=False): first, second = node.children[:2] error = _get_comprehension_type(node) if error is None: - if second.type in ('dictorsetmaker', 'string'): + if second.type == 'dictorsetmaker': error = 'literal' elif first in ('(', '['): if second.type == 'yield_expr': @@ -963,7 +904,7 @@ def _check_assignment(self, node, is_deletion=False): error = 'Ellipsis' elif type_ == 'comparison': error = 'comparison' - elif type_ in ('string', 'number'): + elif type_ in ('string', 'number', 'strings'): error = 'literal' elif type_ == 'yield_expr': # This one seems to be a slightly different warning in Python. diff --git a/pythonFiles/parso/python/fstring.py b/pythonFiles/parso/python/fstring.py deleted file mode 100644 index a8fe7b452df5..000000000000 --- a/pythonFiles/parso/python/fstring.py +++ /dev/null @@ -1,211 +0,0 @@ -import re - -from itertools import count -from parso.utils import PythonVersionInfo -from parso.utils import split_lines -from parso.python.tokenize import Token -from parso import parser -from parso.tree import TypedLeaf, ErrorNode, ErrorLeaf - -version36 = PythonVersionInfo(3, 6) - - -class TokenNamespace: - _c = count() - LBRACE = next(_c) - RBRACE = next(_c) - ENDMARKER = next(_c) - COLON = next(_c) - CONVERSION = next(_c) - PYTHON_EXPR = next(_c) - EXCLAMATION_MARK = next(_c) - UNTERMINATED_STRING = next(_c) - - token_map = dict((v, k) for k, v in locals().items() if not k.startswith('_')) - - @classmethod - def generate_token_id(cls, string): - if string == '{': - return cls.LBRACE - elif string == '}': - return cls.RBRACE - elif string == '!': - return cls.EXCLAMATION_MARK - elif string == ':': - return cls.COLON - return getattr(cls, string) - - -GRAMMAR = """ -fstring: expression* ENDMARKER -format_spec: ':' expression* -expression: '{' PYTHON_EXPR [ '!' CONVERSION ] [ format_spec ] '}' -""" - -_prefix = r'((?:[^{}]+)*)' -_expr = _prefix + r'(\{|\}|$)' -_in_expr = r'([^{}\[\]:"\'!]*)(.?)' -# There's only one conversion character allowed. But the rules have to be -# checked later anyway, so allow more here. This makes error recovery nicer. -_conversion = r'([^={}:]*)(.?)' - -_compiled_expr = re.compile(_expr) -_compiled_in_expr = re.compile(_in_expr) -_compiled_conversion = re.compile(_conversion) - - -def tokenize(code, start_pos=(1, 0)): - def add_to_pos(string): - lines = split_lines(string) - l = len(lines[-1]) - if len(lines) > 1: - start_pos[0] += len(lines) - 1 - start_pos[1] = l - else: - start_pos[1] += l - - def tok(value, type=None, prefix=''): - if type is None: - type = TokenNamespace.generate_token_id(value) - - add_to_pos(prefix) - token = Token(type, value, tuple(start_pos), prefix) - add_to_pos(value) - return token - - start = 0 - recursion_level = 0 - added_prefix = '' - start_pos = list(start_pos) - while True: - match = _compiled_expr.match(code, start) - prefix = added_prefix + match.group(1) - found = match.group(2) - start = match.end() - if not found: - # We're at the end. - break - - if found == '}': - if recursion_level == 0 and len(code) > start and code[start] == '}': - # This is a }} escape. - added_prefix = prefix + '}}' - start += 1 - continue - - recursion_level = max(0, recursion_level - 1) - yield tok(found, prefix=prefix) - added_prefix = '' - else: - assert found == '{' - if recursion_level == 0 and len(code) > start and code[start] == '{': - # This is a {{ escape. - added_prefix = prefix + '{{' - start += 1 - continue - - recursion_level += 1 - yield tok(found, prefix=prefix) - added_prefix = '' - - expression = '' - squared_count = 0 - curly_count = 0 - while True: - expr_match = _compiled_in_expr.match(code, start) - expression += expr_match.group(1) - found = expr_match.group(2) - start = expr_match.end() - - if found == '{': - curly_count += 1 - expression += found - elif found == '}' and curly_count > 0: - curly_count -= 1 - expression += found - elif found == '[': - squared_count += 1 - expression += found - elif found == ']': - # Use a max function here, because the Python code might - # just have syntax errors. - squared_count = max(0, squared_count - 1) - expression += found - elif found == ':' and (squared_count or curly_count): - expression += found - elif found in ('"', "'"): - search = found - if len(code) > start + 1 and \ - code[start] == found == code[start+1]: - search *= 3 - start += 2 - - index = code.find(search, start) - if index == -1: - yield tok(expression, type=TokenNamespace.PYTHON_EXPR) - yield tok( - found + code[start:], - type=TokenNamespace.UNTERMINATED_STRING, - ) - start = len(code) - break - expression += found + code[start:index+1] - start = index + 1 - elif found == '!' and len(code) > start and code[start] == '=': - # This is a python `!=` and not a conversion. - expression += found - else: - yield tok(expression, type=TokenNamespace.PYTHON_EXPR) - if found: - yield tok(found) - break - - if found == '!': - conversion_match = _compiled_conversion.match(code, start) - found = conversion_match.group(2) - start = conversion_match.end() - yield tok(conversion_match.group(1), type=TokenNamespace.CONVERSION) - if found: - yield tok(found) - if found == '}': - recursion_level -= 1 - - # We don't need to handle everything after ':', because that is - # basically new tokens. - - yield tok('', type=TokenNamespace.ENDMARKER, prefix=prefix) - - -class Parser(parser.BaseParser): - def parse(self, tokens): - node = super(Parser, self).parse(tokens) - if isinstance(node, self.default_leaf): # Is an endmarker. - # If there's no curly braces we get back a non-module. We always - # want an fstring. - node = self.default_node('fstring', [node]) - - return node - - def convert_leaf(self, pgen_grammar, type, value, prefix, start_pos): - # TODO this is so ugly. - leaf_type = TokenNamespace.token_map[type].lower() - return TypedLeaf(leaf_type, value, start_pos, prefix) - - def error_recovery(self, pgen_grammar, stack, arcs, typ, value, start_pos, prefix, - add_token_callback): - if not self._error_recovery: - return super(Parser, self).error_recovery( - pgen_grammar, stack, arcs, typ, value, start_pos, prefix, - add_token_callback - ) - - token_type = TokenNamespace.token_map[typ].lower() - if len(stack) == 1: - error_leaf = ErrorLeaf(token_type, value, start_pos, prefix) - stack[0][2][1].append(error_leaf) - else: - dfa, state, (type_, nodes) = stack[1] - stack[0][2][1].append(ErrorNode(nodes)) - stack[1:] = [] - - add_token_callback(typ, value, start_pos, prefix) diff --git a/pythonFiles/parso/python/grammar26.txt b/pythonFiles/parso/python/grammar26.txt index b972a41d6a4a..d9cede2e9da9 100644 --- a/pythonFiles/parso/python/grammar26.txt +++ b/pythonFiles/parso/python/grammar26.txt @@ -119,7 +119,8 @@ atom: ('(' [yield_expr|testlist_comp] ')' | '[' [listmaker] ']' | '{' [dictorsetmaker] '}' | '`' testlist1 '`' | - NAME | NUMBER | STRING+) + NAME | NUMBER | strings) +strings: STRING+ listmaker: test ( list_for | (',' test)* [','] ) # Dave: Renamed testlist_gexpr to testlist_comp, because in 2.7+ this is the # default. It's more consistent like this. diff --git a/pythonFiles/parso/python/grammar27.txt b/pythonFiles/parso/python/grammar27.txt index 4c3f33da32d5..359f12b43e1f 100644 --- a/pythonFiles/parso/python/grammar27.txt +++ b/pythonFiles/parso/python/grammar27.txt @@ -104,7 +104,8 @@ atom: ('(' [yield_expr|testlist_comp] ')' | '[' [listmaker] ']' | '{' [dictorsetmaker] '}' | '`' testlist1 '`' | - NAME | NUMBER | STRING+) + NAME | NUMBER | strings) +strings: STRING+ listmaker: test ( list_for | (',' test)* [','] ) testlist_comp: test ( comp_for | (',' test)* [','] ) lambdef: 'lambda' [varargslist] ':' test diff --git a/pythonFiles/parso/python/grammar33.txt b/pythonFiles/parso/python/grammar33.txt index d7aaffd60e14..3a5580926797 100644 --- a/pythonFiles/parso/python/grammar33.txt +++ b/pythonFiles/parso/python/grammar33.txt @@ -103,7 +103,8 @@ power: atom trailer* ['**' factor] atom: ('(' [yield_expr|testlist_comp] ')' | '[' [testlist_comp] ']' | '{' [dictorsetmaker] '}' | - NAME | NUMBER | STRING+ | '...' | 'None' | 'True' | 'False') + NAME | NUMBER | strings | '...' | 'None' | 'True' | 'False') +strings: STRING+ testlist_comp: (test|star_expr) ( comp_for | (',' (test|star_expr))* [','] ) trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME subscriptlist: subscript (',' subscript)* [','] diff --git a/pythonFiles/parso/python/grammar34.txt b/pythonFiles/parso/python/grammar34.txt index 05c3181627db..324bba18753d 100644 --- a/pythonFiles/parso/python/grammar34.txt +++ b/pythonFiles/parso/python/grammar34.txt @@ -103,7 +103,8 @@ power: atom trailer* ['**' factor] atom: ('(' [yield_expr|testlist_comp] ')' | '[' [testlist_comp] ']' | '{' [dictorsetmaker] '}' | - NAME | NUMBER | STRING+ | '...' | 'None' | 'True' | 'False') + NAME | NUMBER | strings | '...' | 'None' | 'True' | 'False') +strings: STRING+ testlist_comp: (test|star_expr) ( comp_for | (',' (test|star_expr))* [','] ) trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME subscriptlist: subscript (',' subscript)* [','] diff --git a/pythonFiles/parso/python/grammar35.txt b/pythonFiles/parso/python/grammar35.txt index c38217f3f97f..5868b8f7031a 100644 --- a/pythonFiles/parso/python/grammar35.txt +++ b/pythonFiles/parso/python/grammar35.txt @@ -110,7 +110,8 @@ atom_expr: ['await'] atom trailer* atom: ('(' [yield_expr|testlist_comp] ')' | '[' [testlist_comp] ']' | '{' [dictorsetmaker] '}' | - NAME | NUMBER | STRING+ | '...' | 'None' | 'True' | 'False') + NAME | NUMBER | strings | '...' | 'None' | 'True' | 'False') +strings: STRING+ testlist_comp: (test|star_expr) ( comp_for | (',' (test|star_expr))* [','] ) trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME subscriptlist: subscript (',' subscript)* [','] diff --git a/pythonFiles/parso/python/grammar36.txt b/pythonFiles/parso/python/grammar36.txt index e76147e9e4fc..b82c1fec1145 100644 --- a/pythonFiles/parso/python/grammar36.txt +++ b/pythonFiles/parso/python/grammar36.txt @@ -108,7 +108,7 @@ atom_expr: ['await'] atom trailer* atom: ('(' [yield_expr|testlist_comp] ')' | '[' [testlist_comp] ']' | '{' [dictorsetmaker] '}' | - NAME | NUMBER | STRING+ | '...' | 'None' | 'True' | 'False') + NAME | NUMBER | strings | '...' | 'None' | 'True' | 'False') testlist_comp: (test|star_expr) ( comp_for | (',' (test|star_expr))* [','] ) trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME subscriptlist: subscript (',' subscript)* [','] @@ -148,3 +148,10 @@ encoding_decl: NAME yield_expr: 'yield' [yield_arg] yield_arg: 'from' test | testlist + +strings: (STRING | fstring)+ +fstring: FSTRING_START fstring_content* FSTRING_END +fstring_content: FSTRING_STRING | fstring_expr +fstring_conversion: '!' NAME +fstring_expr: '{' testlist_comp [ fstring_conversion ] [ fstring_format_spec ] '}' +fstring_format_spec: ':' fstring_content* diff --git a/pythonFiles/parso/python/grammar37.txt b/pythonFiles/parso/python/grammar37.txt index e76147e9e4fc..7d112f79852b 100644 --- a/pythonFiles/parso/python/grammar37.txt +++ b/pythonFiles/parso/python/grammar37.txt @@ -108,7 +108,7 @@ atom_expr: ['await'] atom trailer* atom: ('(' [yield_expr|testlist_comp] ')' | '[' [testlist_comp] ']' | '{' [dictorsetmaker] '}' | - NAME | NUMBER | STRING+ | '...' | 'None' | 'True' | 'False') + NAME | NUMBER | strings | '...' | 'None' | 'True' | 'False') testlist_comp: (test|star_expr) ( comp_for | (',' (test|star_expr))* [','] ) trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME subscriptlist: subscript (',' subscript)* [','] @@ -148,3 +148,10 @@ encoding_decl: NAME yield_expr: 'yield' [yield_arg] yield_arg: 'from' test | testlist + +strings: (STRING | fstring)+ +fstring: FSTRING_START fstring_content* FSTRING_END +fstring_content: FSTRING_STRING | fstring_expr +fstring_conversion: '!' NAME +fstring_expr: '{' testlist [ fstring_conversion ] [ fstring_format_spec ] '}' +fstring_format_spec: ':' fstring_content* diff --git a/pythonFiles/parso/python/parser.py b/pythonFiles/parso/python/parser.py index 1897f53e8d6f..7cdf987ab365 100644 --- a/pythonFiles/parso/python/parser.py +++ b/pythonFiles/parso/python/parser.py @@ -1,6 +1,7 @@ from parso.python import tree from parso.python.token import (DEDENT, INDENT, ENDMARKER, NEWLINE, NUMBER, - STRING, tok_name, NAME) + STRING, tok_name, NAME, FSTRING_STRING, + FSTRING_START, FSTRING_END) from parso.parser import BaseParser from parso.pgen2.parse import token_to_ilabel @@ -50,6 +51,17 @@ class structure of different scopes. } default_node = tree.PythonNode + # Names/Keywords are handled separately + _leaf_map = { + STRING: tree.String, + NUMBER: tree.Number, + NEWLINE: tree.Newline, + ENDMARKER: tree.EndMarker, + FSTRING_STRING: tree.FStringString, + FSTRING_START: tree.FStringStart, + FSTRING_END: tree.FStringEnd, + } + def __init__(self, pgen_grammar, error_recovery=True, start_symbol='file_input'): super(Parser, self).__init__(pgen_grammar, start_symbol, error_recovery=error_recovery) @@ -121,16 +133,8 @@ def convert_leaf(self, pgen_grammar, type, value, prefix, start_pos): return tree.Keyword(value, start_pos, prefix) else: return tree.Name(value, start_pos, prefix) - elif type == STRING: - return tree.String(value, start_pos, prefix) - elif type == NUMBER: - return tree.Number(value, start_pos, prefix) - elif type == NEWLINE: - return tree.Newline(value, start_pos, prefix) - elif type == ENDMARKER: - return tree.EndMarker(value, start_pos, prefix) - else: - return tree.Operator(value, start_pos, prefix) + + return self._leaf_map.get(type, tree.Operator)(value, start_pos, prefix) def error_recovery(self, pgen_grammar, stack, arcs, typ, value, start_pos, prefix, add_token_callback): diff --git a/pythonFiles/parso/python/token.py b/pythonFiles/parso/python/token.py index fb590a5f28c6..dd849b01daa7 100644 --- a/pythonFiles/parso/python/token.py +++ b/pythonFiles/parso/python/token.py @@ -32,6 +32,14 @@ ERROR_DEDENT = next(_counter) tok_name[ERROR_DEDENT] = 'ERROR_DEDENT' +FSTRING_START = next(_counter) +tok_name[FSTRING_START] = 'FSTRING_START' +FSTRING_END = next(_counter) +tok_name[FSTRING_END] = 'FSTRING_END' +FSTRING_STRING = next(_counter) +tok_name[FSTRING_STRING] = 'FSTRING_STRING' +EXCLAMATION = next(_counter) +tok_name[EXCLAMATION] = 'EXCLAMATION' # Map from operator to number (since tokenize doesn't do this) @@ -84,6 +92,7 @@ //= DOUBLESLASHEQUAL -> RARROW ... ELLIPSIS +! EXCLAMATION """ opmap = {} diff --git a/pythonFiles/parso/python/tokenize.py b/pythonFiles/parso/python/tokenize.py index ecd2437f5ebb..31f081d9b804 100644 --- a/pythonFiles/parso/python/tokenize.py +++ b/pythonFiles/parso/python/tokenize.py @@ -20,14 +20,15 @@ from parso.python.token import (tok_name, ENDMARKER, STRING, NUMBER, opmap, NAME, ERRORTOKEN, NEWLINE, INDENT, DEDENT, - ERROR_DEDENT) + ERROR_DEDENT, FSTRING_STRING, FSTRING_START, + FSTRING_END) from parso._compatibility import py_version from parso.utils import split_lines TokenCollection = namedtuple( 'TokenCollection', - 'pseudo_token single_quoted triple_quoted endpats always_break_tokens', + 'pseudo_token single_quoted triple_quoted endpats fstring_pattern_map always_break_tokens', ) BOM_UTF8_STRING = BOM_UTF8.decode('utf-8') @@ -52,32 +53,35 @@ def group(*choices, **kwargs): return start + '|'.join(choices) + ')' -def any(*choices): - return group(*choices) + '*' - - def maybe(*choices): return group(*choices) + '?' # Return the empty string, plus all of the valid string prefixes. -def _all_string_prefixes(version_info): +def _all_string_prefixes(version_info, include_fstring=False, only_fstring=False): def different_case_versions(prefix): for s in _itertools.product(*[(c, c.upper()) for c in prefix]): yield ''.join(s) # The valid string prefixes. Only contain the lower case versions, # and don't contain any permuations (include 'fr', but not # 'rf'). The various permutations will be generated. - _valid_string_prefixes = ['b', 'r', 'u'] + valid_string_prefixes = ['b', 'r', 'u'] if version_info >= (3, 0): - _valid_string_prefixes.append('br') + valid_string_prefixes.append('br') - if version_info >= (3, 6): - _valid_string_prefixes += ['f', 'fr'] + result = set(['']) + if version_info >= (3, 6) and include_fstring: + f = ['f', 'fr'] + if only_fstring: + valid_string_prefixes = f + result = set() + else: + valid_string_prefixes += f + elif only_fstring: + return set() # if we add binary f-strings, add: ['fb', 'fbr'] - result = set(['']) - for prefix in _valid_string_prefixes: + for prefix in valid_string_prefixes: for t in _itertools.permutations(prefix): # create a list with upper and lower versions of each # character @@ -102,6 +106,10 @@ def _get_token_collection(version_info): return result +fstring_string_single_line = _compile(r'(?:[^{}\r\n]+|\{\{|\}\})+') +fstring_string_multi_line = _compile(r'(?:[^{}]+|\{\{|\}\})+') + + def _create_token_collection(version_info): # Note: we use unicode matching for names ("\w") but ascii matching for # number literals. @@ -141,6 +149,9 @@ def _create_token_collection(version_info): # StringPrefix can be the empty string (making it optional). possible_prefixes = _all_string_prefixes(version_info) StringPrefix = group(*possible_prefixes) + StringPrefixWithF = group(*_all_string_prefixes(version_info, include_fstring=True)) + fstring_prefixes = _all_string_prefixes(version_info, include_fstring=True, only_fstring=True) + FStringStart = group(*fstring_prefixes) # Tail end of ' string. Single = r"[^'\\]*(?:\\.[^'\\]*)*'" @@ -150,14 +161,14 @@ def _create_token_collection(version_info): Single3 = r"[^'\\]*(?:(?:\\.|'(?!''))[^'\\]*)*'''" # Tail end of """ string. Double3 = r'[^"\\]*(?:(?:\\.|"(?!""))[^"\\]*)*"""' - Triple = group(StringPrefix + "'''", StringPrefix + '"""') + Triple = group(StringPrefixWithF + "'''", StringPrefixWithF + '"""') # Because of leftmost-then-longest match semantics, be sure to put the # longest operators first (e.g., if = came before ==, == would get # recognized as two instances of =). - Operator = group(r"\*\*=?", r">>=?", r"<<=?", r"!=", + Operator = group(r"\*\*=?", r">>=?", r"<<=?", r"//=?", r"->", - r"[+\-*/%&@`|^=<>]=?", + r"[+\-*/%&@`|^!=<>]=?", r"~") Bracket = '[][(){}]' @@ -174,7 +185,12 @@ def _create_token_collection(version_info): group("'", r'\\\r?\n'), StringPrefix + r'"[^\n"\\]*(?:\\.[^\n"\\]*)*' + group('"', r'\\\r?\n')) - PseudoExtras = group(r'\\\r?\n|\Z', Comment, Triple) + pseudo_extra_pool = [Comment, Triple] + all_quotes = '"', "'", '"""', "'''" + if fstring_prefixes: + pseudo_extra_pool.append(FStringStart + group(*all_quotes)) + + PseudoExtras = group(r'\\\r?\n|\Z', *pseudo_extra_pool) PseudoToken = group(Whitespace, capture=True) + \ group(PseudoExtras, Number, Funny, ContStr, Name, capture=True) @@ -192,18 +208,24 @@ def _create_token_collection(version_info): # including the opening quotes. single_quoted = set() triple_quoted = set() + fstring_pattern_map = {} for t in possible_prefixes: - for p in (t + '"', t + "'"): - single_quoted.add(p) - for p in (t + '"""', t + "'''"): - triple_quoted.add(p) + for quote in '"', "'": + single_quoted.add(t + quote) + + for quote in '"""', "'''": + triple_quoted.add(t + quote) + + for t in fstring_prefixes: + for quote in all_quotes: + fstring_pattern_map[t + quote] = quote ALWAYS_BREAK_TOKENS = (';', 'import', 'class', 'def', 'try', 'except', 'finally', 'while', 'with', 'return') pseudo_token_compiled = _compile(PseudoToken) return TokenCollection( pseudo_token_compiled, single_quoted, triple_quoted, endpats, - ALWAYS_BREAK_TOKENS + fstring_pattern_map, ALWAYS_BREAK_TOKENS ) @@ -226,12 +248,104 @@ def __repr__(self): self._replace(type=self._get_type_name())) +class FStringNode(object): + def __init__(self, quote): + self.quote = quote + self.parentheses_count = 0 + self.previous_lines = '' + self.last_string_start_pos = None + # In the syntax there can be multiple format_spec's nested: + # {x:{y:3}} + self.format_spec_count = 0 + + def open_parentheses(self, character): + self.parentheses_count += 1 + + def close_parentheses(self, character): + self.parentheses_count -= 1 + + def allow_multiline(self): + return len(self.quote) == 3 + + def is_in_expr(self): + return (self.parentheses_count - self.format_spec_count) > 0 + + +def _check_fstring_ending(fstring_stack, token, from_start=False): + fstring_end = float('inf') + fstring_index = None + for i, node in enumerate(fstring_stack): + if from_start: + if token.startswith(node.quote): + fstring_index = i + fstring_end = len(node.quote) + else: + continue + else: + try: + end = token.index(node.quote) + except ValueError: + pass + else: + if fstring_index is None or end < fstring_end: + fstring_index = i + fstring_end = end + return fstring_index, fstring_end + + +def _find_fstring_string(fstring_stack, line, lnum, pos): + tos = fstring_stack[-1] + if tos.is_in_expr(): + return '', pos + else: + new_pos = pos + allow_multiline = tos.allow_multiline() + if allow_multiline: + match = fstring_string_multi_line.match(line, pos) + else: + match = fstring_string_single_line.match(line, pos) + if match is None: + string = tos.previous_lines + else: + if not tos.previous_lines: + tos.last_string_start_pos = (lnum, pos) + + string = match.group(0) + for fstring_stack_node in fstring_stack: + try: + string = string[:string.index(fstring_stack_node.quote)] + except ValueError: + pass # The string was not found. + + new_pos += len(string) + if allow_multiline and string.endswith('\n'): + tos.previous_lines += string + string = '' + else: + string = tos.previous_lines + string + + return string, new_pos + + def tokenize(code, version_info, start_pos=(1, 0)): """Generate tokens from a the source code (string).""" lines = split_lines(code, keepends=True) return tokenize_lines(lines, version_info, start_pos=start_pos) +def _print_tokens(func): + """ + A small helper function to help debug the tokenize_lines function. + """ + def wrapper(*args, **kwargs): + for token in func(*args, **kwargs): + print(token) + yield token + + return wrapper + + +# @_print_tokens def tokenize_lines(lines, version_info, start_pos=(1, 0)): """ A heavily modified Python standard library tokenizer. @@ -240,7 +354,7 @@ def tokenize_lines(lines, version_info, start_pos=(1, 0)): token. This idea comes from lib2to3. The prefix contains all information that is irrelevant for the parser like newlines in parentheses or comments. """ - pseudo_token, single_quoted, triple_quoted, endpats, always_break_tokens, = \ + pseudo_token, single_quoted, triple_quoted, endpats, fstring_pattern_map, always_break_tokens, = \ _get_token_collection(version_info) paren_level = 0 # count parentheses indents = [0] @@ -257,6 +371,7 @@ def tokenize_lines(lines, version_info, start_pos=(1, 0)): additional_prefix = '' first = True lnum = start_pos[0] - 1 + fstring_stack = [] for line in lines: # loop over lines in stream lnum += 1 pos = 0 @@ -287,6 +402,37 @@ def tokenize_lines(lines, version_info, start_pos=(1, 0)): continue while pos < max: + if fstring_stack: + string, pos = _find_fstring_string(fstring_stack, line, lnum, pos) + if string: + yield PythonToken( + FSTRING_STRING, string, + fstring_stack[-1].last_string_start_pos, + # Never has a prefix because it can start anywhere and + # include whitespace. + prefix='' + ) + fstring_stack[-1].previous_lines = '' + continue + + if pos == max: + break + + rest = line[pos:] + fstring_index, end = _check_fstring_ending(fstring_stack, rest, from_start=True) + + if fstring_index is not None: + yield PythonToken( + FSTRING_END, + fstring_stack[fstring_index].quote, + (lnum, pos), + prefix=additional_prefix, + ) + additional_prefix = '' + del fstring_stack[fstring_index:] + pos += end + continue + pseudomatch = pseudo_token.match(line, pos) if not pseudomatch: # scan for tokens txt = line[pos:] @@ -311,10 +457,11 @@ def tokenize_lines(lines, version_info, start_pos=(1, 0)): if new_line and initial not in '\r\n#': new_line = False - if paren_level == 0: + if paren_level == 0 and not fstring_stack: i = 0 while line[i] == '\f': i += 1 + # TODO don't we need to change spos as well? start -= 1 if start > indents[-1]: yield PythonToken(INDENT, '', spos, '') @@ -326,11 +473,33 @@ def tokenize_lines(lines, version_info, start_pos=(1, 0)): yield PythonToken(DEDENT, '', spos, '') indents.pop() + if fstring_stack: + fstring_index, end = _check_fstring_ending(fstring_stack, token) + if fstring_index is not None: + if end != 0: + yield PythonToken(ERRORTOKEN, token[:end], spos, prefix) + prefix = '' + + yield PythonToken( + FSTRING_END, + fstring_stack[fstring_index].quote, + (lnum, spos[1] + 1), + prefix=prefix + ) + del fstring_stack[fstring_index:] + pos -= len(token) - end + continue + if (initial in numchars or # ordinary number (initial == '.' and token != '.' and token != '...')): yield PythonToken(NUMBER, token, spos, prefix) elif initial in '\r\n': - if not new_line and paren_level == 0: + if any(not f.allow_multiline() for f in fstring_stack): + # Would use fstring_stack.clear, but that's not available + # in Python 2. + fstring_stack[:] = [] + + if not new_line and paren_level == 0 and not fstring_stack: yield PythonToken(NEWLINE, token, spos, prefix) else: additional_prefix = prefix + token @@ -362,8 +531,12 @@ def tokenize_lines(lines, version_info, start_pos=(1, 0)): break else: # ordinary string yield PythonToken(STRING, token, spos, prefix) + elif token in fstring_pattern_map: # The start of an fstring. + fstring_stack.append(FStringNode(fstring_pattern_map[token])) + yield PythonToken(FSTRING_START, token, spos, prefix) elif is_identifier(initial): # ordinary name if token in always_break_tokens: + fstring_stack[:] = [] paren_level = 0 while True: indent = indents.pop() @@ -378,9 +551,18 @@ def tokenize_lines(lines, version_info, start_pos=(1, 0)): break else: if token in '([{': - paren_level += 1 + if fstring_stack: + fstring_stack[-1].open_parentheses(token) + else: + paren_level += 1 elif token in ')]}': - paren_level -= 1 + if fstring_stack: + fstring_stack[-1].close_parentheses(token) + else: + paren_level -= 1 + elif token == ':' and fstring_stack \ + and fstring_stack[-1].parentheses_count == 1: + fstring_stack[-1].format_spec_count += 1 try: # This check is needed in any case to check if it's a valid diff --git a/pythonFiles/parso/python/tree.py b/pythonFiles/parso/python/tree.py index eb977800a607..e2bf010bdff0 100644 --- a/pythonFiles/parso/python/tree.py +++ b/pythonFiles/parso/python/tree.py @@ -262,6 +262,33 @@ def _get_payload(self): return match.group(2)[:-len(match.group(1))] +class FStringString(Leaf): + """ + f-strings contain f-string expressions and normal python strings. These are + the string parts of f-strings. + """ + type = 'fstring_string' + __slots__ = () + + +class FStringStart(Leaf): + """ + f-strings contain f-string expressions and normal python strings. These are + the string parts of f-strings. + """ + type = 'fstring_start' + __slots__ = () + + +class FStringEnd(Leaf): + """ + f-strings contain f-string expressions and normal python strings. These are + the string parts of f-strings. + """ + type = 'fstring_end' + __slots__ = () + + class _StringComparisonMixin(object): def __eq__(self, other): """ diff --git a/pythonFiles/parso/tree.py b/pythonFiles/parso/tree.py index 72a14945b0f6..5316795be57c 100644 --- a/pythonFiles/parso/tree.py +++ b/pythonFiles/parso/tree.py @@ -55,7 +55,6 @@ def get_previous_sibling(self): Returns the node immediately preceding this node in this parent's children list. If this node does not have a previous sibling, it is None. - None. """ # Can't use index(); we need to test by identity for i, child in enumerate(self.parent.children): @@ -339,7 +338,7 @@ def __repr__(self): class ErrorNode(BaseNode): """ - A node that containes valid nodes/leaves that we're follow by a token that + A node that contains valid nodes/leaves that we're follow by a token that was invalid. This basically means that the leaf after this node is where Python would mark a syntax error. """ diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index b88b82ce65bf..62d75c0ec0ba 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -6,7 +6,7 @@ import * as path from 'path'; import { ConfigurationTarget, DiagnosticSeverity, Disposable, Uri, workspace } from 'vscode'; import { isTestExecution } from './constants'; import { - IAutoCompeteSettings, + IAutoCompleteSettings, IFormattingSettings, ILintingSettings, IPythonSettings, @@ -35,7 +35,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { public devOptions: string[] = []; public linting?: ILintingSettings; public formatting?: IFormattingSettings; - public autoComplete?: IAutoCompeteSettings; + public autoComplete?: IAutoCompleteSettings; public unitTest?: IUnitTestSettings; public terminal?: ITerminalSettings; public sortImports?: ISortImportSettings; @@ -219,9 +219,9 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { this.formatting.yapfPath = getAbsolutePath(systemVariables.resolveAny(this.formatting.yapfPath), workspaceRoot); // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - const autoCompleteSettings = systemVariables.resolveAny(pythonSettings.get('autoComplete'))!; + const autoCompleteSettings = systemVariables.resolveAny(pythonSettings.get('autoComplete'))!; if (this.autoComplete) { - Object.assign(this.autoComplete, autoCompleteSettings); + Object.assign(this.autoComplete, autoCompleteSettings); } else { this.autoComplete = autoCompleteSettings; } @@ -229,7 +229,8 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { this.autoComplete = this.autoComplete ? this.autoComplete : { extraPaths: [], addBrackets: false, - preloadModules: [] + preloadModules: [], + showAdvancedMembers: false }; // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion diff --git a/src/client/common/types.ts b/src/client/common/types.ts index f64617178288..5e16a4557786 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -105,7 +105,7 @@ export interface IPythonSettings { readonly linting?: ILintingSettings; readonly formatting?: IFormattingSettings; readonly unitTest?: IUnitTestSettings; - readonly autoComplete?: IAutoCompeteSettings; + readonly autoComplete?: IAutoCompleteSettings; readonly terminal?: ITerminalSettings; readonly sortImports?: ISortImportSettings; readonly workspaceSymbols?: IWorkspaceSymbolSettings; @@ -194,10 +194,11 @@ export interface IFormattingSettings { yapfPath: string; readonly yapfArgs: string[]; } -export interface IAutoCompeteSettings { +export interface IAutoCompleteSettings { readonly addBrackets: boolean; readonly extraPaths: string[]; readonly preloadModules: string[]; + readonly showAdvancedMembers: boolean; } export interface IWorkspaceSymbolSettings { readonly enabled: boolean; @@ -212,6 +213,9 @@ export interface ITerminalSettings { readonly launchArgs: string[]; readonly activateEnvironment: boolean; } +export interface IPythonAnalysisEngineSettings { + readonly showAdvancedMembers: boolean; +} export const IConfigurationService = Symbol('IConfigurationService'); diff --git a/src/client/providers/jediProxy.ts b/src/client/providers/jediProxy.ts index f0136585786f..7a8e47b62b1c 100644 --- a/src/client/providers/jediProxy.ts +++ b/src/client/providers/jediProxy.ts @@ -7,8 +7,7 @@ import * as fs from 'fs-extra'; import * as path from 'path'; import * as pidusage from 'pidusage'; import { setInterval } from 'timers'; -import { Uri } from 'vscode'; -import * as vscode from 'vscode'; +import { CancellationToken, CancellationTokenSource, CompletionItemKind, Disposable, SymbolKind, Uri } from 'vscode'; import { PythonSettings } from '../common/configSettings'; import { debounce, swallowExceptions } from '../common/decorators'; import '../common/extensions'; @@ -22,96 +21,96 @@ import * as logger from './../common/logger'; const IS_WINDOWS = /^win/.test(process.platform); -const pythonVSCodeTypeMappings = new Map(); -pythonVSCodeTypeMappings.set('none', vscode.CompletionItemKind.Value); -pythonVSCodeTypeMappings.set('type', vscode.CompletionItemKind.Class); -pythonVSCodeTypeMappings.set('tuple', vscode.CompletionItemKind.Class); -pythonVSCodeTypeMappings.set('dict', vscode.CompletionItemKind.Class); -pythonVSCodeTypeMappings.set('dictionary', vscode.CompletionItemKind.Class); -pythonVSCodeTypeMappings.set('function', vscode.CompletionItemKind.Function); -pythonVSCodeTypeMappings.set('lambda', vscode.CompletionItemKind.Function); -pythonVSCodeTypeMappings.set('generator', vscode.CompletionItemKind.Function); -pythonVSCodeTypeMappings.set('class', vscode.CompletionItemKind.Class); -pythonVSCodeTypeMappings.set('instance', vscode.CompletionItemKind.Reference); -pythonVSCodeTypeMappings.set('method', vscode.CompletionItemKind.Method); -pythonVSCodeTypeMappings.set('builtin', vscode.CompletionItemKind.Class); -pythonVSCodeTypeMappings.set('builtinfunction', vscode.CompletionItemKind.Function); -pythonVSCodeTypeMappings.set('module', vscode.CompletionItemKind.Module); -pythonVSCodeTypeMappings.set('file', vscode.CompletionItemKind.File); -pythonVSCodeTypeMappings.set('xrange', vscode.CompletionItemKind.Class); -pythonVSCodeTypeMappings.set('slice', vscode.CompletionItemKind.Class); -pythonVSCodeTypeMappings.set('traceback', vscode.CompletionItemKind.Class); -pythonVSCodeTypeMappings.set('frame', vscode.CompletionItemKind.Class); -pythonVSCodeTypeMappings.set('buffer', vscode.CompletionItemKind.Class); -pythonVSCodeTypeMappings.set('dictproxy', vscode.CompletionItemKind.Class); -pythonVSCodeTypeMappings.set('funcdef', vscode.CompletionItemKind.Function); -pythonVSCodeTypeMappings.set('property', vscode.CompletionItemKind.Property); -pythonVSCodeTypeMappings.set('import', vscode.CompletionItemKind.Module); -pythonVSCodeTypeMappings.set('keyword', vscode.CompletionItemKind.Keyword); -pythonVSCodeTypeMappings.set('constant', vscode.CompletionItemKind.Variable); -pythonVSCodeTypeMappings.set('variable', vscode.CompletionItemKind.Variable); -pythonVSCodeTypeMappings.set('value', vscode.CompletionItemKind.Value); -pythonVSCodeTypeMappings.set('param', vscode.CompletionItemKind.Variable); -pythonVSCodeTypeMappings.set('statement', vscode.CompletionItemKind.Keyword); - -const pythonVSCodeSymbolMappings = new Map(); -pythonVSCodeSymbolMappings.set('none', vscode.SymbolKind.Variable); -pythonVSCodeSymbolMappings.set('type', vscode.SymbolKind.Class); -pythonVSCodeSymbolMappings.set('tuple', vscode.SymbolKind.Class); -pythonVSCodeSymbolMappings.set('dict', vscode.SymbolKind.Class); -pythonVSCodeSymbolMappings.set('dictionary', vscode.SymbolKind.Class); -pythonVSCodeSymbolMappings.set('function', vscode.SymbolKind.Function); -pythonVSCodeSymbolMappings.set('lambda', vscode.SymbolKind.Function); -pythonVSCodeSymbolMappings.set('generator', vscode.SymbolKind.Function); -pythonVSCodeSymbolMappings.set('class', vscode.SymbolKind.Class); -pythonVSCodeSymbolMappings.set('instance', vscode.SymbolKind.Class); -pythonVSCodeSymbolMappings.set('method', vscode.SymbolKind.Method); -pythonVSCodeSymbolMappings.set('builtin', vscode.SymbolKind.Class); -pythonVSCodeSymbolMappings.set('builtinfunction', vscode.SymbolKind.Function); -pythonVSCodeSymbolMappings.set('module', vscode.SymbolKind.Module); -pythonVSCodeSymbolMappings.set('file', vscode.SymbolKind.File); -pythonVSCodeSymbolMappings.set('xrange', vscode.SymbolKind.Array); -pythonVSCodeSymbolMappings.set('slice', vscode.SymbolKind.Class); -pythonVSCodeSymbolMappings.set('traceback', vscode.SymbolKind.Class); -pythonVSCodeSymbolMappings.set('frame', vscode.SymbolKind.Class); -pythonVSCodeSymbolMappings.set('buffer', vscode.SymbolKind.Array); -pythonVSCodeSymbolMappings.set('dictproxy', vscode.SymbolKind.Class); -pythonVSCodeSymbolMappings.set('funcdef', vscode.SymbolKind.Function); -pythonVSCodeSymbolMappings.set('property', vscode.SymbolKind.Property); -pythonVSCodeSymbolMappings.set('import', vscode.SymbolKind.Module); -pythonVSCodeSymbolMappings.set('keyword', vscode.SymbolKind.Variable); -pythonVSCodeSymbolMappings.set('constant', vscode.SymbolKind.Constant); -pythonVSCodeSymbolMappings.set('variable', vscode.SymbolKind.Variable); -pythonVSCodeSymbolMappings.set('value', vscode.SymbolKind.Variable); -pythonVSCodeSymbolMappings.set('param', vscode.SymbolKind.Variable); -pythonVSCodeSymbolMappings.set('statement', vscode.SymbolKind.Variable); -pythonVSCodeSymbolMappings.set('boolean', vscode.SymbolKind.Boolean); -pythonVSCodeSymbolMappings.set('int', vscode.SymbolKind.Number); -pythonVSCodeSymbolMappings.set('longlean', vscode.SymbolKind.Number); -pythonVSCodeSymbolMappings.set('float', vscode.SymbolKind.Number); -pythonVSCodeSymbolMappings.set('complex', vscode.SymbolKind.Number); -pythonVSCodeSymbolMappings.set('string', vscode.SymbolKind.String); -pythonVSCodeSymbolMappings.set('unicode', vscode.SymbolKind.String); -pythonVSCodeSymbolMappings.set('list', vscode.SymbolKind.Array); - -function getMappedVSCodeType(pythonType: string): vscode.CompletionItemKind { +const pythonVSCodeTypeMappings = new Map(); +pythonVSCodeTypeMappings.set('none', CompletionItemKind.Value); +pythonVSCodeTypeMappings.set('type', CompletionItemKind.Class); +pythonVSCodeTypeMappings.set('tuple', CompletionItemKind.Class); +pythonVSCodeTypeMappings.set('dict', CompletionItemKind.Class); +pythonVSCodeTypeMappings.set('dictionary', CompletionItemKind.Class); +pythonVSCodeTypeMappings.set('function', CompletionItemKind.Function); +pythonVSCodeTypeMappings.set('lambda', CompletionItemKind.Function); +pythonVSCodeTypeMappings.set('generator', CompletionItemKind.Function); +pythonVSCodeTypeMappings.set('class', CompletionItemKind.Class); +pythonVSCodeTypeMappings.set('instance', CompletionItemKind.Reference); +pythonVSCodeTypeMappings.set('method', CompletionItemKind.Method); +pythonVSCodeTypeMappings.set('builtin', CompletionItemKind.Class); +pythonVSCodeTypeMappings.set('builtinfunction', CompletionItemKind.Function); +pythonVSCodeTypeMappings.set('module', CompletionItemKind.Module); +pythonVSCodeTypeMappings.set('file', CompletionItemKind.File); +pythonVSCodeTypeMappings.set('xrange', CompletionItemKind.Class); +pythonVSCodeTypeMappings.set('slice', CompletionItemKind.Class); +pythonVSCodeTypeMappings.set('traceback', CompletionItemKind.Class); +pythonVSCodeTypeMappings.set('frame', CompletionItemKind.Class); +pythonVSCodeTypeMappings.set('buffer', CompletionItemKind.Class); +pythonVSCodeTypeMappings.set('dictproxy', CompletionItemKind.Class); +pythonVSCodeTypeMappings.set('funcdef', CompletionItemKind.Function); +pythonVSCodeTypeMappings.set('property', CompletionItemKind.Property); +pythonVSCodeTypeMappings.set('import', CompletionItemKind.Module); +pythonVSCodeTypeMappings.set('keyword', CompletionItemKind.Keyword); +pythonVSCodeTypeMappings.set('constant', CompletionItemKind.Variable); +pythonVSCodeTypeMappings.set('variable', CompletionItemKind.Variable); +pythonVSCodeTypeMappings.set('value', CompletionItemKind.Value); +pythonVSCodeTypeMappings.set('param', CompletionItemKind.Variable); +pythonVSCodeTypeMappings.set('statement', CompletionItemKind.Keyword); + +const pythonVSCodeSymbolMappings = new Map(); +pythonVSCodeSymbolMappings.set('none', SymbolKind.Variable); +pythonVSCodeSymbolMappings.set('type', SymbolKind.Class); +pythonVSCodeSymbolMappings.set('tuple', SymbolKind.Class); +pythonVSCodeSymbolMappings.set('dict', SymbolKind.Class); +pythonVSCodeSymbolMappings.set('dictionary', SymbolKind.Class); +pythonVSCodeSymbolMappings.set('function', SymbolKind.Function); +pythonVSCodeSymbolMappings.set('lambda', SymbolKind.Function); +pythonVSCodeSymbolMappings.set('generator', SymbolKind.Function); +pythonVSCodeSymbolMappings.set('class', SymbolKind.Class); +pythonVSCodeSymbolMappings.set('instance', SymbolKind.Class); +pythonVSCodeSymbolMappings.set('method', SymbolKind.Method); +pythonVSCodeSymbolMappings.set('builtin', SymbolKind.Class); +pythonVSCodeSymbolMappings.set('builtinfunction', SymbolKind.Function); +pythonVSCodeSymbolMappings.set('module', SymbolKind.Module); +pythonVSCodeSymbolMappings.set('file', SymbolKind.File); +pythonVSCodeSymbolMappings.set('xrange', SymbolKind.Array); +pythonVSCodeSymbolMappings.set('slice', SymbolKind.Class); +pythonVSCodeSymbolMappings.set('traceback', SymbolKind.Class); +pythonVSCodeSymbolMappings.set('frame', SymbolKind.Class); +pythonVSCodeSymbolMappings.set('buffer', SymbolKind.Array); +pythonVSCodeSymbolMappings.set('dictproxy', SymbolKind.Class); +pythonVSCodeSymbolMappings.set('funcdef', SymbolKind.Function); +pythonVSCodeSymbolMappings.set('property', SymbolKind.Property); +pythonVSCodeSymbolMappings.set('import', SymbolKind.Module); +pythonVSCodeSymbolMappings.set('keyword', SymbolKind.Variable); +pythonVSCodeSymbolMappings.set('constant', SymbolKind.Constant); +pythonVSCodeSymbolMappings.set('variable', SymbolKind.Variable); +pythonVSCodeSymbolMappings.set('value', SymbolKind.Variable); +pythonVSCodeSymbolMappings.set('param', SymbolKind.Variable); +pythonVSCodeSymbolMappings.set('statement', SymbolKind.Variable); +pythonVSCodeSymbolMappings.set('boolean', SymbolKind.Boolean); +pythonVSCodeSymbolMappings.set('int', SymbolKind.Number); +pythonVSCodeSymbolMappings.set('longlean', SymbolKind.Number); +pythonVSCodeSymbolMappings.set('float', SymbolKind.Number); +pythonVSCodeSymbolMappings.set('complex', SymbolKind.Number); +pythonVSCodeSymbolMappings.set('string', SymbolKind.String); +pythonVSCodeSymbolMappings.set('unicode', SymbolKind.String); +pythonVSCodeSymbolMappings.set('list', SymbolKind.Array); + +function getMappedVSCodeType(pythonType: string): CompletionItemKind { if (pythonVSCodeTypeMappings.has(pythonType)) { const value = pythonVSCodeTypeMappings.get(pythonType); if (value) { return value; } } - return vscode.CompletionItemKind.Keyword; + return CompletionItemKind.Keyword; } -function getMappedVSCodeSymbol(pythonType: string): vscode.SymbolKind { +function getMappedVSCodeSymbol(pythonType: string): SymbolKind { if (pythonVSCodeSymbolMappings.has(pythonType)) { const value = pythonVSCodeSymbolMappings.get(pythonType); if (value) { return value; } } - return vscode.SymbolKind.Variable; + return SymbolKind.Variable; } export enum CommandType { @@ -131,7 +130,7 @@ commandNames.set(CommandType.Hover, 'tooltip'); commandNames.set(CommandType.Usages, 'usages'); commandNames.set(CommandType.Symbols, 'names'); -export class JediProxy implements vscode.Disposable { +export class JediProxy implements Disposable { private proc?: ChildProcess; private pythonSettings: PythonSettings; private cmdId: number = 0; @@ -151,7 +150,7 @@ export class JediProxy implements vscode.Disposable { public constructor(private extensionRootDir: string, workspacePath: string, private serviceContainer: IServiceContainer) { this.workspacePath = workspacePath; - this.pythonSettings = PythonSettings.getInstance(vscode.Uri.file(workspacePath)); + this.pythonSettings = PythonSettings.getInstance(Uri.file(workspacePath)); this.lastKnownPythonInterpreter = this.pythonSettings.pythonPath; this.logger = serviceContainer.get(ILogger); this.pythonSettings.on('change', () => this.pythonSettingsChangeHandler()); @@ -315,7 +314,8 @@ export class JediProxy implements vscode.Disposable { args.push('custom'); args.push(this.pythonSettings.jediPath); } - if (Array.isArray(this.pythonSettings.autoComplete.preloadModules) && + if (this.pythonSettings.autoComplete && + Array.isArray(this.pythonSettings.autoComplete.preloadModules) && this.pythonSettings.autoComplete.preloadModules.length > 0) { const modules = this.pythonSettings.autoComplete.preloadModules.filter(m => m.trim().length > 0).join(','); args.push(modules); @@ -636,7 +636,8 @@ export class JediProxy implements vscode.Disposable { } private getConfig() { // Add support for paths relative to workspace. - const extraPaths = this.pythonSettings.autoComplete.extraPaths.map(extraPath => { + const extraPaths = this.pythonSettings.autoComplete ? + this.pythonSettings.autoComplete.extraPaths.map(extraPath => { if (path.isAbsolute(extraPath)) { return extraPath; } @@ -644,7 +645,7 @@ export class JediProxy implements vscode.Disposable { return ''; } return path.join(this.workspacePath, extraPath); - }); + }) : []; // Always add workspace path into extra paths. if (typeof this.workspacePath === 'string') { @@ -686,7 +687,7 @@ export interface ICommand { interface IExecutionCommand extends ICommand { id: number; deferred?: Deferred; - token: vscode.CancellationToken; + token: CancellationToken; delay?: number; } @@ -739,9 +740,9 @@ export interface IReference { } export interface IAutoCompleteItem { - type: vscode.CompletionItemKind; - rawType: vscode.CompletionItemKind; - kind: vscode.SymbolKind; + type: CompletionItemKind; + rawType: CompletionItemKind; + kind: SymbolKind; text: string; description: string; raw_docstring: string; @@ -755,8 +756,8 @@ export interface IDefinitionRange { } export interface IDefinition { rawType: string; - type: vscode.CompletionItemKind; - kind: vscode.SymbolKind; + type: CompletionItemKind; + kind: SymbolKind; text: string; fileName: string; container: string; @@ -764,22 +765,22 @@ export interface IDefinition { } export interface IHoverItem { - kind: vscode.SymbolKind; + kind: SymbolKind; text: string; description: string; docstring: string; signature: string; } -export class JediProxyHandler implements vscode.Disposable { - private commandCancellationTokenSources: Map; +export class JediProxyHandler implements Disposable { + private commandCancellationTokenSources: Map; public get JediProxy(): JediProxy { return this.jediProxy; } public constructor(private jediProxy: JediProxy) { - this.commandCancellationTokenSources = new Map(); + this.commandCancellationTokenSources = new Map(); } public dispose() { @@ -788,7 +789,7 @@ export class JediProxyHandler implements vscode.Dispos } } - public sendCommand(cmd: ICommand, token?: vscode.CancellationToken): Promise { + public sendCommand(cmd: ICommand, token?: CancellationToken): Promise { const executionCmd = >cmd; executionCmd.id = executionCmd.id || this.jediProxy.getNextCommandId(); @@ -799,7 +800,7 @@ export class JediProxyHandler implements vscode.Dispos } } - const cancellation = new vscode.CancellationTokenSource(); + const cancellation = new CancellationTokenSource(); this.commandCancellationTokenSources.set(cmd.command, cancellation); executionCmd.token = cancellation.token; @@ -810,7 +811,7 @@ export class JediProxyHandler implements vscode.Dispos }); } - public sendCommandNonCancellableCommand(cmd: ICommand, token?: vscode.CancellationToken): Promise { + public sendCommandNonCancellableCommand(cmd: ICommand, token?: CancellationToken): Promise { const executionCmd = >cmd; executionCmd.id = executionCmd.id || this.jediProxy.getNextCommandId(); if (token) { diff --git a/src/client/providers/signatureProvider.ts b/src/client/providers/signatureProvider.ts index 12dad261c39b..cf1014296519 100644 --- a/src/client/providers/signatureProvider.ts +++ b/src/client/providers/signatureProvider.ts @@ -1,8 +1,15 @@ 'use strict'; import { EOL } from 'os'; -import * as vscode from 'vscode'; -import { CancellationToken, Position, SignatureHelp, TextDocument } from 'vscode'; +import { + CancellationToken, + ParameterInformation, + Position, + SignatureHelp, + SignatureHelpProvider, + SignatureInformation, + TextDocument +} from 'vscode'; import { JediFactory } from '../languageServices/jediProxyFactory'; import { captureTelemetry } from '../telemetry'; import { SIGNATURE } from '../telemetry/constants'; @@ -45,9 +52,9 @@ function extractParamDocString(paramName: string, docString: string): string { return paramDocString.trim(); } -export class PythonSignatureProvider implements vscode.SignatureHelpProvider { +export class PythonSignatureProvider implements SignatureHelpProvider { public constructor(private jediFactory: JediFactory) { } - private static parseData(data: proxy.IArgumentsResult): vscode.SignatureHelp { + private static parseData(data: proxy.IArgumentsResult): SignatureHelp { if (data && Array.isArray(data.definitions) && data.definitions.length > 0) { const signature = new SignatureHelp(); signature.activeSignature = 0; @@ -60,29 +67,36 @@ export class PythonSignatureProvider implements vscode.SignatureHelpProvider { // Some functions do not come with parameter docs let label: string; let documentation: string; - const validParamInfo = def.params && def.params.length > 0 && def.docstring.startsWith(`${def.name}(`); + const validParamInfo = def.params && def.params.length > 0 && def.docstring && def.docstring.startsWith(`${def.name}(`); if (validParamInfo) { const docLines = def.docstring.splitLines(); label = docLines.shift().trim(); documentation = docLines.join(EOL).trim(); } else { - label = def.description; - documentation = def.docstring; + if (def.params && def.params.length > 0) { + label = `${def.name}(${def.params.map(p => p.name).join(', ')})`; + documentation = def.docstring; + } else { + label = def.description; + documentation = def.docstring; + } } - const sig = { + // tslint:disable-next-line:no-object-literal-type-assertion + const sig = { label, documentation, parameters: [] }; - if (validParamInfo) { + if (def.params && def.params.length) { sig.parameters = def.params.map(arg => { if (arg.docstring.length === 0) { arg.docstring = extractParamDocString(arg.name, def.docstring); } - return { + // tslint:disable-next-line:no-object-literal-type-assertion + return { documentation: arg.docstring.length > 0 ? arg.docstring : arg.description, label: arg.name.trim() }; diff --git a/src/test/definitions/hover.ptvs.test.ts b/src/test/definitions/hover.ptvs.test.ts index 8c3b981ca4bb..d2a456efd4bd 100644 --- a/src/test/definitions/hover.ptvs.test.ts +++ b/src/test/definitions/hover.ptvs.test.ts @@ -49,10 +49,15 @@ suite('Hover Definition (Analysis Engine)', () => { assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '30,11', 'End position is incorrect'); assert.equal(def[0].contents.length, 1, 'Invalid content items'); - const lines = normalizeMarkedString(def[0].contents[0]).splitLines(); - assert.equal(lines.length, 2, 'incorrect number of lines'); - assert.equal(lines[0].trim(), 'obj.method1: method method1 of one.Class1 objects', 'function signature line #1 is incorrect'); - assert.equal(lines[1].trim(), 'This is method1', 'function signature line #2 is incorrect'); + const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); + const expected = [ + 'obj.method1:', + 'method method1 of one.Class1 objects', + '```html', + 'This is method1', + '```' + ]; + verifySignatureLines(actual, expected); }); test('Across files', async () => { @@ -61,10 +66,15 @@ suite('Hover Definition (Analysis Engine)', () => { assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '1,0', 'Start position is incorrect'); assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '1,12', 'End position is incorrect'); - const lines = normalizeMarkedString(def[0].contents[0]).splitLines(); - assert.equal(lines.length, 2, 'incorrect number of lines'); - assert.equal(lines[0].trim(), 'two.ct().fun: method fun of two.ct objects', 'function signature line #1 is incorrect'); - assert.equal(lines[1].trim(), 'This is fun', 'function signature line #2 is incorrect'); + const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); + const expected = [ + 'two.ct().fun:', + 'method fun of two.ct objects', + '```html', + 'This is fun', + '```' + ]; + verifySignatureLines(actual, expected); }); test('With Unicode Characters', async () => { @@ -73,13 +83,18 @@ suite('Hover Definition (Analysis Engine)', () => { assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '25,0', 'Start position is incorrect'); assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '25,7', 'End position is incorrect'); - const lines = normalizeMarkedString(def[0].contents[0]).splitLines(); - assert.equal(lines.length, 5, 'incorrect number of lines'); - assert.equal(lines[0].trim(), 'Foo.bar: def four.Foo.bar()', 'function signature line #1 is incorrect'); - assert.equal(lines[1].trim(), '说明 - keep this line, it works', 'function signature line #2 is incorrect'); - assert.equal(lines[2].trim(), 'delete following line, it works', 'function signature line #3 is incorrect'); - assert.equal(lines[3].trim(), '如果存在需要等待审批或正在执行的任务,将不刷新页面', 'function signature line #4 is incorrect'); - assert.equal(lines[4].trim(), 'declared in Foo', 'function signature line #5 is incorrect'); + const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); + const expected = [ + 'Foo.bar:', + 'four.Foo.bar() -> bool', + '```html', + '说明 - keep this line, it works', + 'delete following line, it works', + '如果存在需要等待审批或正在执行的任务,将不刷新页面', + '```', + 'declared in Foo' + ]; + verifySignatureLines(actual, expected); }); test('Across files with Unicode Characters', async () => { @@ -88,11 +103,16 @@ suite('Hover Definition (Analysis Engine)', () => { assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '1,0', 'Start position is incorrect'); assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '1,16', 'End position is incorrect'); - const lines = normalizeMarkedString(def[0].contents[0]).splitLines(); - assert.equal(lines.length, 3, 'incorrect number of lines'); - assert.equal(lines[0].trim(), 'four.showMessage: def four.showMessage()', 'function signature line #1 is incorrect'); - assert.equal(lines[1].trim(), 'Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи.', 'function signature line #2 is incorrect'); - assert.equal(lines[2].trim(), 'Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.', 'function signature line #3 is incorrect'); + const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); + const expected = [ + 'four.showMessage:', + 'four.showMessage()', + '```html', + 'Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи.', + 'Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.', + '```' + ]; + verifySignatureLines(actual, expected); }); test('Nothing for keywords (class)', async () => { @@ -111,10 +131,22 @@ suite('Hover Definition (Analysis Engine)', () => { assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '11,7', 'Start position is incorrect'); assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '11,18', 'End position is incorrect'); - const lines = normalizeMarkedString(def[0].contents[0]).splitLines(); - assert.equal(lines.length, 9, 'incorrect number of lines'); - assert.equal(lines[0].trim(), 'misc.Random: class misc.Random(_random.Random)', 'function signature line #1 is incorrect'); - assert.equal(lines[1].trim(), 'Random number generator base class used by bound module functions.', 'function signature line #2 is incorrect'); + const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); + const expected = [ + 'misc.Random:', + 'class misc.Random(_random.Random)', + 'Random number generator base class used by bound module functions.', + '```html', + 'Used to instantiate instances of Random to get generators that don\'t', + 'share state.', + 'Class Random can also be subclassed if you want to use a different basic', + 'generator of your own devising: in that case, override the following', + 'methods: random(), seed(), getstate(), and setstate().', + 'Optionally, implement a getrandbits() method so that randrange()', + 'can cover arbitrarily large ranges.', + '```' + ]; + verifySignatureLines(actual, expected); }); test('Highlight Method', async () => { @@ -123,10 +155,13 @@ suite('Hover Definition (Analysis Engine)', () => { assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '12,0', 'Start position is incorrect'); assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '12,12', 'End position is incorrect'); - const lines = normalizeMarkedString(def[0].contents[0]).splitLines(); - assert.equal(lines.length, 2, 'incorrect number of lines'); - assert.equal(lines[0].trim(), 'rnd2.randint: method randint of misc.Random objects -> int', 'function signature line #1 is incorrect'); - assert.equal(lines[1].trim(), 'Return random integer in range [a, b], including both end points.', 'function signature line #2 is incorrect'); + const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); + const expected = [ + 'rnd2.randint:', + 'method randint of misc.Random objects -> int', + 'Return random integer in range [a, b], including both end points.' + ]; + verifySignatureLines(actual, expected); }); test('Highlight Function', async () => { @@ -135,11 +170,14 @@ suite('Hover Definition (Analysis Engine)', () => { assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '8,6', 'Start position is incorrect'); assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '8,15', 'End position is incorrect'); - const lines = normalizeMarkedString(def[0].contents[0]).splitLines(); - assert.equal(lines.length, 3, 'incorrect number of lines'); - assert.equal(lines[0].trim(), 'math.acos: built-in function acos(x)', 'function signature line #1 is incorrect'); - assert.equal(lines[1].trim(), 'acos(x)', 'function signature line #2 is incorrect'); - assert.equal(lines[2].trim(), 'Return the arc cosine (measured in radians) of x.', 'function signature line #3 is incorrect'); + const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); + const expected = [ + 'math.acos:', + 'built-in function acos(x)', + 'acos(x)', + 'Return the arc cosine (measured in radians) of x.' + ]; + verifySignatureLines(actual, expected); }); test('Highlight Multiline Method Signature', async () => { @@ -148,11 +186,16 @@ suite('Hover Definition (Analysis Engine)', () => { assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '14,4', 'Start position is incorrect'); assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '14,15', 'End position is incorrect'); - const lines = normalizeMarkedString(def[0].contents[0]).splitLines(); - assert.equal(lines.length, 3, 'incorrect number of lines'); - assert.equal(lines[0].trim(), 'misc.Thread: class misc.Thread(_Verbose)', 'function signature line #1 is incorrect'); - assert.equal(lines[1].trim(), 'A class that represents a thread of control.', 'function signature line #2 is incorrect'); - + const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); + const expected = [ + 'misc.Thread:', + 'class misc.Thread(_Verbose)', + 'A class that represents a thread of control.', + '```html', + 'This class can be safely subclassed in a limited fashion.', + '```' + ]; + verifySignatureLines(actual, expected); }); test('Variable', async () => { @@ -181,4 +224,11 @@ suite('Hover Definition (Analysis Engine)', () => { assert.fail(contents, '', '\'Return a capitalized version of S/Return a copy of the string S with only its first character\' message missing', 'compare'); } }); + + function verifySignatureLines(actual: string[], expected: string[]) { + assert.equal(actual.length, expected.length, 'incorrect number of lines'); + for (let i = 0; i < actual.length; i += 1) { + assert.equal(actual[i].trim(), expected[i], `signature line ${i + 1} is incorrect`); + } + } }); diff --git a/src/test/signature/signature.jedi.test.ts b/src/test/signature/signature.jedi.test.ts index 1ab80cce964d..0d3a0b5ed90b 100644 --- a/src/test/signature/signature.jedi.test.ts +++ b/src/test/signature/signature.jedi.test.ts @@ -74,13 +74,15 @@ suite('Signatures (Jedi)', () => { new SignatureHelpResult(0, 3, 0, 0, null), new SignatureHelpResult(0, 4, 0, 0, null), new SignatureHelpResult(0, 5, 0, 0, null), - new SignatureHelpResult(0, 6, 1, 0, 'start'), - new SignatureHelpResult(0, 7, 1, 0, 'start'), - new SignatureHelpResult(0, 8, 1, 1, 'stop'), - new SignatureHelpResult(0, 9, 1, 1, 'stop'), - new SignatureHelpResult(0, 10, 1, 1, 'stop'), - new SignatureHelpResult(0, 11, 1, 2, 'step'), - new SignatureHelpResult(1, 0, 1, 2, 'step') + new SignatureHelpResult(0, 6, 1, 0, 'stop'), + new SignatureHelpResult(0, 7, 1, 0, 'stop') + // new SignatureHelpResult(0, 6, 1, 0, 'start'), + // new SignatureHelpResult(0, 7, 1, 0, 'start'), + // new SignatureHelpResult(0, 8, 1, 1, 'stop'), + // new SignatureHelpResult(0, 9, 1, 1, 'stop'), + // new SignatureHelpResult(0, 10, 1, 1, 'stop'), + // new SignatureHelpResult(0, 11, 1, 2, 'step'), + // new SignatureHelpResult(1, 0, 1, 2, 'step') ]; const document = await openDocument(path.join(autoCompPath, 'basicSig.py')); diff --git a/src/test/signature/signature.ptvs.test.ts b/src/test/signature/signature.ptvs.test.ts index 68720e33cde1..ad8e58508342 100644 --- a/src/test/signature/signature.ptvs.test.ts +++ b/src/test/signature/signature.ptvs.test.ts @@ -74,14 +74,13 @@ suite('Signatures (Analysis Engine)', () => { new SignatureHelpResult(0, 3, 1, -1, null), new SignatureHelpResult(0, 4, 1, -1, null), new SignatureHelpResult(0, 5, 1, -1, null), - new SignatureHelpResult(0, 6, 1, 0, 'stop'), - new SignatureHelpResult(0, 7, 1, 0, 'stop') - // https://github.com/Microsoft/PTVS/issues/3869 - // new SignatureHelpResult(0, 8, 1, 1, 'stop'), - // new SignatureHelpResult(0, 9, 1, 1, 'stop'), - // new SignatureHelpResult(0, 10, 1, 1, 'stop'), - // new SignatureHelpResult(0, 11, 1, 2, 'step'), - // new SignatureHelpResult(1, 0, 1, 2, 'step') + new SignatureHelpResult(0, 6, 1, 0, 'start'), + new SignatureHelpResult(0, 7, 1, 0, 'start'), + new SignatureHelpResult(0, 8, 1, 1, 'stop'), + new SignatureHelpResult(0, 9, 1, 1, 'stop'), + new SignatureHelpResult(0, 10, 1, 1, 'stop'), + new SignatureHelpResult(0, 11, 1, 2, 'step'), + new SignatureHelpResult(1, 0, 1, 2, 'step') ]; const document = await openDocument(path.join(autoCompPath, 'basicSig.py')); From ed62e53438cfeaf51821a36b1c2ab5477f5dc243 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 17 Apr 2018 13:09:51 -0700 Subject: [PATCH 122/433] Add support for hit count breakpoints (#1411) Fixes #1409 --- news/3 Code Health/1409.md | 1 + src/client/debugger/mainV2.ts | 1 + 2 files changed, 2 insertions(+) create mode 100644 news/3 Code Health/1409.md diff --git a/news/3 Code Health/1409.md b/news/3 Code Health/1409.md new file mode 100644 index 000000000000..24e5dd3195ec --- /dev/null +++ b/news/3 Code Health/1409.md @@ -0,0 +1 @@ +Add support for [hit count breakpoints](https://code.visualstudio.com/docs/editor/debugging#_advanced-breakpoint-topics) in the experimental debugger. diff --git a/src/client/debugger/mainV2.ts b/src/client/debugger/mainV2.ts index 2d8855e7354b..65f78dffa2aa 100644 --- a/src/client/debugger/mainV2.ts +++ b/src/client/debugger/mainV2.ts @@ -67,6 +67,7 @@ export class PythonDebugger extends DebugSession { body.supportsEvaluateForHovers = true; body.supportsModulesRequest = true; body.supportsValueFormattingOptions = true; + body.supportsHitConditionalBreakpoints = true; body.supportsSetExpression = true; body.exceptionBreakpointFilters = [ { From 03ef890d089b86936c1461acf3a9faddc3e10f52 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Tue, 17 Apr 2018 13:22:58 -0700 Subject: [PATCH 123/433] Replace unzip package (#1419) * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * #1096 The if statement is automatically formatted incorrectly * Merge fix * Add more tests * More tests * Typo * Test * Also better handle multiline arguments * Add a couple missing periods [skip ci] * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * Merge fix * #1257 On type formatting errors for args and kwargs * Handle f-strings * Stop importing from test code * #1308 Single line statements leading to an indentation on the next line * #726 editing python after inline if statement invalid indent * Undo change * Move constant * Harden LS startup error checks * #1364 Intellisense doesn't work after specific const string * Telemetry for the analysis enging * PR feedback * Fix typo * Test baseline update * Jedi 0.12 * Priority to goto_defition * News * Replace unzip --- package.json | 2 +- src/client/activation/downloader.ts | 42 ++++++++++++++++++++++------- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 434dc231a1d8..74853cf944cd 100644 --- a/package.json +++ b/package.json @@ -1848,6 +1848,7 @@ "md5": "2.2.1", "minimatch": "3.0.4", "named-js-regexp": "1.3.3", + "node-stream-zip": "^1.6.0", "opn": "5.3.0", "pidusage": "1.2.0", "reflect-metadata": "0.1.12", @@ -1862,7 +1863,6 @@ "uint64be": "1.0.1", "unicode": "10.0.0", "untildify": "3.0.2", - "unzip": "0.1.11", "vscode-debugadapter": "1.28.0", "vscode-debugprotocol": "1.28.0", "vscode-extension-telemetry": "0.0.15", diff --git a/src/client/activation/downloader.ts b/src/client/activation/downloader.ts index de634d627316..c48b0b4b2503 100644 --- a/src/client/activation/downloader.ts +++ b/src/client/activation/downloader.ts @@ -5,7 +5,6 @@ import * as fs from 'fs'; import * as path from 'path'; import * as request from 'request'; import * as requestProgress from 'request-progress'; -import * as unzip from 'unzip'; import { ExtensionContext, OutputChannel, ProgressLocation, window } from 'vscode'; import { STANDARD_OUTPUT_CHANNEL } from '../common/constants'; import { noop } from '../common/core.utils'; @@ -16,6 +15,9 @@ import { IServiceContainer } from '../ioc/types'; import { HashVerifier } from './hashVerifier'; import { PlatformData } from './platformData'; +// tslint:disable-next-line:no-require-imports no-var-requires +const StreamZip = require('node-stream-zip'); + const downloadUriPrefix = 'https://pvsc.blob.core.windows.net/python-analysis'; const downloadBaseFileName = 'python-analysis-vscode'; const downloadVersion = '0.1.0'; @@ -109,15 +111,37 @@ export class AnalysisEngineDownloader { const installFolder = path.join(extensionPath, this.engineFolder); const deferred = createDeferred(); - fs.createReadStream(tempFilePath) - .pipe(unzip.Extract({ path: installFolder })) - .on('finish', () => { - deferred.resolve(); - }) - .on('error', (err) => { - deferred.reject(err); + const title = 'Extracting files... '; + await window.withProgress({ + location: ProgressLocation.Window, + title + }, (progress) => { + const zip = new StreamZip({ + file: tempFilePath, + storeEntries: true }); - await deferred.promise; + + let totalFiles = 0; + let extractedFiles = 0; + zip.on('ready', () => { + totalFiles = zip.entriesCount; + if (!fs.existsSync(installFolder)) { + fs.mkdirSync(installFolder); + } + zip.extract(null, installFolder, (err, count) => { + if (err) { + deferred.reject(err); + } else { + deferred.resolve(); + } + zip.close(); + }); + }).on('extract', (entry, file) => { + extractedFiles += 1; + progress.report({ message: `${title}${Math.round(100 * extractedFiles / totalFiles)}%` }); + }); + return deferred.promise; + }); this.output.append('done.'); // Set file to executable From abe12df107c17d88ec5ad7089bbbf41625f3ee5a Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 17 Apr 2018 13:54:06 -0700 Subject: [PATCH 124/433] Disable logging in unit tests (#1414) Fixes #1413 --- src/test/debugger/attach.ptvsd.test.ts | 2 +- src/test/debugger/run.test.ts | 2 +- src/test/debugger/web.framework.test.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/debugger/attach.ptvsd.test.ts b/src/test/debugger/attach.ptvsd.test.ts index b5f0f9839e1b..b72172d2560c 100644 --- a/src/test/debugger/attach.ptvsd.test.ts +++ b/src/test/debugger/attach.ptvsd.test.ts @@ -80,7 +80,7 @@ suite('Attach Debugger - Experimental', () => { type: 'pythonExperimental', port: port, host: 'localhost', - logToFile: true, + logToFile: false, debugOptions: [DebugOptions.RedirectOutput] }; const platformService = TypeMoq.Mock.ofType(); diff --git a/src/test/debugger/run.test.ts b/src/test/debugger/run.test.ts index f1ebdc3ebd18..cca794c8e345 100644 --- a/src/test/debugger/run.test.ts +++ b/src/test/debugger/run.test.ts @@ -52,7 +52,7 @@ suite('Run without Debugging', () => { args: [], env: { PYTHONPATH: PTVSD_PATH }, envFile: '', - logToFile: true, + logToFile: false, type: debuggerType }; diff --git a/src/test/debugger/web.framework.test.ts b/src/test/debugger/web.framework.test.ts index ca3ca6c6a19f..0036fe9041f3 100644 --- a/src/test/debugger/web.framework.test.ts +++ b/src/test/debugger/web.framework.test.ts @@ -52,7 +52,7 @@ suite(`Django and Flask Debugging: ${debuggerType}`, () => { args: [], env, envFile: '', - logToFile: true, + logToFile: false, type: debuggerType }; From 730a959c944244c970a2a86565b23934267c85e6 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 17 Apr 2018 14:22:54 -0700 Subject: [PATCH 125/433] Add news entry for source reference support in the experimental debugger --- news/1 Enhancements/1333.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/1 Enhancements/1333.md diff --git a/news/1 Enhancements/1333.md b/news/1 Enhancements/1333.md new file mode 100644 index 000000000000..dbf4296ba5bb --- /dev/null +++ b/news/1 Enhancements/1333.md @@ -0,0 +1 @@ +Added support for source references (remote debugging without having the source code locally) in the experimental debugger. From 25aeec78e5c8ba958ce53743aee73464215df4b4 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 17 Apr 2018 14:58:37 -0700 Subject: [PATCH 126/433] Pin node-upstream-zip dependency (#1422) --- package.json | 2 +- yarn.lock | 81 +++++----------------------------------------------- 2 files changed, 8 insertions(+), 75 deletions(-) diff --git a/package.json b/package.json index 74853cf944cd..4eade1bf39ca 100644 --- a/package.json +++ b/package.json @@ -1848,7 +1848,7 @@ "md5": "2.2.1", "minimatch": "3.0.4", "named-js-regexp": "1.3.3", - "node-stream-zip": "^1.6.0", + "node-stream-zip": "1.6.0", "opn": "5.3.0", "pidusage": "1.2.0", "reflect-metadata": "0.1.12", diff --git a/yarn.lock b/yarn.lock index 743208b1dcf3..4f8867ffeff1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -470,13 +470,6 @@ binary-extensions@^1.0.0: version "1.11.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205" -"binary@>= 0.3.0 < 1": - version "0.3.0" - resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79" - dependencies: - buffers "~0.1.1" - chainsaw "~0.1.0" - block-stream@*: version "0.0.9" resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" @@ -557,10 +550,6 @@ buffer-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" -buffers@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" - builtin-modules@^1.0.0, builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -630,12 +619,6 @@ chai@^4.1.2: pathval "^1.0.0" type-detect "^4.0.0" -chainsaw@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98" - dependencies: - traverse ">=0.3.0 <0.4" - chalk@^0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.5.1.tgz#663b3a648b68b55d04690d49167aa837858f2174" @@ -1489,15 +1472,6 @@ fstream-ignore@^1.0.5: inherits "2" minimatch "^3.0.0" -"fstream@>= 0.1.30 < 1": - version "0.1.31" - resolved "https://registry.yarnpkg.com/fstream/-/fstream-0.1.31.tgz#7337f058fbbbbefa8c9f561a28cab0849202c988" - dependencies: - graceful-fs "~3.0.2" - inherits "~2.0.0" - mkdirp "0.5" - rimraf "2" - fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: version "1.0.11" resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" @@ -1721,7 +1695,7 @@ graceful-fs@4.X, graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, gr version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" -graceful-fs@^3.0.0, graceful-fs@~3.0.2: +graceful-fs@^3.0.0: version "3.0.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-3.0.11.tgz#7613c778a1afea62f25c630a086d7f3acbbdd818" dependencies: @@ -2943,13 +2917,6 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -"match-stream@>= 0.0.2 < 1": - version "0.0.2" - resolved "https://registry.yarnpkg.com/match-stream/-/match-stream-0.0.2.tgz#99eb050093b34dffade421b9ac0b410a9cfa17cf" - dependencies: - buffers "~0.1.1" - readable-stream "~1.0.0" - md5.js@1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" @@ -3087,7 +3054,7 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@0.5, mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: +mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" dependencies: @@ -3201,6 +3168,10 @@ node-pre-gyp@^0.6.39: tar "^2.2.1" tar-pack "^3.4.0" +node-stream-zip@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/node-stream-zip/-/node-stream-zip-1.6.0.tgz#ad4b660dc72c33725af776de34785af6ffd7c203" + node.extend@~1.1.2: version "1.1.6" resolved "https://registry.yarnpkg.com/node.extend/-/node.extend-1.1.6.tgz#a7b882c82d6c93a4863a5504bd5de8ec86258b96" @@ -3402,10 +3373,6 @@ osenv@^0.1.4: os-homedir "^1.0.0" os-tmpdir "^1.0.0" -"over@>= 0.0.5 < 1": - version "0.0.5" - resolved "https://registry.yarnpkg.com/over/-/over-0.0.5.tgz#f29852e70fd7e25f360e013a8ec44c82aedb5708" - p-map@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" @@ -3586,15 +3553,6 @@ pseudomap@^1.0.1, pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" -"pullstream@>= 0.4.1 < 1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/pullstream/-/pullstream-0.4.1.tgz#d6fb3bf5aed697e831150eb1002c25a3f8ae1314" - dependencies: - over ">= 0.0.5 < 1" - readable-stream "~1.0.31" - setimmediate ">= 1.0.2 < 2" - slice-stream ">= 1.0.0 < 2" - pump@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" @@ -3673,7 +3631,7 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" -"readable-stream@>=1.0.33-1 <1.1.0-0", readable-stream@~1.0.0, readable-stream@~1.0.17, readable-stream@~1.0.31: +"readable-stream@>=1.0.33-1 <1.1.0-0", readable-stream@~1.0.17: version "1.0.34" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" dependencies: @@ -4044,10 +4002,6 @@ set-value@^2.0.0: is-plain-object "^2.0.3" split-string "^3.0.1" -"setimmediate@>= 1.0.1 < 2", "setimmediate@>= 1.0.2 < 2": - version "1.0.5" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - shortid@^2.2.8: version "2.2.8" resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.8.tgz#033b117d6a2e975804f6f0969dbe7d3d0b355131" @@ -4076,12 +4030,6 @@ slash@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" -"slice-stream@>= 1.0.0 < 2": - version "1.0.0" - resolved "https://registry.yarnpkg.com/slice-stream/-/slice-stream-1.0.0.tgz#5b33bd66f013b1a7f86460b03d463dec39ad3ea0" - dependencies: - readable-stream "~1.0.31" - snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" @@ -4522,10 +4470,6 @@ tough-cookie@~2.3.0, tough-cookie@~2.3.3: dependencies: punycode "^1.4.1" -"traverse@>=0.3.0 <0.4": - version "0.3.9" - resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" - tree-kill@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.0.tgz#5846786237b4239014f05db156b643212d4c6f36" @@ -4696,17 +4640,6 @@ untildify@3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/untildify/-/untildify-3.0.2.tgz#7f1f302055b3fea0f3e81dc78eb36766cb65e3f1" -unzip@0.1.11: - version "0.1.11" - resolved "https://registry.yarnpkg.com/unzip/-/unzip-0.1.11.tgz#89749c63b058d7d90d619f86b98aa1535d3b97f0" - dependencies: - binary ">= 0.3.0 < 1" - fstream ">= 0.1.30 < 1" - match-stream ">= 0.0.2 < 1" - pullstream ">= 0.4.1 < 1" - readable-stream "~1.0.31" - setimmediate ">= 1.0.1 < 2" - upath@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/upath/-/upath-1.0.4.tgz#ee2321ba0a786c50973db043a50b7bcba822361d" From f2f2fb4b59961304dc188b96ce1d77a2af0a956b Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 17 Apr 2018 16:32:50 -0700 Subject: [PATCH 127/433] Make remote debugging unit tests more robust Fixes #1424 --- src/test/debugger/attach.ptvsd.test.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/test/debugger/attach.ptvsd.test.ts b/src/test/debugger/attach.ptvsd.test.ts index b72172d2560c..fd67270f7edc 100644 --- a/src/test/debugger/attach.ptvsd.test.ts +++ b/src/test/debugger/attach.ptvsd.test.ts @@ -98,8 +98,6 @@ suite('Attach Debugger - Experimental', () => { debugClient.waitForEvent('initialized') ]); - await debugClient.configurationDoneRequest(); - const stdOutPromise = debugClient.assertOutput('stdout', 'this is stdout'); const stdErrPromise = debugClient.assertOutput('stderr', 'this is stderr'); @@ -112,14 +110,14 @@ suite('Attach Debugger - Experimental', () => { source: { path: breakpointLocation.path } }); const exceptionBreakpointPromise = debugClient.setExceptionBreakpointsRequest({ filters: [] }); + const breakpointStoppedPromise = debugClient.assertStoppedLocation('breakpoint', breakpointLocation); await Promise.all([ - breakpointPromise, - exceptionBreakpointPromise, - stdOutPromise, stdErrPromise + breakpointPromise, exceptionBreakpointPromise, + debugClient.configurationDoneRequest(), debugClient.threadsRequest(), + stdOutPromise, stdErrPromise, + breakpointStoppedPromise ]); - await debugClient.assertStoppedLocation('breakpoint', breakpointLocation); - await Promise.all([ continueDebugging(debugClient), debugClient.assertOutput('stdout', 'this is print'), @@ -127,7 +125,8 @@ suite('Attach Debugger - Experimental', () => { debugClient.waitForEvent('terminated') ]); } - test('Confirm we are able to attach to a running program', async () => { + test('Confirm we are able to attach to a running program', async function () { + this.retries(0); await testAttachingToRemoteProcess(path.dirname(fileToDebug), path.dirname(fileToDebug), IS_WINDOWS); }); test('Confirm local and remote paths are translated', async () => { From 29126413e23f9f6b4765c5f880f62f3ad8056b30 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 17 Apr 2018 16:41:13 -0700 Subject: [PATCH 128/433] Add support for log points Fixes #1306 --- news/1 Enhancements/1306.md | 1 + src/client/debugger/mainV2.ts | 1 + 2 files changed, 2 insertions(+) create mode 100644 news/1 Enhancements/1306.md diff --git a/news/1 Enhancements/1306.md b/news/1 Enhancements/1306.md new file mode 100644 index 000000000000..8b97fceaf12a --- /dev/null +++ b/news/1 Enhancements/1306.md @@ -0,0 +1 @@ +Add support for [logpoints](https://code.visualstudio.com/docs/editor/debugging#_logpoints) in the experimental debugger. diff --git a/src/client/debugger/mainV2.ts b/src/client/debugger/mainV2.ts index 65f78dffa2aa..8393a7bd5fbc 100644 --- a/src/client/debugger/mainV2.ts +++ b/src/client/debugger/mainV2.ts @@ -69,6 +69,7 @@ export class PythonDebugger extends DebugSession { body.supportsValueFormattingOptions = true; body.supportsHitConditionalBreakpoints = true; body.supportsSetExpression = true; + body.supportsLogPoints = true; body.exceptionBreakpointFilters = [ { filter: 'raised', From 8fd2b993f2e685b7e1fd073f4ba4c16d46c8d50c Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 18 Apr 2018 10:29:00 -0700 Subject: [PATCH 129/433] Change how debug options are captured in launch.json (#1395) Fixes #1326 --- package.json | 105 ++++++++++-------- src/client/debugger/Common/Contracts.ts | 44 ++++++-- .../DebugClients/RemoteDebugClient.ts | 8 +- .../DebugClients/localDebugClientV2.ts | 2 +- .../DebugServers/RemoteDebugServer.ts | 10 +- src/client/debugger/Main.ts | 10 +- .../debugger/configProviders/baseProvider.ts | 34 +++--- .../configProviders/pythonProvider.ts | 17 ++- .../configProviders/pythonV2Provider.ts | 71 ++++++++---- src/client/extension.ts | 4 +- src/test/debugger/attach.test.ts | 4 +- .../configProvider/provider.attach.test.ts | 72 +++++++++--- 12 files changed, 257 insertions(+), 124 deletions(-) diff --git a/package.json b/package.json index 4eade1bf39ca..906ad9e94b14 100644 --- a/package.json +++ b/package.json @@ -934,22 +934,6 @@ "description": "Absolute path to the working directory of the program being debugged. Default is the root directory of the file (leave empty).", "default": "${workspaceFolder}" }, - "debugOptions": { - "type": "array", - "description": "Advanced options, view read me for further details.", - "items": { - "type": "string", - "enum": [ - "RedirectOutput", - "DebugStdLib", - "Django", - "Jinja", - "Sudo", - "Pyramid" - ] - }, - "default": [] - }, "env": { "type": "object", "description": "Environment variables defined as a key value pair. Property ends up being the Environment Variable and the value of the property ends up being the value of the Env Variable.", @@ -974,25 +958,48 @@ "type": "boolean", "description": "Enable logging of debugger events to a log file.", "default": false + }, + "redirectOutput": { + "type": "boolean", + "description": "Redirect output.", + "default": true + }, + "debugStdLib": { + "type": "boolean", + "description": "Debug standard library code.", + "default": false + }, + "django": { + "type": "boolean", + "description": "Django debugging.", + "default": false + }, + "jinja": { + "enum": [ + true, + false, + null + ], + "description": "Jinja template debugging (e.g. Flask).", + "default": null + }, + "sudo": { + "type": "boolean", + "description": "Running debug program under elevated permissions (on Unix).", + "default": false + }, + "pyramid": { + "type": "boolean", + "description": "Whether debugging Pyramid applications", + "default": false } } }, "attach": { "required": [ - "port", - "remoteRoot" + "port" ], "properties": { - "localRoot": { - "type": "string", - "description": "Local source root that corrresponds to the 'remoteRoot'.", - "default": "${workspaceFolder}" - }, - "remoteRoot": { - "type": "string", - "description": "The source root of the remote host.", - "default": "" - }, "port": { "type": "number", "description": "Debug port to attach", @@ -1003,23 +1010,9 @@ "description": "IP Address of the of remote server (default is localhost or use 127.0.0.1).", "default": "localhost" }, - "debugOptions": { - "type": "array", - "description": "Advanced options, view read me for further details.", - "items": { - "type": "string", - "enum": [ - "RedirectOutput", - "DebugStdLib", - "Django", - "Jinja" - ] - }, - "default": [] - }, "pathMappings": { "type": "array", - "label": "Additional path mappings.", + "label": "Path mappings.", "items": { "type": "object", "label": "Path mapping", @@ -1031,7 +1024,7 @@ "localRoot": { "type": "string", "label": "Local source root.", - "default": "" + "default": "${workspaceFolder}" }, "remoteRoot": { "type": "string", @@ -1046,6 +1039,30 @@ "type": "boolean", "description": "Enable logging of debugger events to a log file.", "default": false + }, + "redirectOutput": { + "type": "boolean", + "description": "Redirect output.", + "default": true + }, + "debugStdLib": { + "type": "boolean", + "description": "Debug standard library code.", + "default": false + }, + "django": { + "type": "boolean", + "description": "Django debugging.", + "default": false + }, + "jinja": { + "enum": [ + true, + false, + null + ], + "description": "Jinja template debugging (e.g. Flask).", + "default": null } } } diff --git a/src/client/debugger/Common/Contracts.ts b/src/client/debugger/Common/Contracts.ts index f54917476a66..d56e190355de 100644 --- a/src/client/debugger/Common/Contracts.ts +++ b/src/client/debugger/Common/Contracts.ts @@ -58,7 +58,23 @@ export interface ExceptionHandling { export type DebuggerType = 'python' | 'pythonExperimental'; -export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArguments { +export interface AdditionalLaunchDebugOptions { + redirectOutput?: boolean; + django?: boolean; + jinja?: boolean; + debugStdLib?: boolean; + sudo?: boolean; + pyramid?: boolean; +} + +export interface AdditionalAttachDebugOptions { + redirectOutput?: boolean; + django?: boolean; + jinja?: boolean; + debugStdLib?: boolean; +} + +export interface BaseLaunchRequestArguments extends DebugProtocol.LaunchRequestArguments { type?: DebuggerType; /** An absolute path to the program to debug. */ module?: string; @@ -67,32 +83,42 @@ export interface LaunchRequestArguments extends DebugProtocol.LaunchRequestArgum /** Automatically stop target after launch. If not specified, target does not stop. */ stopOnEntry?: boolean; args: string[]; - applicationType?: string; cwd?: string; debugOptions?: DebugOptions[]; env?: Object; envFile: string; - exceptionHandling?: ExceptionHandling; console?: 'none' | 'integratedTerminal' | 'externalTerminal'; port?: number; host?: string; logToFile?: boolean; } -export interface AttachRequestArguments extends DebugProtocol.AttachRequestArguments { +export interface LaunchRequestArgumentsV1 extends BaseLaunchRequestArguments { + exceptionHandling?: ExceptionHandling; +} + +export interface LaunchRequestArguments extends BaseLaunchRequestArguments, AdditionalLaunchDebugOptions { +} + +export interface BaseAttachRequestArguments extends DebugProtocol.AttachRequestArguments { type?: DebuggerType; /** An absolute path to local directory with source. */ - debugOptions?: string[]; - localRoot?: string; - remoteRoot?: string; port?: number; host?: string; - secret?: string; logToFile?: boolean; - pathMappings?: { localRoot: string; remoteRoot: string }[]; debugOptions?: DebugOptions[]; } +export interface AttachRequestArgumentsV1 extends BaseAttachRequestArguments { + secret?: string; + localRoot: string; + remoteRoot: string; +} +export interface AttachRequestArguments extends BaseAttachRequestArguments, AdditionalAttachDebugOptions { + localRoot?: string; + remoteRoot?: string; + pathMappings?: { localRoot: string; remoteRoot: string }[]; +} export interface IDebugServer { port: number; host?: string; diff --git a/src/client/debugger/DebugClients/RemoteDebugClient.ts b/src/client/debugger/DebugClients/RemoteDebugClient.ts index a7bee8e9f578..172b8ba79bd4 100644 --- a/src/client/debugger/DebugClients/RemoteDebugClient.ts +++ b/src/client/debugger/DebugClients/RemoteDebugClient.ts @@ -1,15 +1,15 @@ import { DebugSession } from 'vscode-debugadapter'; -import { AttachRequestArguments, IPythonProcess } from '../Common/Contracts'; +import { AttachRequestArgumentsV1, BaseAttachRequestArguments, IPythonProcess } from '../Common/Contracts'; import { BaseDebugServer } from '../DebugServers/BaseDebugServer'; import { RemoteDebugServer } from '../DebugServers/RemoteDebugServer'; import { RemoteDebugServerV2 } from '../DebugServers/RemoteDebugServerv2'; import { DebugClient, DebugType } from './DebugClient'; -export class RemoteDebugClient extends DebugClient { +export class RemoteDebugClient extends DebugClient { private pythonProcess?: IPythonProcess; private debugServer?: BaseDebugServer; // tslint:disable-next-line:no-any - constructor(args: AttachRequestArguments, debugSession: DebugSession) { + constructor(args: T, debugSession: DebugSession) { super(args, debugSession); } @@ -19,7 +19,7 @@ export class RemoteDebugClient extends DebugClient { this.debugServer = new RemoteDebugServerV2(this.debugSession, undefined as any, this.args); } else { this.pythonProcess = pythonProcess!; - this.debugServer = new RemoteDebugServer(this.debugSession, this.pythonProcess!, this.args); + this.debugServer = new RemoteDebugServer(this.debugSession, this.pythonProcess!, this.args as {} as AttachRequestArgumentsV1); } return this.debugServer!; } diff --git a/src/client/debugger/DebugClients/localDebugClientV2.ts b/src/client/debugger/DebugClients/localDebugClientV2.ts index 417efba39e26..9028cb10f01e 100644 --- a/src/client/debugger/DebugClients/localDebugClientV2.ts +++ b/src/client/debugger/DebugClients/localDebugClientV2.ts @@ -22,7 +22,7 @@ export class LocalDebugClientV2 extends LocalDebugClient { return ['-m', this.args.module, ...programArgs]; } if (this.args.program && this.args.program.length > 0) { - return ['--file', this.args.program, ...programArgs]; + return [this.args.program, ...programArgs]; } return programArgs; } diff --git a/src/client/debugger/DebugServers/RemoteDebugServer.ts b/src/client/debugger/DebugServers/RemoteDebugServer.ts index f954a15b5147..8574bce7aff7 100644 --- a/src/client/debugger/DebugServers/RemoteDebugServer.ts +++ b/src/client/debugger/DebugServers/RemoteDebugServer.ts @@ -1,8 +1,8 @@ -// tslint:disable:quotemark ordered-imports no-any no-empty curly member-ordering one-line max-func-body-length no-var-self prefer-const cyclomatic-complexity prefer-template +// tslint:disable:quotemark ordered-imports no-any no-empty curly member-ordering one-line max-func-body-length no-var-self prefer-const cyclomatic-complexity prefer-template no-this-assignment "use strict"; import { DebugSession, OutputEvent } from "vscode-debugadapter"; -import { IPythonProcess, IDebugServer, AttachRequestArguments, VALID_DEBUG_OPTIONS } from "../Common/Contracts"; +import { IPythonProcess, IDebugServer, AttachRequestArgumentsV1, VALID_DEBUG_OPTIONS } from "../Common/Contracts"; import * as net from "net"; import { BaseDebugServer } from "./BaseDebugServer"; import { SocketStream } from "../../common/net/socket/SocketStream"; @@ -15,8 +15,8 @@ const AttachCommandBytes: Buffer = new Buffer("ATCH", "ascii"); export class RemoteDebugServer extends BaseDebugServer { private socket?: net.Socket; - private args: AttachRequestArguments; - constructor(debugSession: DebugSession, pythonProcess: IPythonProcess, args: AttachRequestArguments) { + private args: AttachRequestArgumentsV1; + constructor(debugSession: DebugSession, pythonProcess: IPythonProcess, args: AttachRequestArgumentsV1) { super(debugSession, pythonProcess); this.args = args; } @@ -29,7 +29,7 @@ export class RemoteDebugServer extends BaseDebugServer { catch (ex) { } this.socket = undefined; } - private stream: SocketStream; + private stream!: SocketStream; public Start(): Promise { return new Promise((resolve, reject) => { let that = this; diff --git a/src/client/debugger/Main.ts b/src/client/debugger/Main.ts index eaadb8cc028e..115f7c948785 100644 --- a/src/client/debugger/Main.ts +++ b/src/client/debugger/Main.ts @@ -14,9 +14,9 @@ import { DebugProtocol } from "vscode-debugprotocol"; import { DEBUGGER } from '../../client/telemetry/constants'; import { DebuggerTelemetry } from '../../client/telemetry/types'; import { isNotInstalledError } from '../common/helpers'; -import { enum_EXCEPTION_STATE, IPythonBreakpoint, IPythonException, PythonBreakpointConditionKind, PythonBreakpointPassCountKind, PythonEvaluationResultReprKind } from "./Common/Contracts"; +import { enum_EXCEPTION_STATE, IPythonBreakpoint, IPythonException, PythonBreakpointConditionKind, PythonBreakpointPassCountKind, PythonEvaluationResultReprKind, LaunchRequestArgumentsV1, AttachRequestArgumentsV1 } from "./Common/Contracts"; import { IDebugServer, IPythonEvaluationResult, IPythonModule, IPythonStackFrame, IPythonThread } from "./Common/Contracts"; -import { AttachRequestArguments, DebugOptions, LaunchRequestArguments, PythonEvaluationResultFlags, TelemetryEvent } from "./Common/Contracts"; +import { DebugOptions, LaunchRequestArguments, PythonEvaluationResultFlags, TelemetryEvent } from "./Common/Contracts"; import { getPythonExecutable, validatePath } from './Common/Utils'; import { DebugClient } from "./DebugClients/DebugClient"; import { CreateAttachDebugClient, CreateLaunchDebugClient } from "./DebugClients/DebugFactory"; @@ -203,8 +203,8 @@ export class PythonDebugger extends LoggingDebugSession { this.sendEvent(new OutputEvent(output, outputChannel)); } private entryResponse?: DebugProtocol.LaunchResponse; - private launchArgs!: LaunchRequestArguments; - private attachArgs!: AttachRequestArguments; + private launchArgs!: LaunchRequestArgumentsV1; + private attachArgs!: AttachRequestArgumentsV1; private canStartDebugger(): Promise { return Promise.resolve(true); } @@ -280,7 +280,7 @@ export class PythonDebugger extends LoggingDebugSession { this.sendErrorResponse(response, 200, errorMsg); }); } - protected attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArguments) { + protected attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArgumentsV1) { if (args.logToFile === true) { logger.setup(LogLevel.Verbose, true); } diff --git a/src/client/debugger/configProviders/baseProvider.ts b/src/client/debugger/configProviders/baseProvider.ts index 849994a735b1..d1395d8dda6b 100644 --- a/src/client/debugger/configProviders/baseProvider.ts +++ b/src/client/debugger/configProviders/baseProvider.ts @@ -3,6 +3,8 @@ 'use strict'; +// tslint:disable:no-invalid-template-strings + import { injectable, unmanaged } from 'inversify'; import * as path from 'path'; import { CancellationToken, DebugConfiguration, DebugConfigurationProvider, ProviderResult, Uri, WorkspaceFolder } from 'vscode'; @@ -11,23 +13,21 @@ import { PythonLanguage } from '../../common/constants'; import { IFileSystem, IPlatformService } from '../../common/platform/types'; import { IConfigurationService } from '../../common/types'; import { IServiceContainer } from '../../ioc/types'; -import { AttachRequestArguments, DebuggerType, DebugOptions, LaunchRequestArguments } from '../Common/Contracts'; - -// tslint:disable:no-invalid-template-strings +import { BaseAttachRequestArguments, BaseLaunchRequestArguments, DebuggerType, DebugOptions } from '../Common/Contracts'; -export type PythonLaunchDebugConfiguration = DebugConfiguration & LaunchRequestArguments; -export type PythonAttachDebugConfiguration = DebugConfiguration & AttachRequestArguments; +export type PythonLaunchDebugConfiguration = DebugConfiguration & T; +export type PythonAttachDebugConfiguration = DebugConfiguration & T; @injectable() -export abstract class BaseConfigurationProvider implements DebugConfigurationProvider { +export abstract class BaseConfigurationProvider implements DebugConfigurationProvider { constructor(@unmanaged() public debugType: DebuggerType, protected serviceContainer: IServiceContainer) { } public resolveDebugConfiguration(folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, token?: CancellationToken): ProviderResult { const workspaceFolder = this.getWorkspaceFolder(folder); if (debugConfiguration.request === 'attach') { - this.provideAttachDefaults(workspaceFolder, debugConfiguration as PythonAttachDebugConfiguration); + this.provideAttachDefaults(workspaceFolder, debugConfiguration as PythonAttachDebugConfiguration); } else { - const config = debugConfiguration as PythonLaunchDebugConfiguration; + const config = debugConfiguration as PythonLaunchDebugConfiguration; const numberOfSettings = Object.keys(config); if ((config.noDebug === true && numberOfSettings.length === 1) || numberOfSettings.length === 0) { @@ -42,24 +42,22 @@ export abstract class BaseConfigurationProvider implements DebugConfigurationPro this.provideLaunchDefaults(workspaceFolder, config); } + + const dbgConfig = (debugConfiguration as (BaseLaunchRequestArguments | BaseAttachRequestArguments)); + if (Array.isArray(dbgConfig.debugOptions)) { + dbgConfig.debugOptions = dbgConfig.debugOptions!.filter((item, pos) => dbgConfig.debugOptions!.indexOf(item) === pos); + } return debugConfiguration; } - protected provideAttachDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonAttachDebugConfiguration): void { + protected provideAttachDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonAttachDebugConfiguration): void { if (!Array.isArray(debugConfiguration.debugOptions)) { debugConfiguration.debugOptions = []; } - // Always redirect output. - if (debugConfiguration.debugOptions.indexOf(DebugOptions.RedirectOutput) === -1) { - debugConfiguration.debugOptions.push(DebugOptions.RedirectOutput); - } if (!debugConfiguration.host) { debugConfiguration.host = 'localhost'; } - if (!debugConfiguration.localRoot && workspaceFolder) { - debugConfiguration.localRoot = workspaceFolder.fsPath; - } } - protected provideLaunchDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonLaunchDebugConfiguration): void { + protected provideLaunchDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonLaunchDebugConfiguration): void { this.resolveAndUpdatePythonPath(workspaceFolder, debugConfiguration); if (typeof debugConfiguration.cwd !== 'string' && workspaceFolder) { debugConfiguration.cwd = workspaceFolder.fsPath; @@ -122,7 +120,7 @@ export abstract class BaseConfigurationProvider implements DebugConfigurationPro return editor.document.fileName; } } - private resolveAndUpdatePythonPath(workspaceFolder: Uri | undefined, debugConfiguration: PythonLaunchDebugConfiguration): void { + private resolveAndUpdatePythonPath(workspaceFolder: Uri | undefined, debugConfiguration: PythonLaunchDebugConfiguration): void { if (!debugConfiguration) { return; } diff --git a/src/client/debugger/configProviders/pythonProvider.ts b/src/client/debugger/configProviders/pythonProvider.ts index 1b349bf2c465..69b2fe743319 100644 --- a/src/client/debugger/configProviders/pythonProvider.ts +++ b/src/client/debugger/configProviders/pythonProvider.ts @@ -4,12 +4,25 @@ 'use strict'; import { inject, injectable } from 'inversify'; +import { Uri } from 'vscode'; import { IServiceContainer } from '../../ioc/types'; -import { BaseConfigurationProvider } from './baseProvider'; +import { AttachRequestArgumentsV1, DebugOptions, LaunchRequestArgumentsV1 } from '../Common/Contracts'; +import { BaseConfigurationProvider, PythonAttachDebugConfiguration } from './baseProvider'; @injectable() -export class PythonDebugConfigurationProvider extends BaseConfigurationProvider { +export class PythonDebugConfigurationProvider extends BaseConfigurationProvider { constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { super('python', serviceContainer); } + protected provideAttachDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonAttachDebugConfiguration): void { + super.provideAttachDefaults(workspaceFolder, debugConfiguration); + const debugOptions = debugConfiguration.debugOptions!; + // Always redirect output. + if (debugOptions.indexOf(DebugOptions.RedirectOutput) === -1) { + debugOptions.push(DebugOptions.RedirectOutput); + } + if (!debugConfiguration.localRoot && workspaceFolder) { + debugConfiguration.localRoot = workspaceFolder.fsPath; + } + } } diff --git a/src/client/debugger/configProviders/pythonV2Provider.ts b/src/client/debugger/configProviders/pythonV2Provider.ts index 47e587daabc7..e8953534c1b0 100644 --- a/src/client/debugger/configProviders/pythonV2Provider.ts +++ b/src/client/debugger/configProviders/pythonV2Provider.ts @@ -7,50 +7,81 @@ import { inject, injectable } from 'inversify'; import { Uri } from 'vscode'; import { IPlatformService } from '../../common/platform/types'; import { IServiceContainer } from '../../ioc/types'; -import { DebugOptions } from '../Common/Contracts'; +import { AttachRequestArguments, DebugOptions, LaunchRequestArguments } from '../Common/Contracts'; import { BaseConfigurationProvider, PythonAttachDebugConfiguration, PythonLaunchDebugConfiguration } from './baseProvider'; @injectable() -export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvider { +export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvider { constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { super('pythonExperimental', serviceContainer); } - protected provideLaunchDefaults(workspaceFolder: Uri, debugConfiguration: PythonLaunchDebugConfiguration): void { + protected provideLaunchDefaults(workspaceFolder: Uri, debugConfiguration: PythonLaunchDebugConfiguration): void { super.provideLaunchDefaults(workspaceFolder, debugConfiguration); - - debugConfiguration.stopOnEntry = false; - debugConfiguration.debugOptions = Array.isArray(debugConfiguration.debugOptions) ? debugConfiguration.debugOptions : []; - - // Add PTVSD specific flags. + const debugOptions = debugConfiguration.debugOptions!; + if (debugConfiguration.debugStdLib) { + this.debugOption(debugOptions, DebugOptions.DebugStdLib); + } + if (debugConfiguration.django) { + this.debugOption(debugOptions, DebugOptions.Django); + } + if (debugConfiguration.jinja) { + this.debugOption(debugOptions, DebugOptions.Jinja); + } + if (debugConfiguration.redirectOutput || debugConfiguration.redirectOutput === undefined) { + this.debugOption(debugOptions, DebugOptions.RedirectOutput); + } + if (debugConfiguration.sudo) { + this.debugOption(debugOptions, DebugOptions.Sudo); + } if (this.serviceContainer.get(IPlatformService).isWindows) { - debugConfiguration.debugOptions.push(DebugOptions.FixFilePathCase); + this.debugOption(debugOptions, DebugOptions.FixFilePathCase); } if (debugConfiguration.module && debugConfiguration.module.toUpperCase() === 'FLASK' - && debugConfiguration.debugOptions.indexOf(DebugOptions.Jinja) === -1) { - debugConfiguration.debugOptions.push(DebugOptions.Jinja); + && debugOptions.indexOf(DebugOptions.Jinja) === -1 + && debugConfiguration.jinja !== false) { + this.debugOption(debugOptions, DebugOptions.Jinja); } } - protected provideAttachDefaults(workspaceFolder: Uri, debugConfiguration: PythonAttachDebugConfiguration): void { + protected provideAttachDefaults(workspaceFolder: Uri, debugConfiguration: PythonAttachDebugConfiguration): void { super.provideAttachDefaults(workspaceFolder, debugConfiguration); - - debugConfiguration.debugOptions = Array.isArray(debugConfiguration.debugOptions) ? debugConfiguration.debugOptions : []; + const debugOptions = debugConfiguration.debugOptions!; + if (debugConfiguration.debugStdLib) { + this.debugOption(debugOptions, DebugOptions.DebugStdLib); + } + if (debugConfiguration.django) { + this.debugOption(debugOptions, DebugOptions.Django); + } + if (debugConfiguration.jinja) { + this.debugOption(debugOptions, DebugOptions.Jinja); + } + if (debugConfiguration.redirectOutput || debugConfiguration.redirectOutput === undefined) { + this.debugOption(debugOptions, DebugOptions.RedirectOutput); + } // We'll need paths to be fixed only in the case where local and remote hosts are the same // I.e. only if hostName === 'localhost' or '127.0.0.1' or '' const isLocalHost = !debugConfiguration.host || debugConfiguration.host === 'localhost' || debugConfiguration.host === '127.0.0.1'; if (this.serviceContainer.get(IPlatformService).isWindows && isLocalHost) { - debugConfiguration.debugOptions.push(DebugOptions.FixFilePathCase); + this.debugOption(debugOptions, DebugOptions.FixFilePathCase); } if (this.serviceContainer.get(IPlatformService).isWindows) { - debugConfiguration.debugOptions.push(DebugOptions.WindowsClient); + this.debugOption(debugOptions, DebugOptions.WindowsClient); } if (!debugConfiguration.pathMappings) { debugConfiguration.pathMappings = []; } - debugConfiguration.pathMappings!.push({ - localRoot: debugConfiguration.localRoot, - remoteRoot: debugConfiguration.remoteRoot - }); + if (debugConfiguration.localRoot && debugConfiguration.remoteRoot) { + debugConfiguration.pathMappings!.push({ + localRoot: debugConfiguration.localRoot, + remoteRoot: debugConfiguration.remoteRoot + }); + } + } + private debugOption(debugOptions: DebugOptions[], debugOption: DebugOptions) { + if (debugOptions.indexOf(debugOption) >= 0) { + return; + } + debugOptions.push(debugOption); } } diff --git a/src/client/extension.ts b/src/client/extension.ts index ed1b0d09443b..627cf7c39385 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -26,6 +26,7 @@ import { registerTypes as commonRegisterTypes } from './common/serviceRegistry'; import { StopWatch } from './common/stopWatch'; import { GLOBAL_MEMENTO, IConfigurationService, IDisposableRegistry, ILogger, IMemento, IOutputChannel, IPersistentStateFactory, WORKSPACE_MEMENTO } from './common/types'; import { registerTypes as variableRegisterTypes } from './common/variables/serviceRegistry'; +import { AttachRequestArguments, LaunchRequestArguments } from './debugger/Common/Contracts'; import { BaseConfigurationProvider } from './debugger/configProviders/baseProvider'; import { registerTypes as debugConfigurationRegisterTypes } from './debugger/configProviders/serviceRegistry'; import { IDebugConfigurationProvider } from './debugger/types'; @@ -153,7 +154,8 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(new TerminalProvider(serviceContainer)); context.subscriptions.push(new WorkspaceSymbols(serviceContainer)); - serviceContainer.getAll(IDebugConfigurationProvider).forEach(debugConfig => { + type ConfigurationProvider = BaseConfigurationProvider; + serviceContainer.getAll(IDebugConfigurationProvider).forEach(debugConfig => { context.subscriptions.push(debug.registerDebugConfigurationProvider(debugConfig.debugType, debugConfig)); }); activationDeferred.resolve(); diff --git a/src/test/debugger/attach.test.ts b/src/test/debugger/attach.test.ts index 7747a0450c01..c03e0cdf3573 100644 --- a/src/test/debugger/attach.test.ts +++ b/src/test/debugger/attach.test.ts @@ -11,7 +11,7 @@ import { DebugClient } from 'vscode-debugadapter-testsupport'; import { createDeferred } from '../../client/common/helpers'; import { BufferDecoder } from '../../client/common/process/decoder'; import { ProcessService } from '../../client/common/process/proc'; -import { AttachRequestArguments } from '../../client/debugger/Common/Contracts'; +import { AttachRequestArgumentsV1 } from '../../client/debugger/Common/Contracts'; import { PYTHON_PATH, sleep } from '../common'; import { initialize, IS_APPVEYOR, IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; import { DEBUGGER_TIMEOUT } from './common/constants'; @@ -53,7 +53,7 @@ suite('Attach Debugger', () => { } const port = await getFreePort({ host: 'localhost', port: 3000 }); - const args: AttachRequestArguments = { + const args: AttachRequestArgumentsV1 = { localRoot: path.dirname(fileToDebug), remoteRoot: path.dirname(fileToDebug), port: port, diff --git a/src/test/debugger/configProvider/provider.attach.test.ts b/src/test/debugger/configProvider/provider.attach.test.ts index 30d1a4192800..f470616ee7a5 100644 --- a/src/test/debugger/configProvider/provider.attach.test.ts +++ b/src/test/debugger/configProvider/provider.attach.test.ts @@ -3,7 +3,7 @@ 'use strict'; -// tslint:disable:max-func-body-length no-invalid-template-strings no-any no-object-literal-type-assertion +// tslint:disable:max-func-body-length no-invalid-template-strings no-any no-object-literal-type-assertion no-invalid-this import { expect } from 'chai'; import * as path from 'path'; @@ -83,9 +83,11 @@ enum OS { expect(Object.keys(debugConfig!)).to.have.lengthOf.above(3); expect(debugConfig).to.have.property('request', 'attach'); - expect(debugConfig).to.have.property('localRoot'); - expect(debugConfig!.localRoot!.toLowerCase()).to.be.equal(__dirname.toLowerCase()); expect(debugConfig).to.have.property('debugOptions').deep.equal(debugOptionsAvailable); + if (provider.debugType === 'python') { + expect(debugConfig).to.have.property('localRoot'); + expect(debugConfig!.localRoot!.toLowerCase()).to.be.equal(__dirname.toLowerCase()); + } }); test('Defaults should be returned when an empty object is passed without Workspace Folder, no workspaces and active file', async () => { const pythonFile = 'xyz.py'; @@ -98,10 +100,12 @@ enum OS { expect(Object.keys(debugConfig!)).to.have.lengthOf.least(3); expect(debugConfig).to.have.property('request', 'attach'); - expect(debugConfig).to.have.property('localRoot'); - expect(debugConfig).to.have.property('host', 'localhost'); - expect(debugConfig!.localRoot!.toLowerCase()).to.be.equal(filePath.toLowerCase()); expect(debugConfig).to.have.property('debugOptions').deep.equal(debugOptionsAvailable); + expect(debugConfig).to.have.property('host', 'localhost'); + if (provider.debugType === 'python') { + expect(debugConfig).to.have.property('localRoot'); + expect(debugConfig!.localRoot!.toLowerCase()).to.be.equal(filePath.toLowerCase()); + } }); test('Defaults should be returned when an empty object is passed without Workspace Folder, no workspaces and no active file', async () => { setupActiveEditor(undefined, PythonLanguage.language); @@ -111,9 +115,11 @@ enum OS { expect(Object.keys(debugConfig!)).to.have.lengthOf.least(3); expect(debugConfig).to.have.property('request', 'attach'); - expect(debugConfig).to.not.have.property('localRoot'); - expect(debugConfig).to.have.property('host', 'localhost'); expect(debugConfig).to.have.property('debugOptions').deep.equal(debugOptionsAvailable); + expect(debugConfig).to.have.property('host', 'localhost'); + if (provider.debugType === 'python') { + expect(debugConfig).to.not.have.property('localRoot'); + } }); test('Defaults should be returned when an empty object is passed without Workspace Folder, no workspaces and non python file', async () => { const activeFile = 'xyz.js'; @@ -125,9 +131,9 @@ enum OS { expect(Object.keys(debugConfig!)).to.have.lengthOf.least(3); expect(debugConfig).to.have.property('request', 'attach'); + expect(debugConfig).to.have.property('debugOptions').deep.equal(debugOptionsAvailable); expect(debugConfig).to.not.have.property('localRoot'); expect(debugConfig).to.have.property('host', 'localhost'); - expect(debugConfig).to.have.property('debugOptions').deep.equal(debugOptionsAvailable); }); test('Defaults should be returned when an empty object is passed without Workspace Folder, with a workspace and an active python file', async () => { const activeFile = 'xyz.py'; @@ -140,10 +146,12 @@ enum OS { expect(Object.keys(debugConfig!)).to.have.lengthOf.least(3); expect(debugConfig).to.have.property('request', 'attach'); - expect(debugConfig).to.have.property('localRoot'); - expect(debugConfig).to.have.property('host', 'localhost'); - expect(debugConfig!.localRoot!.toLowerCase()).to.be.equal(filePath.toLowerCase()); expect(debugConfig).to.have.property('debugOptions').deep.equal(debugOptionsAvailable); + expect(debugConfig).to.have.property('host', 'localhost'); + if (provider.debugType === 'python') { + expect(debugConfig).to.have.property('localRoot'); + expect(debugConfig!.localRoot!.toLowerCase()).to.be.equal(filePath.toLowerCase()); + } }); test('Ensure \'localRoot\' is left unaltered', async () => { const activeFile = 'xyz.py'; @@ -156,6 +164,43 @@ enum OS { const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { localRoot, request: 'attach' } as any as DebugConfiguration); expect(debugConfig).to.have.property('localRoot', localRoot); + if (provider.debugType === 'pythonExperimental') { + expect(debugConfig!.pathMappings).to.be.lengthOf(0); + } + }); + test('Ensure \'localRoot\' and \'remoteRoot\' is used', async function () { + if (provider.debugType !== 'pythonExperimental') { + return this.skip(); + } + const activeFile = 'xyz.py'; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + setupActiveEditor(activeFile, PythonLanguage.language); + const defaultWorkspace = path.join('usr', 'desktop'); + setupWorkspaces([defaultWorkspace]); + + const localRoot = `Debug_PythonPath_Local_Root_${new Date().toString()}`; + const remoteRoot = `Debug_PythonPath_Remote_Root_${new Date().toString()}`; + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { localRoot, remoteRoot, request: 'attach' } as any as DebugConfiguration); + + expect(debugConfig!.pathMappings).to.be.lengthOf(1); + expect(debugConfig!.pathMappings).to.deep.include({ localRoot, remoteRoot }); + }); + test('Ensure \'localRoot\' and \'remoteRoot\' is used', async function () { + if (provider.debugType !== 'pythonExperimental') { + return this.skip(); + } + const activeFile = 'xyz.py'; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + setupActiveEditor(activeFile, PythonLanguage.language); + const defaultWorkspace = path.join('usr', 'desktop'); + setupWorkspaces([defaultWorkspace]); + + const localRoot = `Debug_PythonPath_Local_Root_${new Date().toString()}`; + const remoteRoot = `Debug_PythonPath_Remote_Root_${new Date().toString()}`; + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { localRoot, remoteRoot, request: 'attach' } as any as DebugConfiguration); + + expect(debugConfig!.pathMappings).to.be.lengthOf(1); + expect(debugConfig!.pathMappings).to.deep.include({ localRoot, remoteRoot }); }); test('Ensure \'remoteRoot\' is left unaltered', async () => { const activeFile = 'xyz.py'; @@ -189,9 +234,10 @@ enum OS { setupWorkspaces([defaultWorkspace]); const debugOptions = debugOptionsAvailable.slice().concat(DebugOptions.Jinja, DebugOptions.Sudo); + const expectedDebugOptions = debugOptions.slice(); const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { debugOptions, request: 'attach' } as any as DebugConfiguration); - expect(debugConfig).to.have.property('debugOptions').to.be.deep.equal(debugOptions); + expect(debugConfig).to.have.property('debugOptions').to.be.deep.equal(expectedDebugOptions); }); }); }); From 5054ccd18f39c357ec24316294e4de04a7a38c1f Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 18 Apr 2018 12:43:06 -0700 Subject: [PATCH 130/433] Add news entry to document the support for remote debugging Fixes #907 --- news/1 Enhancements/907.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 news/1 Enhancements/907.md diff --git a/news/1 Enhancements/907.md b/news/1 Enhancements/907.md new file mode 100644 index 000000000000..378afbeb1c0c --- /dev/null +++ b/news/1 Enhancements/907.md @@ -0,0 +1,10 @@ +Add prelimnary support for remote debugging using the experimental debugger. +Attach to a Python program after having imported `ptvsd` and enabling the debugger to attach as follows: +```python +import ptvsd +ptvsd.enable_attach(('0.0.0.0', 5678)) +``` +Additional capabilities: +* `ptvsd.break_into_debugger()` to break into the attached debugger. +* `ptvsd.wait_for_attach(timeout)` to cause the program to wait untill a debugger attaches. +* `ptvsd.is_attached()` to determine whether a debugger is attached to the program. From 114a4b3d31adf61c22a4cbc7fec566655378df76 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 18 Apr 2018 14:57:53 -0700 Subject: [PATCH 131/433] Capture and display errors returned by pipenv command (#1430) Fixes #1254 Fixes #1428 --- news/2 Fixes/1254.md | 1 + news/3 Code Health/1428.md | 1 + .../locators/services/pipEnvService.ts | 19 ++++++++++--- src/test/interpreters/pipEnvService.test.ts | 28 +++++++++++++++++-- 4 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 news/2 Fixes/1254.md create mode 100644 news/3 Code Health/1428.md diff --git a/news/2 Fixes/1254.md b/news/2 Fixes/1254.md new file mode 100644 index 000000000000..06f1f135aa71 --- /dev/null +++ b/news/2 Fixes/1254.md @@ -0,0 +1 @@ +Dislay errors returned by the PipEnv command when identifying the corresonding environment. diff --git a/news/3 Code Health/1428.md b/news/3 Code Health/1428.md new file mode 100644 index 000000000000..0a8db4aa2ee7 --- /dev/null +++ b/news/3 Code Health/1428.md @@ -0,0 +1 @@ +Ensure custom environment variables defined in `.env` file are passed onto the `pipenv` command. diff --git a/src/client/interpreter/locators/services/pipEnvService.ts b/src/client/interpreter/locators/services/pipEnvService.ts index c4914bace250..935e075308e8 100644 --- a/src/client/interpreter/locators/services/pipEnvService.ts +++ b/src/client/interpreter/locators/services/pipEnvService.ts @@ -8,6 +8,7 @@ import { IApplicationShell, IWorkspaceService } from '../../../common/applicatio import { IFileSystem } from '../../../common/platform/types'; import { IProcessService } from '../../../common/process/types'; import { ICurrentProcess } from '../../../common/types'; +import { IEnvironmentVariablesProvider } from '../../../common/variables/types'; import { getPythonExecutable } from '../../../debugger/Common/Utils'; import { IServiceContainer } from '../../../ioc/types'; import { IInterpreterVersionService, InterpreterType, PythonInterpreter } from '../../contracts'; @@ -22,6 +23,7 @@ export class PipEnvService extends CacheableLocatorService { private readonly process: IProcessService; private readonly workspace: IWorkspaceService; private readonly fs: IFileSystem; + private readonly envVarsProvider: IEnvironmentVariablesProvider; constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { super('PipEnvService', serviceContainer); @@ -29,6 +31,7 @@ export class PipEnvService extends CacheableLocatorService { this.process = this.serviceContainer.get(IProcessService); this.workspace = this.serviceContainer.get(IWorkspaceService); this.fs = this.serviceContainer.get(IFileSystem); + this.envVarsProvider = this.serviceContainer.get(IEnvironmentVariablesProvider); } // tslint:disable-next-line:no-empty public dispose() { } @@ -91,14 +94,22 @@ export class PipEnvService extends CacheableLocatorService { private async invokePipenv(arg: string, rootPath: string): Promise { try { - const result = await this.process.exec(execName, [arg], { cwd: rootPath }); - if (result && result.stdout) { - return result.stdout.trim(); + const env = await this.envVarsProvider.getEnvironmentVariables(Uri.file(rootPath)); + const result = await this.process.exec(execName, [arg], { cwd: rootPath, env }); + if (result) { + const stdout = result.stdout ? result.stdout.trim() : ''; + const stderr = result.stderr ? result.stderr.trim() : ''; + if (stderr.length > 0 && stdout.length === 0) { + throw new Error(stderr); + } + return stdout; } // tslint:disable-next-line:no-empty } catch (error) { + console.error(error); + const errorMessage = error.message || error; const appShell = this.serviceContainer.get(IApplicationShell); - appShell.showWarningMessage(`Workspace contains pipfile but attempt to run 'pipenv --venv' failed with ${error}. Make sure pipenv is on the PATH.`); + appShell.showWarningMessage(`Workspace contains pipfile but attempt to run 'pipenv --venv' failed with ${errorMessage}. Make sure pipenv is on the PATH.`); } } } diff --git a/src/test/interpreters/pipEnvService.test.ts b/src/test/interpreters/pipEnvService.test.ts index 5fe4cc4f5324..65545cfc7b53 100644 --- a/src/test/interpreters/pipEnvService.test.ts +++ b/src/test/interpreters/pipEnvService.test.ts @@ -3,6 +3,8 @@ 'use strict'; +// tslint:disable:max-func-body-length no-any + import { expect } from 'chai'; import * as path from 'path'; import * as TypeMoq from 'typemoq'; @@ -12,6 +14,7 @@ import { EnumEx } from '../../client/common/enumUtils'; import { IFileSystem } from '../../client/common/platform/types'; import { IProcessService } from '../../client/common/process/types'; import { ICurrentProcess, IPersistentState, IPersistentStateFactory } from '../../client/common/types'; +import { IEnvironmentVariablesProvider } from '../../client/common/variables/types'; import { IInterpreterLocatorService, IInterpreterVersionService } from '../../client/interpreter/contracts'; import { PipEnvService } from '../../client/interpreter/locators/services/pipEnvService'; import { IServiceContainer } from '../../client/ioc/types'; @@ -20,7 +23,6 @@ enum OS { Mac, Windows, Linux } -// tslint:disable-next-line:max-func-body-length suite('Interpreters - PipEnv', () => { const rootWorkspace = Uri.file(path.join('usr', 'desktop', 'wkspc1')).fsPath; EnumEx.getNamesAndValues(OS).forEach(os => { @@ -35,6 +37,7 @@ suite('Interpreters - PipEnv', () => { let fileSystem: TypeMoq.IMock; let appShell: TypeMoq.IMock; let persistentStateFactory: TypeMoq.IMock; + let envVarsProvider: TypeMoq.IMock; setup(() => { serviceContainer = TypeMoq.Mock.ofType(); const workspaceService = TypeMoq.Mock.ofType(); @@ -44,6 +47,7 @@ suite('Interpreters - PipEnv', () => { appShell = TypeMoq.Mock.ofType(); currentProcess = TypeMoq.Mock.ofType(); persistentStateFactory = TypeMoq.Mock.ofType(); + envVarsProvider = TypeMoq.Mock.ofType(); // tslint:disable-next-line:no-any const persistentState = TypeMoq.Mock.ofType>(); @@ -64,6 +68,7 @@ suite('Interpreters - PipEnv', () => { serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IFileSystem))).returns(() => fileSystem.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IApplicationShell))).returns(() => appShell.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPersistentStateFactory))).returns(() => persistentStateFactory.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IEnvironmentVariablesProvider))).returns(() => envVarsProvider.object); pipEnvService = new PipEnvService(serviceContainer.object); }); @@ -74,6 +79,7 @@ suite('Interpreters - PipEnv', () => { }); test(`Should return an empty list if there is a \'PipFile\'${testSuffix}`, async () => { const env = {}; + envVarsProvider.setup(e => e.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})).verifiable(TypeMoq.Times.once()); currentProcess.setup(c => c.env).returns(() => env); fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(false)).verifiable(TypeMoq.Times.once()); const environments = await pipEnvService.getInterpreters(resource); @@ -81,7 +87,7 @@ suite('Interpreters - PipEnv', () => { expect(environments).to.be.deep.equal([]); fileSystem.verifyAll(); }); - test(`Should display wanring message if there is a \'PipFile\' but \'pipenv --venv\' failes ${testSuffix}`, async () => { + test(`Should display warning message if there is a \'PipFile\' but \'pipenv --venv\' failes ${testSuffix}`, async () => { const env = {}; currentProcess.setup(c => c.env).returns(() => env); processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.reject('')); @@ -91,10 +97,25 @@ suite('Interpreters - PipEnv', () => { expect(environments).to.be.deep.equal([]); appShell.verifyAll(); + appShell.verifyAll(); + }); + test(`Should display warning message if there is a \'PipFile\' but \'pipenv --venv\' failes with stderr ${testSuffix}`, async () => { + const env = {}; + envVarsProvider.setup(e => e.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})).verifiable(TypeMoq.Times.once()); + currentProcess.setup(c => c.env).returns(() => env); + processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stderr: 'PipEnv Failed', stdout: '' })); + fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(true)); + appShell.setup(a => a.showWarningMessage(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('')).verifiable(TypeMoq.Times.once()); + const environments = await pipEnvService.getInterpreters(resource); + + expect(environments).to.be.deep.equal([]); + envVarsProvider.verifyAll(); + appShell.verifyAll(); }); test(`Should return interpreter information${testSuffix}`, async () => { const env = {}; const venvDir = 'one'; + envVarsProvider.setup(e => e.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})).verifiable(TypeMoq.Times.once()); currentProcess.setup(c => c.env).returns(() => env); processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: venvDir })); interpreterVersionService.setup(v => v.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('xyz')); @@ -104,6 +125,7 @@ suite('Interpreters - PipEnv', () => { expect(environments).to.be.lengthOf(1); fileSystem.verifyAll(); + envVarsProvider.verifyAll(); }); test(`Should return interpreter information using PipFile defined in Env variable${testSuffix}`, async () => { const envPipFile = 'XYZ'; @@ -111,6 +133,7 @@ suite('Interpreters - PipEnv', () => { PIPENV_PIPFILE: envPipFile }; const venvDir = 'one'; + envVarsProvider.setup(e => e.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})).verifiable(TypeMoq.Times.once()); currentProcess.setup(c => c.env).returns(() => env); processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: venvDir })); interpreterVersionService.setup(v => v.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('xyz')); @@ -121,6 +144,7 @@ suite('Interpreters - PipEnv', () => { expect(environments).to.be.lengthOf(1); fileSystem.verifyAll(); + envVarsProvider.verifyAll(); }); }); }); From 958e5c1faa8bc40f428a31b129b097384b6be27d Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Wed, 18 Apr 2018 17:03:44 -0700 Subject: [PATCH 132/433] Add news entry for 1072 --- news/2 Fixes/1072.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/2 Fixes/1072.md diff --git a/news/2 Fixes/1072.md b/news/2 Fixes/1072.md new file mode 100644 index 000000000000..75e3c52bdc0c --- /dev/null +++ b/news/2 Fixes/1072.md @@ -0,0 +1 @@ +IntelliSense under Python 2 for inherited attributes works again (thanks to an upgraded Jedi). From a656bce4e0efdca8a5ddfc1a1a33ab7e93118c42 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Wed, 18 Apr 2018 17:05:29 -0700 Subject: [PATCH 133/433] News entry for #344 --- news/2 Fixes/344.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/2 Fixes/344.md diff --git a/news/2 Fixes/344.md b/news/2 Fixes/344.md new file mode 100644 index 000000000000..0a1d459ac2d6 --- /dev/null +++ b/news/2 Fixes/344.md @@ -0,0 +1 @@ +Parameter hints following an f-string work again. From 3cc14de4907ec32112f99f703bf65501918dfe22 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 18 Apr 2018 17:22:52 -0700 Subject: [PATCH 134/433] Document changes to launch.json in the experimental debugger (#1435) Fixes #1434 --- news/1 Enhancements/1395.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/1 Enhancements/1395.md diff --git a/news/1 Enhancements/1395.md b/news/1 Enhancements/1395.md new file mode 100644 index 000000000000..b572085061eb --- /dev/null +++ b/news/1 Enhancements/1395.md @@ -0,0 +1 @@ +Settings configured within the `debugOptions` property of `launch.json` for the old debugger are now defined as individual (boolean) properties in the new experimental debugger (e.g. `"debugOptions": ["RedirectOutput"]` becomes `"redirectOutput": true`). From a465bfb19f579f56d0498b826ed340f5be4f8fd3 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 19 Apr 2018 11:51:15 -0700 Subject: [PATCH 135/433] News entry for #338 Closes #338 --- news/2 Fixes/338.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/2 Fixes/338.md diff --git a/news/2 Fixes/338.md b/news/2 Fixes/338.md new file mode 100644 index 000000000000..8c4553127ac2 --- /dev/null +++ b/news/2 Fixes/338.md @@ -0,0 +1 @@ +Provide type details appropriate for the iterable in a `for` loop when the line has a `# type` comment. From 8598db44f8991c30ca6e92603c0485b03c48e85f Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 19 Apr 2018 12:08:29 -0700 Subject: [PATCH 136/433] Multiple files with same name now work with IntelliSense Closes #178 --- news/2 Fixes/178.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/2 Fixes/178.md diff --git a/news/2 Fixes/178.md b/news/2 Fixes/178.md new file mode 100644 index 000000000000..78002bbeb749 --- /dev/null +++ b/news/2 Fixes/178.md @@ -0,0 +1 @@ +IntelliSense work appropriately when a project contains multiple files with the same name (thanks to Jedi 0.12.0 update). From 02ee69b49d15f5f5395da7df012df8c23d535ba7 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 19 Apr 2018 12:08:56 -0700 Subject: [PATCH 137/433] Fix spelling mistake --- news/2 Fixes/178.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/news/2 Fixes/178.md b/news/2 Fixes/178.md index 78002bbeb749..0d7105e2dc8c 100644 --- a/news/2 Fixes/178.md +++ b/news/2 Fixes/178.md @@ -1 +1 @@ -IntelliSense work appropriately when a project contains multiple files with the same name (thanks to Jedi 0.12.0 update). +IntelliSense works appropriately when a project contains multiple files with the same name (thanks to Jedi 0.12.0 update). From bc6d3d5eec1a1388fe279ad29af63664f6a12a3d Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 19 Apr 2018 12:23:42 -0700 Subject: [PATCH 138/433] Intellisense in module-level conditionals now works --- news/2 Fixes/142.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/2 Fixes/142.md diff --git a/news/2 Fixes/142.md b/news/2 Fixes/142.md new file mode 100644 index 000000000000..ab8918fcdfff --- /dev/null +++ b/news/2 Fixes/142.md @@ -0,0 +1 @@ +IntelliSense works in module-level `if` statements (thanks to Jedi 0.12.0 upgrade). From a2cd38350936995b2039ff1fd741a0fcd262c20d Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 19 Apr 2018 16:42:15 -0700 Subject: [PATCH 139/433] Distribution TPN updates (#1444) --- ThirdPartyNotices-Distribution.txt | 220 +++++++++++++++++++++++++---- 1 file changed, 193 insertions(+), 27 deletions(-) diff --git a/ThirdPartyNotices-Distribution.txt b/ThirdPartyNotices-Distribution.txt index 2ce55b59b5bd..a13b7f31cdf0 100644 --- a/ThirdPartyNotices-Distribution.txt +++ b/ThirdPartyNotices-Distribution.txt @@ -4,13 +4,12 @@ Do Not Translate or Localize Microsoft Python extension for Visual Studio Code incorporates components from the projects listed below. Microsoft licenses these components to you under Microsoft's licensing terms for the Microsoft Python extension for Visual Studio Code. The original copyright notices and the licenses under which Microsoft received such components are set forth below for informational purposes. Microsoft reserves all rights not expressly granted herein, whether by implication, estoppel or otherwise. - 1. Arch (https://github.com/feross/arch) 2. diff-match-patch (https://github.com/ForbesLindesay-Unmaintained/diff-match-patch) 3. Files from the Python Project (https://www.python.org/) 4. fuzzy (https://github.com/mattyork/fuzzy) 5. Get-port (https://github.com/sindresorhus/get-port) -6. Go for Visual Studio Code (https://github.com/Microsoft/vscode-go) +6. Go for Visual Studio Code (https://github.com/Microsoft/vscode-go) 7. Google Diff Match and Patch (https://github.com/GerHobbelt/google-diff-match-patch) 8. Iconv-lite (https://github.com/ashtuchkin/iconv-lite) 9. Inversify (https://github.com/inversify/InversifyJS) @@ -25,37 +24,41 @@ Microsoft Python extension for Visual Studio Code incorporates components from t 16. named-js-regexp (https://github.com/edvinv/named-js-regexp) 17. node-fs-extra (https://github.com/jprichardson/node-fs-extra) 18. node-semver (https://github.com/npm/node-semver) -19. node-tmp (https://github.com/raszi/node-tmp) -20. node-tree-kill (https://github.com/pkrumins/node-tree-kill) -21. node-winreg (https://github.com/fresc81/node-winreg) -22. node-xml2js (https://github.com/Leonidas-from-XIV/node-xml2js) -23. omnisharp-vscode (https://github.com/OmniSharp/omnisharp-vscode) -24. opn (https://github.com/sindresorhus/opn) -25. pidusage (https://github.com/soyuka/pidusage) -26. PTVS (https://github.com/Microsoft/PTVS) -27. PTVSD (https://github.com/Microsoft/PTVSD) -28. PyDev.Debugger (https://github.com/fabioz/PyDev.Debugger) +19. node-stream-zip (https://github.com/antelle/node-stream-zip) +20. node-tmp (https://github.com/raszi/node-tmp) +21. node-tree-kill (https://github.com/pkrumins/node-tree-kill) +22. node-winreg (https://github.com/fresc81/node-winreg) +23. node-xml2js (https://github.com/Leonidas-from-XIV/node-xml2js) +24. omnisharp-vscode (https://github.com/OmniSharp/omnisharp-vscode) +25. opn (https://github.com/sindresorhus/opn) +26. pidusage (https://github.com/soyuka/pidusage) +27. PTVS (https://github.com/Microsoft/PTVS) +28. PTVSD (https://github.com/Microsoft/PTVSD) +29. PyDev.Debugger (https://github.com/fabioz/PyDev.Debugger) Includes:Files copyright Yuli Fitterman Includes:IPython Includes:py2app Includes:Python (various files) -29. Python documentation (https://docs.python.org/) -30. python-functools32 (https://github.com/MiCHiLU/python-functools32/blob/master/functools32/functools32.py) -31. pythonVSCode (https://github.com/DonJayamanne/pythonVSCode) -32. Reflect-metadata (https://github.com/rbuckton/reflect-metadata) -33. RxJS (https://github.com/ReactiveX/RxJS) +30. Python documentation (https://docs.python.org/) +31. python-functools32 (https://github.com/MiCHiLU/python-functools32/blob/master/functools32/functools32.py) +32. pythonVSCode (https://github.com/DonJayamanne/pythonVSCode) +33. Reflect-metadata (https://github.com/rbuckton/reflect-metadata) +34. request (https://github.com/request/request) +35. request-progress (https://github.com/IndigoUnited/node-request-progress) +36. RxJS (https://github.com/ReactiveX/RxJS) Includes:Contributor Covenant v1.1.0, v1.4 Includes:File from Angular.io Includes:File from setImmediate -34. Sphinx (http://sphinx-doc.org/) -35. uint64be (https://github.com/mafintosh/uint64be) -36. untangle (https://github.com/stchris/untangle) -37. untildify (https://github.com/sindresorhus/untildify) -38. vscode-debugadapter (https://github.com/Microsoft/vscode-debugadapter-node/tree/master/adapter) -39. vscode-debugprotocol (https://github.com/Microsoft/vscode-debugadapter-node/tree/master/protocol) -40. vscode-extension-telemetry (https://github.com/Microsoft/vscode-extension-telemetry) -41. vscode-languageclient (https://github.com/Microsoft/vscode-languageserver-node) -42. vscode-languageserver (https://github.com/Microsoft/vscode-languageserver-node/) +37. Sphinx (http://sphinx-doc.org/) +38. sudo-prompt (https://github.com/jorangreef/sudo-prompt) +39. uint64be (https://github.com/mafintosh/uint64be) +40. untangle (https://github.com/stchris/untangle) +41. untildify (https://github.com/sindresorhus/untildify) +42. vscode-debugadapter (https://github.com/Microsoft/vscode-debugadapter-node/tree/master/adapter) +43. vscode-debugprotocol (https://github.com/Microsoft/vscode-debugadapter-node/tree/master/protocol) +44. vscode-extension-telemetry (https://github.com/Microsoft/vscode-extension-telemetry) +45. vscode-languageclient (https://github.com/Microsoft/vscode-languageserver-node) +46. vscode-languageserver (https://github.com/Microsoft/vscode-languageserver-node/) %% Arch NOTICES AND INFORMATION BEGIN HERE @@ -753,6 +756,55 @@ IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ========================================= END OF node-semver NOTICES AND INFORMATION +%% node-stream-zip NOTICES AND INFORMATION BEGIN HERE +========================================= +Copyright (c) 2015 Antelle https://github.com/antelle + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +== dependency license: adm-zip == + +Copyright (c) 2012 Another-D-Mention Software and other contributors, +http://www.another-d-mention.ro/ + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +========================================= +END OF node-stream-zip NOTICES AND INFORMATION + %% node-tmp NOTICES AND INFORMATION BEGIN HERE ========================================= The MIT License (MIT) @@ -782,7 +834,9 @@ END OF node-tmp NOTICES AND INFORMATION %% node-tree-kill NOTICES AND INFORMATION BEGIN HERE ========================================= -Copyright (c) 2014 Peteris Krumins +MIT License + +Copyright (c) 2018 Peter Krumins Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -1832,6 +1886,90 @@ and limitations under the License. ========================================= END OF Reflect-metadata NOTICES AND INFORMATION +%% request NOTICES AND INFORMATION BEGIN HERE +========================================= +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of this License; and + +You must cause any modified files to carry prominent notices stating that You changed the files; and + +You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + +If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS +========================================= +END OF request NOTICES AND INFORMATION + +%% request-progress NOTICES AND INFORMATION BEGIN HERE +========================================= +Copyright (c) 2012 IndigoUnited + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +========================================= +END OF request-progress NOTICES AND INFORMATION + %% RxJS NOTICES AND INFORMATION BEGIN HERE ========================================= Copyright (c) 2015-2017 Google, Inc., Netflix, Inc., Microsoft Corp. and contributors @@ -1937,6 +2075,34 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ========================================= END OF Sphinx NOTICES AND INFORMATION +%% sudo-prompt NOTICES AND INFORMATION BEGIN HERE +========================================= +The MIT License (MIT) + +Copyright (c) 2015 Joran Dirk Greef + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +========================================= +END OF sudo-prompt NOTICES AND INFORMATION + %% uint64be NOTICES AND INFORMATION BEGIN HERE ========================================= The MIT License (MIT) From 7bcb999d2d01b245d3aa25224793cdccac5a0d39 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 20 Apr 2018 11:13:05 -0700 Subject: [PATCH 140/433] CI against release ver of ptvsd, allow failure of master ver of ptvsd (#1408) Fixes #1253 --- .appveyor.yml | 19 +++++++++++++++++++ .travis.yml | 31 +++++++++++++++++++++++++------ gulpfile.js | 2 ++ news/3 Code Health/1253.md | 1 + package.json | 1 + 5 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 news/3 Code Health/1253.md diff --git a/.appveyor.yml b/.appveyor.yml index 8be46ea0344b..dbea6e3a5494 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -9,6 +9,12 @@ environment: nodejs_version: "8.9.1" APPVEYOR: "true" DEBUGGER_TEST: "true" + - PYTHON: "C:\\Python36" + PYTHON_VERSION: "3.6.3" + PYTHON_ARCH: "32" + nodejs_version: "8.9.1" + APPVEYOR: "true" + DEBUGGER_TEST_RELEASE: "true" - PYTHON: "C:\\Python36" PYTHON_VERSION: "3.6.3" PYTHON_ARCH: "32" @@ -28,6 +34,15 @@ environment: APPVEYOR: "true" ANALYSIS_TEST: "true" +matrix: + allow_failures: + - PYTHON: "C:\\Python36" + PYTHON_VERSION: "3.6.3" + PYTHON_ARCH: "32" + nodejs_version: "8.9.1" + APPVEYOR: "true" + DEBUGGER_TEST_RELEASE: "true" + init: - "ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH%" @@ -58,6 +73,10 @@ test_script: - yarn run vscode:prepublish - if [%DEBUGGER_TEST%]==[true] ( yarn run testDebugger --silent) + - yarn run clean:ptvsd + - pip install -t ./pythonFiles/experimental/ptvsd ptvsd --pre --no-cache-dir + - if [%DEBUGGER_TEST_RELEASE%]==[true] ( + yarn run testDebugger --silent) - if [%SINGLE_WORKSPACE_TEST%]==[true] ( yarn run testSingleWorkspace --silent) - if [%MULTIROOT_WORKSPACE_TEST%]==[true] ( diff --git a/.travis.yml b/.travis.yml index 99ec74c36292..52ae4e0b99f9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,9 @@ matrix: - os: linux python: "2.7" env: DEBUGGER_TEST=true + - os: linux + python: "2.7" + env: DEBUGGER_TEST_RELEASE=true - os: linux python: "2.7" env: SINGLE_WORKSPACE_TEST=true @@ -15,12 +18,22 @@ matrix: - os: linux python: "3.6-dev" env: DEBUGGER_TEST=true + - os: linux + python: "3.6-dev" + env: DEBUGGER_TEST_RELEASE=true - os: linux python: "3.6-dev" env: SINGLE_WORKSPACE_TEST=true - os: linux python: "3.6-dev" env: MULTIROOT_WORKSPACE_TEST=true + allow_failures: + - os: linux + python: "2.7" + env: DEBUGGER_TEST=true + - os: linux + python: "3.6-dev" + env: DEBUGGER_TEST=true before_install: | if [ $TRAVIS_OS_NAME == "linux" ]; then export CXX="g++-4.9" CC="gcc-4.9" DISPLAY=:99.0; @@ -51,12 +64,18 @@ script: - if [ $TRAVIS_UPLOAD_COVERAGE == "true" ]; then bash <(curl -s https://codecov.io/bash); fi - # - rm -rf ./pythonFiles/experimental/ptvsd - # - pip install -t ./pythonFiles/experimental/ptvsd ptvsd - # - yarn run testDebugger --silent - # - if [ $TRAVIS_UPLOAD_COVERAGE == "true" ]; then - # bash <(curl -s https://codecov.io/bash); - # fi + - yarn run clean:ptvsd + - pip install -t ./pythonFiles/experimental/ptvsd ptvsd --pre --no-cache-dir; + - if [ $DEBUGGER_TEST_RELEASE == "true" ]; then + yarn run clean; + yarn run vscode:prepublish; + yarn run cover:enable; + yarn run testDebugger --silent; + fi + - yarn run debugger-coverage + - if [ $TRAVIS_UPLOAD_COVERAGE == "true" ]; then + bash <(curl -s https://codecov.io/bash); + fi - if [ $SINGLE_WORKSPACE_TEST == "true" ]; then yarn run clean; yarn run vscode:prepublish; diff --git a/gulpfile.js b/gulpfile.js index ba89b0a92e2f..753af8c46292 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -95,6 +95,8 @@ gulp.task('output:clean', () => del(['coverage', 'debug_coverage*'])); gulp.task('cover:clean', () => del(['coverage', 'debug_coverage*'])); +gulp.task('clean:ptvsd', () => del(['coverage', 'pythonFiles/experimental/ptvsd*'])); + gulp.task('cover:enable', () => { return gulp.src("./coverconfig.json") .pipe(jeditor((json) => { diff --git a/news/3 Code Health/1253.md b/news/3 Code Health/1253.md new file mode 100644 index 000000000000..5dcc6d79bfec --- /dev/null +++ b/news/3 Code Health/1253.md @@ -0,0 +1 @@ +Run CI tests against the release version and master branch of PTVSD (experimental debugger), allowing tests to fail against the mastre branch of PTVSD. diff --git a/package.json b/package.json index 906ad9e94b14..546445b45674 100644 --- a/package.json +++ b/package.json @@ -1849,6 +1849,7 @@ "lint-staged": "node gulpfile.js", "lint": "tslint src/**/*.ts -t verbose", "clean": "gulp clean", + "clean:ptvsd": "gulp clean:ptvsd", "cover:enable": "gulp cover:enable", "debugger-coverage": "gulp debugger-coverage" }, From f07883997c728e3e412cd6f68d90daa4a8c4cc92 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 20 Apr 2018 11:26:24 -0700 Subject: [PATCH 141/433] Hide Jedi flag (#1443) Fixes #1439 --- package.json | 6 ------ src/client/common/configSettings.ts | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/package.json b/package.json index 546445b45674..a763aab7fc57 100644 --- a/package.json +++ b/package.json @@ -1163,12 +1163,6 @@ "default": "${workspaceFolder}/.env", "scope": "resource" }, - "python.jediEnabled": { - "type": "boolean", - "default": true, - "description": "Enables Jedi as IntelliSense engine instead of Microsoft Python Analysis Engine.", - "scope": "resource" - }, "python.jediPath": { "type": "string", "default": "", diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index 62d75c0ec0ba..49b8c53332fc 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -115,7 +115,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { this.venvPath = systemVariables.resolveAny(pythonSettings.get('venvPath'))!; this.venvFolders = systemVariables.resolveAny(pythonSettings.get('venvFolders'))!; - this.jediEnabled = systemVariables.resolveAny(pythonSettings.get('jediEnabled'))!; + this.jediEnabled = systemVariables.resolveAny(pythonSettings.get('jediEnabled', true))!; if (this.jediEnabled) { // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion this.jediPath = systemVariables.resolveAny(pythonSettings.get('jediPath'))!; From d439a7b8a6b782a197281a1416b07a6aa1aa1feb Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 20 Apr 2018 16:01:34 -0700 Subject: [PATCH 142/433] Set focus to the terminal upon creation of a terminal using the Create Terminal command (#1433) Fixes #1315 --- news/1 Enhancements/1315.md | 1 + src/client/common/terminal/service.ts | 16 ++++++++-------- src/client/common/terminal/types.ts | 3 +-- src/client/providers/terminalProvider.ts | 2 +- src/test/common/terminals/service.test.ts | 12 ++++++++++++ src/test/providers/terminal.test.ts | 8 ++++---- 6 files changed, 27 insertions(+), 15 deletions(-) create mode 100644 news/1 Enhancements/1315.md diff --git a/news/1 Enhancements/1315.md b/news/1 Enhancements/1315.md new file mode 100644 index 000000000000..ff67ed87e629 --- /dev/null +++ b/news/1 Enhancements/1315.md @@ -0,0 +1 @@ +Set focus to the terminal upon creation of a terminal using the `Python: Create Terminal` command. diff --git a/src/client/common/terminal/service.ts b/src/client/common/terminal/service.ts index 5ec43970a75a..7e54a853b1dc 100644 --- a/src/client/common/terminal/service.ts +++ b/src/client/common/terminal/service.ts @@ -11,14 +11,14 @@ import { ITerminalHelper, ITerminalService, TerminalShellType } from './types'; @injectable() export class TerminalService implements ITerminalService, Disposable { private terminal?: Terminal; - private terminalShellType: TerminalShellType; + private terminalShellType!: TerminalShellType; private terminalClosed = new EventEmitter(); private terminalManager: ITerminalManager; private terminalHelper: ITerminalHelper; public get onDidCloseTerminal(): Event { return this.terminalClosed.event; } - constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer, + constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer, private resource?: Uri, private title: string = 'Python') { @@ -44,11 +44,11 @@ export class TerminalService implements ITerminalService, Disposable { this.terminal!.show(true); this.terminal!.sendText(text); } - public async show(): Promise { - await this.ensureTerminal(); - this.terminal!.show(true); + public async show(preserveFocus: boolean = true): Promise { + await this.ensureTerminal(preserveFocus); + this.terminal!.show(preserveFocus); } - private async ensureTerminal(): Promise { + private async ensureTerminal(preserveFocus: boolean = true): Promise { if (this.terminal) { return; } @@ -62,7 +62,7 @@ export class TerminalService implements ITerminalService, Disposable { const activationCommamnds = await this.terminalHelper.getEnvironmentActivationCommands(this.terminalShellType, this.resource); if (activationCommamnds) { for (const command of activationCommamnds!) { - this.terminal!.show(true); + this.terminal!.show(preserveFocus); this.terminal!.sendText(command); // Give the command some time to complete. @@ -71,7 +71,7 @@ export class TerminalService implements ITerminalService, Disposable { } } - this.terminal!.show(true); + this.terminal!.show(preserveFocus); } private terminalCloseHandler(terminal: Terminal) { if (terminal === this.terminal) { diff --git a/src/client/common/terminal/types.ts b/src/client/common/terminal/types.ts index 5a2914fd2ab0..56cdb305c077 100644 --- a/src/client/common/terminal/types.ts +++ b/src/client/common/terminal/types.ts @@ -3,7 +3,6 @@ // Licensed under the MIT License. import { Event, Terminal, Uri } from 'vscode'; -import { PythonInterpreter } from '../../interpreter/contracts'; export enum TerminalShellType { powershell = 1, @@ -19,7 +18,7 @@ export interface ITerminalService { readonly onDidCloseTerminal: Event; sendCommand(command: string, args: string[]): Promise; sendText(text: string): Promise; - show(): Promise; + show(preserveFocus?: boolean): Promise; } export const ITerminalServiceFactory = Symbol('ITerminalServiceFactory'); diff --git a/src/client/providers/terminalProvider.ts b/src/client/providers/terminalProvider.ts index 7bfb5bb4d08f..2d84986ebb07 100644 --- a/src/client/providers/terminalProvider.ts +++ b/src/client/providers/terminalProvider.ts @@ -24,7 +24,7 @@ export class TerminalProvider implements Disposable { private async onCreateTerminal() { const terminalService = this.serviceContainer.get(ITerminalServiceFactory); const activeResource = this.getActiveResource(); - await terminalService.createTerminalService(activeResource, 'Python').show(); + await terminalService.createTerminalService(activeResource, 'Python').show(false); } private getActiveResource(): Uri | undefined { const documentManager = this.serviceContainer.get(IDocumentManager); diff --git a/src/test/common/terminals/service.test.ts b/src/test/common/terminals/service.test.ts index f18b3738b7af..1f71218b8094 100644 --- a/src/test/common/terminals/service.test.ts +++ b/src/test/common/terminals/service.test.ts @@ -114,6 +114,18 @@ suite('Terminal Service', () => { terminal.verify(t => t.show(TypeMoq.It.isValue(true)), TypeMoq.Times.exactly(2)); }); + test('Ensure terminal shown and focus is set to the Terminal', async () => { + terminalHelper.setup(helper => helper.getEnvironmentActivationCommands(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); + service = new TerminalService(mockServiceContainer.object); + terminalHelper.setup(h => h.getTerminalShellPath()).returns(() => ''); + terminalHelper.setup(h => h.identifyTerminalShell(TypeMoq.It.isAny())).returns(() => TerminalShellType.bash); + terminalManager.setup(t => t.createTerminal(TypeMoq.It.isAny())).returns(() => terminal.object); + + await service.show(false); + + terminal.verify(t => t.show(TypeMoq.It.isValue(false)), TypeMoq.Times.exactly(2)); + }); + test('Ensure terminal is activated once after creation', async () => { service = new TerminalService(mockServiceContainer.object); terminalHelper.setup(h => h.getTerminalShellPath()).returns(() => ''); diff --git a/src/test/providers/terminal.test.ts b/src/test/providers/terminal.test.ts index 7b9097120d7e..6573ef9d231a 100644 --- a/src/test/providers/terminal.test.ts +++ b/src/test/providers/terminal.test.ts @@ -68,7 +68,7 @@ suite('Terminal Provider', () => { terminalServiceFactory.setup(t => t.createTerminalService(TypeMoq.It.isValue(undefined), TypeMoq.It.isValue('Python'))).returns(() => terminalService.object); commandHandler!.call(terminalProvider); - terminalService.verify(t => t.show(), TypeMoq.Times.once()); + terminalService.verify(t => t.show(false), TypeMoq.Times.once()); }); test('Ensure terminal creation does not use uri of the active documents which is untitled', () => { @@ -94,7 +94,7 @@ suite('Terminal Provider', () => { terminalServiceFactory.setup(t => t.createTerminalService(TypeMoq.It.isValue(undefined), TypeMoq.It.isValue('Python'))).returns(() => terminalService.object); commandHandler!.call(terminalProvider); - terminalService.verify(t => t.show(), TypeMoq.Times.once()); + terminalService.verify(t => t.show(false), TypeMoq.Times.once()); }); test('Ensure terminal creation uses uri of active document', () => { @@ -122,7 +122,7 @@ suite('Terminal Provider', () => { terminalServiceFactory.setup(t => t.createTerminalService(TypeMoq.It.isValue(documentUri), TypeMoq.It.isValue('Python'))).returns(() => terminalService.object); commandHandler!.call(terminalProvider); - terminalService.verify(t => t.show(), TypeMoq.Times.once()); + terminalService.verify(t => t.show(false), TypeMoq.Times.once()); }); test('Ensure terminal creation uses uri of active workspace', () => { @@ -147,6 +147,6 @@ suite('Terminal Provider', () => { terminalServiceFactory.setup(t => t.createTerminalService(TypeMoq.It.isValue(workspaceUri), TypeMoq.It.isValue('Python'))).returns(() => terminalService.object); commandHandler!.call(terminalProvider); - terminalService.verify(t => t.show(), TypeMoq.Times.once()); + terminalService.verify(t => t.show(false), TypeMoq.Times.once()); }); }); From d4b7de7599102a2451972c8827026f0a172e8820 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Mon, 23 Apr 2018 10:33:01 -0700 Subject: [PATCH 143/433] Fixes multiple issues with formatting on type (#1450) * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * #1096 The if statement is automatically formatted incorrectly * Merge fix * Add more tests * More tests * Typo * Test * Also better handle multiline arguments * Add a couple missing periods [skip ci] * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * Merge fix * #1257 On type formatting errors for args and kwargs * Handle f-strings * Stop importing from test code * #1308 Single line statements leading to an indentation on the next line * #726 editing python after inline if statement invalid indent * Undo change * Move constant * Harden LS startup error checks * #1364 Intellisense doesn't work after specific const string * Telemetry for the analysis enging * PR feedback * Fix typo * Test baseline update * Jedi 0.12 * Priority to goto_defition * News * Replace unzip * Linux flavors + test * Grammar check * Grammar test * Test baselines * Pin dependency [skip ci] --- package.json | 6 + src/client/activation/analysis.ts | 2 +- src/client/activation/analysisEngineHashes.ts | 15 +- src/client/activation/downloader.ts | 8 +- src/client/activation/platformData.ts | 67 +- src/client/formatters/lineFormatter.ts | 132 +- src/client/language/tokenizer.ts | 139 +- src/test/activation/platformData.test.ts | 84 + src/test/definitions/hover.ptvs.test.ts | 32 +- .../format/extension.lineFormatter.test.ts | 52 + src/test/language/tokenizer.test.ts | 129 +- .../pythonFiles/formatting/pythonGrammar.py | 1572 +++++++++++++++++ src/test/signature/signature.ptvs.test.ts | 3 +- 13 files changed, 2089 insertions(+), 152 deletions(-) create mode 100644 src/test/activation/platformData.test.ts create mode 100644 src/test/pythonFiles/formatting/pythonGrammar.py diff --git a/package.json b/package.json index a763aab7fc57..0b518adeb974 100644 --- a/package.json +++ b/package.json @@ -1581,6 +1581,12 @@ "description": "Automatically add brackets for functions.", "scope": "resource" }, + "python.autoComplete.showAdvancedMembers": { + "type": "boolean", + "default": false, + "description": "Controls appearance of methods with double underscores in the completion list.", + "scope": "resource" + }, "python.workspaceSymbols.tagFilePath": { "type": "string", "default": "${workspaceFolder}/.vscode/tags", diff --git a/src/client/activation/analysis.ts b/src/client/activation/analysis.ts index 7ed94f893a8f..d2e853dc7dda 100644 --- a/src/client/activation/analysis.ts +++ b/src/client/activation/analysis.ts @@ -57,7 +57,7 @@ export class AnalysisExtensionActivator implements IExtensionActivator { this.appShell = this.services.get(IApplicationShell); this.output = this.services.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); this.fs = this.services.get(IFileSystem); - this.platformData = new PlatformData(services.get(IPlatformService)); + this.platformData = new PlatformData(services.get(IPlatformService), this.fs); } public async activate(context: ExtensionContext): Promise { diff --git a/src/client/activation/analysisEngineHashes.ts b/src/client/activation/analysisEngineHashes.ts index 52761329113e..2f9123a46c59 100644 --- a/src/client/activation/analysisEngineHashes.ts +++ b/src/client/activation/analysisEngineHashes.ts @@ -3,7 +3,14 @@ // This file will be replaced by a generated one during the release build // with actual hashes of the uploaded packages. -export const analysis_engine_win_x86_sha512 = ''; -export const analysis_engine_win_x64_sha512 = ''; -export const analysis_engine_osx_x64_sha512 = ''; -export const analysis_engine_linux_x64_sha512 = ''; +// Values are for test purposes only +export const analysis_engine_win_x86_sha512 = 'win-x86'; +export const analysis_engine_win_x64_sha512 = 'win-x64'; +export const analysis_engine_osx_x64_sha512 = 'osx-x64'; +export const analysis_engine_centos_x64_sha512 = 'centos-x64'; +export const analysis_engine_debian_x64_sha512 = 'debian-x64'; +export const analysis_engine_fedora_x64_sha512 = 'fedora-x64'; +export const analysis_engine_ol_x64_sha512 = 'ol-x64'; +export const analysis_engine_opensuse_x64_sha512 = 'opensuse-x64'; +export const analysis_engine_rhel_x64_sha512 = 'rhel-x64'; +export const analysis_engine_ubuntu_x64_sha512 = 'ubuntu-x64'; diff --git a/src/client/activation/downloader.ts b/src/client/activation/downloader.ts index c48b0b4b2503..98a2d2e1bfc2 100644 --- a/src/client/activation/downloader.ts +++ b/src/client/activation/downloader.ts @@ -9,7 +9,7 @@ import { ExtensionContext, OutputChannel, ProgressLocation, window } from 'vscod import { STANDARD_OUTPUT_CHANNEL } from '../common/constants'; import { noop } from '../common/core.utils'; import { createDeferred, createTemporaryFile } from '../common/helpers'; -import { IPlatformService } from '../common/platform/types'; +import { IFileSystem, IPlatformService } from '../common/platform/types'; import { IOutputChannel } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { HashVerifier } from './hashVerifier'; @@ -31,7 +31,7 @@ export class AnalysisEngineDownloader { constructor(private readonly services: IServiceContainer, private engineFolder: string) { this.output = this.services.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); this.platform = this.services.get(IPlatformService); - this.platformData = new PlatformData(this.platform); + this.platformData = new PlatformData(this.platform, this.services.get(IFileSystem)); } public async downloadAnalysisEngine(context: ExtensionContext): Promise { @@ -49,7 +49,7 @@ export class AnalysisEngineDownloader { } private async downloadFile(): Promise { - const platformString = this.platformData.getPlatformDesignator(); + const platformString = await this.platformData.getPlatformName(); const remoteFileName = `${downloadBaseFileName}-${platformString}.${downloadVersion}${downloadFileExtension}`; const uri = `${downloadUriPrefix}/${remoteFileName}`; this.output.append(`Downloading ${uri}... `); @@ -98,7 +98,7 @@ export class AnalysisEngineDownloader { this.output.appendLine(''); this.output.append('Verifying download... '); const verifier = new HashVerifier(); - if (!await verifier.verifyHash(filePath, this.platformData.getExpectedHash())) { + if (!await verifier.verifyHash(filePath, await this.platformData.getExpectedHash())) { throw new Error('Hash of the downloaded file does not match.'); } this.output.append('valid.'); diff --git a/src/client/activation/platformData.ts b/src/client/activation/platformData.ts index 541e5a602bef..2a1cb29da461 100644 --- a/src/client/activation/platformData.ts +++ b/src/client/activation/platformData.ts @@ -1,27 +1,54 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { IPlatformService } from '../common/platform/types'; +import { IFileSystem, IPlatformService } from '../common/platform/types'; import { - analysis_engine_linux_x64_sha512, + analysis_engine_centos_x64_sha512, + analysis_engine_debian_x64_sha512, + analysis_engine_fedora_x64_sha512, + analysis_engine_ol_x64_sha512, + analysis_engine_opensuse_x64_sha512, analysis_engine_osx_x64_sha512, + analysis_engine_rhel_x64_sha512, + analysis_engine_ubuntu_x64_sha512, analysis_engine_win_x64_sha512, analysis_engine_win_x86_sha512 } from './analysisEngineHashes'; +// '/etc/os-release', ID=flavor +const supportedLinuxFlavors = [ + 'centos', + 'debian', + 'fedora', + 'ol', + 'opensuse', + 'rhel', + 'ubuntu' +]; + export class PlatformData { - constructor(private platform: IPlatformService) { } - public getPlatformDesignator(): string { + constructor(private platform: IPlatformService, private fs: IFileSystem) { } + public async getPlatformName(): Promise { if (this.platform.isWindows) { return this.platform.is64bit ? 'win-x64' : 'win-x86'; } if (this.platform.isMac) { return 'osx-x64'; } - if (this.platform.isLinux && this.platform.is64bit) { - return 'linux-x64'; + if (this.platform.isLinux) { + if (!this.platform.is64bit) { + throw new Error('Python Analysis Engine does not support 32-bit Linux.'); + } + const linuxFlavor = await this.getLinuxFlavor(); + if (linuxFlavor.length === 0) { + throw new Error('Unable to determine Linux flavor from /etc/os-release.'); + } + if (supportedLinuxFlavors.indexOf(linuxFlavor) < 0) { + throw new Error(`${linuxFlavor} is not supported.`); + } + return `${linuxFlavor}-x64`; } - throw new Error('Python Analysis Engine does not support 32-bit Linux.'); + throw new Error('Unknown OS platform.'); } public getEngineDllName(): string { @@ -34,7 +61,7 @@ export class PlatformData { : 'Microsoft.PythonTools.VsCode'; } - public getExpectedHash(): string { + public async getExpectedHash(): Promise { if (this.platform.isWindows) { return this.platform.is64bit ? analysis_engine_win_x64_sha512 : analysis_engine_win_x86_sha512; } @@ -42,8 +69,30 @@ export class PlatformData { return analysis_engine_osx_x64_sha512; } if (this.platform.isLinux && this.platform.is64bit) { - return analysis_engine_linux_x64_sha512; + const linuxFlavor = await this.getLinuxFlavor(); + // tslint:disable-next-line:switch-default + switch (linuxFlavor) { + case 'centos': return analysis_engine_centos_x64_sha512; + case 'debian': return analysis_engine_debian_x64_sha512; + case 'fedora': return analysis_engine_fedora_x64_sha512; + case 'ol': return analysis_engine_ol_x64_sha512; + case 'opensuse': return analysis_engine_opensuse_x64_sha512; + case 'rhel': return analysis_engine_rhel_x64_sha512; + case 'ubuntu': return analysis_engine_ubuntu_x64_sha512; + } } throw new Error('Unknown platform.'); } + + private async getLinuxFlavor(): Promise { + const verFile = '/etc/os-release'; + const data = await this.fs.readFile(verFile); + if (data) { + const res = /ID=(.*)/.exec(data); + if (res && res.length > 1) { + return res[1]; + } + } + return ''; + } } diff --git a/src/client/formatters/lineFormatter.ts b/src/client/formatters/lineFormatter.ts index 046533952464..2c7b37580f11 100644 --- a/src/client/formatters/lineFormatter.ts +++ b/src/client/formatters/lineFormatter.ts @@ -43,7 +43,7 @@ export class LineFormatter { case TokenType.Comma: this.builder.append(','); - if (next && !this.isCloseBraceType(next.type)) { + if (next && !this.isCloseBraceType(next.type) && next.type !== TokenType.Colon) { this.builder.softAppendSpace(); } break; @@ -52,7 +52,12 @@ export class LineFormatter { if (prev && !this.isOpenBraceType(prev.type) && prev.type !== TokenType.Colon && prev.type !== TokenType.Operator) { this.builder.softAppendSpace(); } - this.builder.append(this.text.substring(t.start, t.end)); + const id = this.text.substring(t.start, t.end); + this.builder.append(id); + if (this.keywordWithSpaceAfter(id) && next && this.isOpenBraceType(next.type)) { + // for x in () + this.builder.softAppendSpace(); + } break; case TokenType.Colon: @@ -84,8 +89,10 @@ export class LineFormatter { return this.builder.getText(); } + // tslint:disable-next-line:cyclomatic-complexity private handleOperator(index: number): void { const t = this.tokens.getItemAt(index); + const prev = index > 0 ? this.tokens.getItemAt(index - 1) : undefined; if (t.length === 1) { const opCode = this.text.charCodeAt(t.start); switch (opCode) { @@ -99,18 +106,36 @@ export class LineFormatter { case Char.ExclamationMark: this.builder.append(this.text[t.start]); return; + case Char.Asterisk: + if (prev && prev.type === TokenType.Identifier && prev.length === 6 && this.text.substr(prev.start, prev.length) === 'lambda') { + this.builder.softAppendSpace(); + this.builder.append('*'); + return; + } + break; default: break; } + } else if (t.length === 2) { + if (this.text.charCodeAt(t.start) === Char.Asterisk && this.text.charCodeAt(t.start + 1) === Char.Asterisk) { + if (!prev || (prev.type !== TokenType.Identifier && prev.type !== TokenType.Number)) { + this.builder.append('**'); + return; + } + if (prev && prev.type === TokenType.Identifier && prev.length === 6 && this.text.substr(prev.start, prev.length) === 'lambda') { + this.builder.softAppendSpace(); + this.builder.append('**'); + return; + } + } } + // Do not append space if operator is preceded by '(' or ',' as in foo(**kwarg) - if (index > 0) { - const prev = this.tokens.getItemAt(index - 1); - if (this.isOpenBraceType(prev.type) || prev.type === TokenType.Comma) { - this.builder.append(this.text.substring(t.start, t.end)); - return; - } + if (prev && (this.isOpenBraceType(prev.type) || prev.type === TokenType.Comma)) { + this.builder.append(this.text.substring(t.start, t.end)); + return; } + this.builder.softAppendSpace(); this.builder.append(this.text.substring(t.start, t.end)); this.builder.softAppendSpace(); @@ -135,43 +160,82 @@ export class LineFormatter { return; } - if (this.isEqualsInsideArguments(index - 1)) { + const prev = index > 0 ? this.tokens.getItemAt(index - 1) : undefined; + if (prev && prev.length === 1 && this.text.charCodeAt(prev.start) === Char.Equal && this.isEqualsInsideArguments(index - 1)) { // Don't add space around = inside function arguments. this.builder.append(this.text.substring(t.start, t.end)); return; } - if (index > 0) { - const prev = this.tokens.getItemAt(index - 1); - if (this.isOpenBraceType(prev.type) || prev.type === TokenType.Colon) { - // Don't insert space after (, [ or { . - this.builder.append(this.text.substring(t.start, t.end)); - return; - } + if (prev && (this.isOpenBraceType(prev.type) || prev.type === TokenType.Colon)) { + // Don't insert space after (, [ or { . + this.builder.append(this.text.substring(t.start, t.end)); + return; } - // In general, keep tokens separated. - this.builder.softAppendSpace(); - this.builder.append(this.text.substring(t.start, t.end)); + if (t.type === TokenType.Unknown) { + this.handleUnknown(t); + } else { + // In general, keep tokens separated. + this.builder.softAppendSpace(); + this.builder.append(this.text.substring(t.start, t.end)); + } } + private handleUnknown(t: IToken): void { + const prevChar = t.start > 0 ? this.text.charCodeAt(t.start - 1) : 0; + if (prevChar === Char.Space || prevChar === Char.Tab) { + this.builder.softAppendSpace(); + } + this.builder.append(this.text.substring(t.start, t.end)); + + const nextChar = t.end < this.text.length - 1 ? this.text.charCodeAt(t.end) : 0; + if (nextChar === Char.Space || nextChar === Char.Tab) { + this.builder.softAppendSpace(); + } + } private isEqualsInsideArguments(index: number): boolean { + // Since we don't have complete statement, this is mostly heuristics. + // Therefore the code may not be handling all possible ways of the + // argument list continuation. if (index < 1) { return false; } + const prev = this.tokens.getItemAt(index - 1); - if (prev.type === TokenType.Identifier) { - if (index >= 2) { - // (x=1 or ,x=1 - const prevPrev = this.tokens.getItemAt(index - 2); - return prevPrev.type === TokenType.Comma || prevPrev.type === TokenType.OpenBrace; - } else if (index < this.tokens.count - 2) { - const next = this.tokens.getItemAt(index + 1); - const nextNext = this.tokens.getItemAt(index + 2); - // x=1, or x=1) - if (this.isValueType(next.type)) { - return nextNext.type === TokenType.Comma || nextNext.type === TokenType.CloseBrace; - } + if (prev.type !== TokenType.Identifier) { + return false; + } + + const first = this.tokens.getItemAt(0); + if (first.type === TokenType.Comma) { + return true; // Line starts with commma + } + + const last = this.tokens.getItemAt(this.tokens.count - 1); + if (last.type === TokenType.Comma) { + return true; // Line ends in comma + } + + if (index >= 2) { + // (x=1 or ,x=1 + const prevPrev = this.tokens.getItemAt(index - 2); + return prevPrev.type === TokenType.Comma || prevPrev.type === TokenType.OpenBrace; + } + + if (index >= this.tokens.count - 2) { + return false; + } + + const next = this.tokens.getItemAt(index + 1); + const nextNext = this.tokens.getItemAt(index + 2); + // x=1, or x=1) + if (this.isValueType(next.type)) { + if (nextNext.type === TokenType.CloseBrace) { + return true; + } + if (nextNext.type === TokenType.Comma) { + return last.type === TokenType.CloseBrace; } } return false; @@ -198,4 +262,10 @@ export class LineFormatter { } return false; } + private keywordWithSpaceAfter(s: string): boolean { + return s === 'in' || s === 'return' || s === 'and' || + s === 'or' || s === 'not' || s === 'from' || + s === 'import' || s === 'except' || s === 'for' || + s === 'as' || s === 'is'; + } } diff --git a/src/client/language/tokenizer.ts b/src/client/language/tokenizer.ts index e1c8c4b03d9e..7ceafdccb0e6 100644 --- a/src/client/language/tokenizer.ts +++ b/src/client/language/tokenizer.ts @@ -27,13 +27,6 @@ class Token extends TextRange implements IToken { } export class Tokenizer implements ITokenizer { - // private keywords = [ - // 'and', 'assert', 'break', 'class', 'continue', 'def', 'del', - // 'elif', 'else', 'except', 'exec', 'False', 'finally', 'for', 'from', - // 'global', 'if', 'import', 'in', 'is', 'lambda', 'None', 'nonlocal', - // 'not', 'or', 'pass', 'print', 'raise', 'return', 'True', 'try', - // 'while', 'with', 'yield' - // ]; private cs: ICharacterStream = new CharacterStream(''); private tokens: IToken[] = []; private floatRegex = /[-+]?(?:(?:\d*\.\d+)|(?:\d+\.?))(?:[Ee][+-]?\d+)?/; @@ -87,15 +80,17 @@ export class Tokenizer implements ITokenizer { // tslint:disable-next-line:cyclomatic-complexity private handleCharacter(): boolean { - // f-strings - const fString = this.cs.currentChar === Char.f && (this.cs.nextChar === Char.SingleQuote || this.cs.nextChar === Char.DoubleQuote); - if (fString) { - this.cs.moveNext(); - } - const quoteType = this.getQuoteType(); - if (quoteType !== QuoteType.None) { - this.handleString(quoteType, fString); - return true; + // f-strings, b-strings, etc + const stringPrefixLength = this.getStringPrefixLength(); + if (stringPrefixLength >= 0) { + // Indeed a string + this.cs.advance(stringPrefixLength); + + const quoteType = this.getQuoteType(); + if (quoteType !== QuoteType.None) { + this.handleString(quoteType, stringPrefixLength); + return true; + } } if (this.cs.currentChar === Char.Hash) { this.handleComment(); @@ -133,16 +128,16 @@ export class Tokenizer implements ITokenizer { case Char.Colon: this.tokens.push(new Token(TokenType.Colon, this.cs.position, 1)); break; - case Char.At: - case Char.Period: - this.tokens.push(new Token(TokenType.Operator, this.cs.position, 1)); - break; default: if (this.isPossibleNumber()) { if (this.tryNumber()) { return true; } } + if (this.cs.currentChar === Char.Period) { + this.tokens.push(new Token(TokenType.Operator, this.cs.position, 1)); + break; + } if (!this.tryIdentifier()) { if (!this.tryOperator()) { this.handleUnknown(); @@ -170,29 +165,8 @@ export class Tokenizer implements ITokenizer { return false; } + // tslint:disable-next-line:cyclomatic-complexity private isPossibleNumber(): boolean { - if (this.cs.currentChar === Char.Hyphen || this.cs.currentChar === Char.Plus) { - // Next character must be decimal or a dot otherwise - // it is not a number. No whitespace is allowed. - if (isDecimal(this.cs.nextChar) || this.cs.nextChar === Char.Period) { - // Check what previous token is, if any - if (this.tokens.length === 0) { - // At the start of the file this can only be a number - return true; - } - - const prev = this.tokens[this.tokens.length - 1]; - if (prev.type === TokenType.OpenBrace - || prev.type === TokenType.OpenBracket - || prev.type === TokenType.Comma - || prev.type === TokenType.Semicolon - || prev.type === TokenType.Operator) { - return true; - } - } - return false; - } - if (isDecimal(this.cs.currentChar)) { return true; } @@ -201,12 +175,52 @@ export class Tokenizer implements ITokenizer { return true; } + const next = (this.cs.currentChar === Char.Hyphen || this.cs.currentChar === Char.Plus) ? 1 : 0; + // Next character must be decimal or a dot otherwise + // it is not a number. No whitespace is allowed. + if (isDecimal(this.cs.lookAhead(next)) || this.cs.lookAhead(next) === Char.Period) { + // Check what previous token is, if any + if (this.tokens.length === 0) { + // At the start of the file this can only be a number + return true; + } + + const prev = this.tokens[this.tokens.length - 1]; + if (prev.type === TokenType.OpenBrace + || prev.type === TokenType.OpenBracket + || prev.type === TokenType.Comma + || prev.type === TokenType.Colon + || prev.type === TokenType.Semicolon + || prev.type === TokenType.Operator) { + return true; + } + } + + if (this.cs.lookAhead(next) === Char._0) { + const nextNext = this.cs.lookAhead(next + 1); + if (nextNext === Char.x || nextNext === Char.X) { + return true; + } + if (nextNext === Char.b || nextNext === Char.B) { + return true; + } + if (nextNext === Char.o || nextNext === Char.O) { + return true; + } + } + return false; } // tslint:disable-next-line:cyclomatic-complexity private tryNumber(): boolean { const start = this.cs.position; + let leadingSign = 0; + + if (this.cs.currentChar === Char.Hyphen || this.cs.currentChar === Char.Plus) { + this.cs.moveNext(); // Skip leading +/- + leadingSign = 1; + } if (this.cs.currentChar === Char._0) { let radix = 0; @@ -234,20 +248,19 @@ export class Tokenizer implements ITokenizer { } radix = 8; } - const text = this.cs.getText().substr(start, this.cs.position - start); + const text = this.cs.getText().substr(start + leadingSign, this.cs.position - start - leadingSign); if (radix > 0 && parseInt(text.substr(2), radix)) { - this.tokens.push(new Token(TokenType.Number, start, text.length)); + this.tokens.push(new Token(TokenType.Number, start, text.length + leadingSign)); return true; } } - if (isDecimal(this.cs.currentChar) || - this.cs.currentChar === Char.Plus || this.cs.currentChar === Char.Hyphen || this.cs.currentChar === Char.Period) { + if (isDecimal(this.cs.currentChar) || this.cs.currentChar === Char.Period) { const candidate = this.cs.getText().substr(this.cs.position); const re = this.floatRegex.exec(candidate); if (re && re.length > 0 && re[0] && candidate.startsWith(re[0])) { - this.tokens.push(new Token(TokenType.Number, start, re[0].length)); - this.cs.position = start + re[0].length; + this.tokens.push(new Token(TokenType.Number, start, re[0].length + leadingSign)); + this.cs.position = start + re[0].length + leadingSign; return true; } } @@ -262,7 +275,6 @@ export class Tokenizer implements ITokenizer { const nextChar = this.cs.nextChar; switch (this.cs.currentChar) { case Char.Plus: - case Char.Hyphen: case Char.Ampersand: case Char.Bar: case Char.Caret: @@ -271,6 +283,10 @@ export class Tokenizer implements ITokenizer { length = nextChar === Char.Equal ? 2 : 1; break; + case Char.Hyphen: + length = nextChar === Char.Equal || nextChar === Char.Greater ? 2 : 1; + break; + case Char.Asterisk: if (nextChar === Char.Asterisk) { length = this.cs.lookAhead(2) === Char.Equal ? 3 : 2; @@ -306,7 +322,7 @@ export class Tokenizer implements ITokenizer { break; case Char.At: - length = nextChar === Char.Equal ? 2 : 0; + length = nextChar === Char.Equal ? 2 : 1; break; default: @@ -334,6 +350,25 @@ export class Tokenizer implements ITokenizer { this.tokens.push(new Token(TokenType.Comment, start, this.cs.position - start)); } + private getStringPrefixLength(): number { + if (this.cs.currentChar === Char.f && (this.cs.nextChar === Char.SingleQuote || this.cs.nextChar === Char.DoubleQuote)) { + return 1; // f-string + } + if (this.cs.currentChar === Char.b || this.cs.currentChar === Char.B || this.cs.currentChar === Char.u || this.cs.currentChar === Char.U) { + if (this.cs.nextChar === Char.SingleQuote || this.cs.nextChar === Char.DoubleQuote) { + // b-string or u-string + return 1; + } + if (this.cs.nextChar === Char.r || this.cs.nextChar === Char.R) { + // b-string or u-string with 'r' suffix + if (this.cs.lookAhead(2) === Char.SingleQuote || this.cs.lookAhead(2) === Char.DoubleQuote) { + return 2; + } + } + } + return this.cs.currentChar === Char.SingleQuote || this.cs.currentChar === Char.DoubleQuote ? 0 : -1; + } + private getQuoteType(): QuoteType { if (this.cs.currentChar === Char.SingleQuote) { return this.cs.nextChar === Char.SingleQuote && this.cs.lookAhead(2) === Char.SingleQuote @@ -348,8 +383,8 @@ export class Tokenizer implements ITokenizer { return QuoteType.None; } - private handleString(quoteType: QuoteType, fString: boolean): void { - const start = fString ? this.cs.position - 1 : this.cs.position; + private handleString(quoteType: QuoteType, stringPrefixLength: number): void { + const start = this.cs.position - stringPrefixLength; if (quoteType === QuoteType.Single || quoteType === QuoteType.Double) { this.cs.moveNext(); this.skipToSingleEndQuote(quoteType === QuoteType.Single diff --git a/src/test/activation/platformData.test.ts b/src/test/activation/platformData.test.ts new file mode 100644 index 000000000000..4270f4c6a3fc --- /dev/null +++ b/src/test/activation/platformData.test.ts @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// tslint:disable:no-unused-variable +import * as assert from 'assert'; +import * as TypeMoq from 'typemoq'; +import { PlatformData } from '../../client/activation/platformData'; +import { IFileSystem, IPlatformService } from '../../client/common/platform/types'; +import { initialize } from '../initialize'; + +const testDataWinMac = [ + { isWindows: true, is64Bit: true, expectedName: 'win-x64' }, + { isWindows: true, is64Bit: false, expectedName: 'win-x86' }, + { isWindows: false, is64Bit: true, expectedName: 'osx-x64' } +]; + +const testDataLinux = [ + { name: 'centos', expectedName: 'centos-x64' }, + { name: 'debian', expectedName: 'debian-x64' }, + { name: 'fedora', expectedName: 'fedora-x64' }, + { name: 'ol', expectedName: 'ol-x64' }, + { name: 'opensuse', expectedName: 'opensuse-x64' }, + { name: 'rhel', expectedName: 'rhel-x64' }, + { name: 'ubuntu', expectedName: 'ubuntu-x64' } +]; + +const testDataModuleName = [ + { isWindows: true, expectedName: 'Microsoft.PythonTools.VsCode.exe' }, + { isWindows: false, expectedName: 'Microsoft.PythonTools.VsCode' } +]; + +// tslint:disable-next-line:max-func-body-length +suite('Activation - platform data', () => { + suiteSetup(initialize); + + test('Name and hash (Windows/Mac)', async () => { + for (const t of testDataWinMac) { + const platformService = TypeMoq.Mock.ofType(); + platformService.setup(x => x.isWindows).returns(() => t.isWindows); + platformService.setup(x => x.isMac).returns(() => !t.isWindows); + platformService.setup(x => x.is64bit).returns(() => t.is64Bit); + + const fs = TypeMoq.Mock.ofType(); + const pd = new PlatformData(platformService.object, fs.object); + + let actual = await pd.getPlatformName(); + assert.equal(actual, t.expectedName, `${actual} does not match ${t.expectedName}`); + + actual = await pd.getExpectedHash(); + assert.equal(actual, t.expectedName, `${actual} hash not match ${t.expectedName}`); + } + }); + test('Name and hash (Linux)', async () => { + for (const t of testDataLinux) { + const platformService = TypeMoq.Mock.ofType(); + platformService.setup(x => x.isWindows).returns(() => false); + platformService.setup(x => x.isMac).returns(() => false); + platformService.setup(x => x.isLinux).returns(() => true); + platformService.setup(x => x.is64bit).returns(() => true); + + const fs = TypeMoq.Mock.ofType(); + fs.setup(x => x.readFile(TypeMoq.It.isAnyString())).returns(() => Promise.resolve(`NAME="name"\nID=${t.name}\nID_LIKE=debian`)); + const pd = new PlatformData(platformService.object, fs.object); + + let actual = await pd.getPlatformName(); + assert.equal(actual, t.expectedName, `${actual} does not match ${t.expectedName}`); + + actual = await pd.getExpectedHash(); + assert.equal(actual, t.expectedName, `${actual} hash not match ${t.expectedName}`); + } + }); + test('Module name', async () => { + for (const t of testDataModuleName) { + const platformService = TypeMoq.Mock.ofType(); + platformService.setup(x => x.isWindows).returns(() => t.isWindows); + + const fs = TypeMoq.Mock.ofType(); + const pd = new PlatformData(platformService.object, fs.object); + + const actual = pd.getEngineExecutableName(); + assert.equal(actual, t.expectedName, `${actual} does not match ${t.expectedName}`); + } + }); +}); diff --git a/src/test/definitions/hover.ptvs.test.ts b/src/test/definitions/hover.ptvs.test.ts index d2a456efd4bd..4f0f014c7bff 100644 --- a/src/test/definitions/hover.ptvs.test.ts +++ b/src/test/definitions/hover.ptvs.test.ts @@ -53,9 +53,7 @@ suite('Hover Definition (Analysis Engine)', () => { const expected = [ 'obj.method1:', 'method method1 of one.Class1 objects', - '```html', - 'This is method1', - '```' + 'This is method1' ]; verifySignatureLines(actual, expected); }); @@ -70,9 +68,7 @@ suite('Hover Definition (Analysis Engine)', () => { const expected = [ 'two.ct().fun:', 'method fun of two.ct objects', - '```html', - 'This is fun', - '```' + 'This is fun' ]; verifySignatureLines(actual, expected); }); @@ -87,11 +83,9 @@ suite('Hover Definition (Analysis Engine)', () => { const expected = [ 'Foo.bar:', 'four.Foo.bar() -> bool', - '```html', '说明 - keep this line, it works', 'delete following line, it works', '如果存在需要等待审批或正在执行的任务,将不刷新页面', - '```', 'declared in Foo' ]; verifySignatureLines(actual, expected); @@ -107,22 +101,26 @@ suite('Hover Definition (Analysis Engine)', () => { const expected = [ 'four.showMessage:', 'four.showMessage()', - '```html', 'Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи.', - 'Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.', - '```' + 'Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.' ]; verifySignatureLines(actual, expected); }); test('Nothing for keywords (class)', async () => { const def = await openAndHover(fileOne, 5, 1); - assert.equal(def.length, 0, 'Definition length is incorrect'); + if (def.length > 0) { + const actual = normalizeMarkedString(def[0].contents[0]); + assert.equal(actual, '', 'Definition length is incorrect'); + } }); test('Nothing for keywords (for)', async () => { const def = await openAndHover(fileHover, 3, 1); - assert.equal(def!.length, 0, 'Definition length is incorrect'); + if (def.length > 0) { + const actual = normalizeMarkedString(def[0].contents[0]); + assert.equal(actual, '', 'Definition length is incorrect'); + } }); test('Highlighting Class', async () => { @@ -136,15 +134,13 @@ suite('Hover Definition (Analysis Engine)', () => { 'misc.Random:', 'class misc.Random(_random.Random)', 'Random number generator base class used by bound module functions.', - '```html', 'Used to instantiate instances of Random to get generators that don\'t', 'share state.', 'Class Random can also be subclassed if you want to use a different basic', 'generator of your own devising: in that case, override the following', 'methods: random(), seed(), getstate(), and setstate().', 'Optionally, implement a getrandbits() method so that randrange()', - 'can cover arbitrarily large ranges.', - '```' + 'can cover arbitrarily large ranges.' ]; verifySignatureLines(actual, expected); }); @@ -191,9 +187,7 @@ suite('Hover Definition (Analysis Engine)', () => { 'misc.Thread:', 'class misc.Thread(_Verbose)', 'A class that represents a thread of control.', - '```html', - 'This class can be safely subclassed in a limited fashion.', - '```' + 'This class can be safely subclassed in a limited fashion.' ]; verifySignatureLines(actual, expected); }); diff --git a/src/test/format/extension.lineFormatter.test.ts b/src/test/format/extension.lineFormatter.test.ts index 3325c19382a2..a9cb0fa04447 100644 --- a/src/test/format/extension.lineFormatter.test.ts +++ b/src/test/format/extension.lineFormatter.test.ts @@ -3,9 +3,16 @@ // Licensed under the MIT License. import * as assert from 'assert'; +import * as fs from 'fs'; +import * as path from 'path'; +import '../../client/common/extensions'; import { LineFormatter } from '../../client/formatters/lineFormatter'; +const formatFilesPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'formatting'); +const grammarFile = path.join(formatFilesPath, 'pythonGrammar.py'); + // https://www.python.org/dev/peps/pep-0008/#code-lay-out +// tslint:disable-next-line:max-func-body-length suite('Formatting - line formatter', () => { const formatter = new LineFormatter(); @@ -81,8 +88,53 @@ suite('Formatting - line formatter', () => { const actual = formatter.formatLine(',x = 1,y =m)'); assert.equal(actual, ', x=1, y=m)'); }); + test('Equals in multiline arguments ending comma', () => { + const actual = formatter.formatLine('x = 1,y =m,'); + assert.equal(actual, 'x=1, y=m,'); + }); test('Operators without following space', () => { const actual = formatter.formatLine('foo( *a, ** b, ! c)'); assert.equal(actual, 'foo(*a, **b, !c)'); }); + test('Brace after keyword', () => { + const actual = formatter.formatLine('for x in(1,2,3)'); + assert.equal(actual, 'for x in (1, 2, 3)'); + }); + test('Dot operator', () => { + const actual = formatter.formatLine('x.y'); + assert.equal(actual, 'x.y'); + }); + test('Unknown tokens no space', () => { + const actual = formatter.formatLine('abc\\n\\'); + assert.equal(actual, 'abc\\n\\'); + }); + test('Unknown tokens with space', () => { + const actual = formatter.formatLine('abc \\n \\'); + assert.equal(actual, 'abc \\n \\'); + }); + test('Double asterisk', () => { + const actual = formatter.formatLine('a**2, ** k'); + assert.equal(actual, 'a ** 2, **k'); + }); + test('Lambda', () => { + const actual = formatter.formatLine('lambda * args, :0'); + assert.equal(actual, 'lambda *args,: 0'); + }); + test('Comma expression', () => { + const actual = formatter.formatLine('x=1,2,3'); + assert.equal(actual, 'x = 1, 2, 3'); + }); + test('is exression', () => { + const actual = formatter.formatLine('a( (False is 2) is 3)'); + assert.equal(actual, 'a((False is 2) is 3)'); + }); + test('Grammar file', () => { + const content = fs.readFileSync(grammarFile).toString('utf8'); + const lines = content.splitLines({ trim: false, removeEmptyEntries: false }); + for (let i = 0; i < lines.length; i += 1) { + const line = lines[i]; + const actual = formatter.formatLine(line); + assert.equal(actual, line, `Line ${i + 1} changed: '${line}' to '${actual}'`); + } + }); }); diff --git a/src/test/language/tokenizer.test.ts b/src/test/language/tokenizer.test.ts index 202f0c774297..d7119b7b4f6f 100644 --- a/src/test/language/tokenizer.test.ts +++ b/src/test/language/tokenizer.test.ts @@ -9,14 +9,14 @@ import { TokenType } from '../../client/language/types'; // tslint:disable-next-line:max-func-body-length suite('Language.Tokenizer', () => { - test('Empty', async () => { + test('Empty', () => { const t = new Tokenizer(); const tokens = t.tokenize(''); assert.equal(tokens instanceof TextRangeCollection, true); assert.equal(tokens.count, 0); assert.equal(tokens.length, 0); }); - test('Strings: unclosed', async () => { + test('Strings: unclosed', () => { const t = new Tokenizer(); const tokens = t.tokenize(' "string" """line1\n#line2"""\t\'un#closed'); assert.equal(tokens.count, 3); @@ -28,7 +28,7 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(i).type, TokenType.String); } }); - test('Strings: block next to regular, double-quoted', async () => { + test('Strings: block next to regular, double-quoted', () => { const t = new Tokenizer(); const tokens = t.tokenize('"string""""s2"""'); assert.equal(tokens.count, 2); @@ -40,7 +40,7 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(i).type, TokenType.String); } }); - test('Strings: block next to block, double-quoted', async () => { + test('Strings: block next to block, double-quoted', () => { const t = new Tokenizer(); const tokens = t.tokenize('""""""""'); assert.equal(tokens.count, 2); @@ -52,7 +52,7 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(i).type, TokenType.String); } }); - test('Strings: unclosed sequence of quotes', async () => { + test('Strings: unclosed sequence of quotes', () => { const t = new Tokenizer(); const tokens = t.tokenize('"""""'); assert.equal(tokens.count, 1); @@ -64,7 +64,7 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(i).type, TokenType.String); } }); - test('Strings: single quote escape', async () => { + test('Strings: single quote escape', () => { const t = new Tokenizer(); // tslint:disable-next-line:quotemark const tokens = t.tokenize("'\\'quoted\\''"); @@ -72,14 +72,14 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(0).type, TokenType.String); assert.equal(tokens.getItemAt(0).length, 12); }); - test('Strings: double quote escape', async () => { + test('Strings: double quote escape', () => { const t = new Tokenizer(); const tokens = t.tokenize('"\\"quoted\\""'); assert.equal(tokens.count, 1); assert.equal(tokens.getItemAt(0).type, TokenType.String); assert.equal(tokens.getItemAt(0).length, 12); }); - test('Strings: single quoted f-string ', async () => { + test('Strings: single quoted f-string ', () => { const t = new Tokenizer(); // tslint:disable-next-line:quotemark const tokens = t.tokenize("a+f'quoted'"); @@ -89,7 +89,7 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(2).type, TokenType.String); assert.equal(tokens.getItemAt(2).length, 9); }); - test('Strings: double quoted f-string ', async () => { + test('Strings: double quoted f-string ', () => { const t = new Tokenizer(); const tokens = t.tokenize('x(1,f"quoted")'); assert.equal(tokens.count, 6); @@ -101,7 +101,7 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(4).length, 9); assert.equal(tokens.getItemAt(5).type, TokenType.CloseBrace); }); - test('Strings: single quoted multiline f-string ', async () => { + test('Strings: single quoted multiline f-string ', () => { const t = new Tokenizer(); // tslint:disable-next-line:quotemark const tokens = t.tokenize("f'''quoted'''"); @@ -109,14 +109,14 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(0).type, TokenType.String); assert.equal(tokens.getItemAt(0).length, 13); }); - test('Strings: double quoted multiline f-string ', async () => { + test('Strings: double quoted multiline f-string ', () => { const t = new Tokenizer(); const tokens = t.tokenize('f"""quoted """'); assert.equal(tokens.count, 1); assert.equal(tokens.getItemAt(0).type, TokenType.String); assert.equal(tokens.getItemAt(0).length, 14); }); - test('Strings: escape at the end of single quoted string ', async () => { + test('Strings: escape at the end of single quoted string ', () => { const t = new Tokenizer(); // tslint:disable-next-line:quotemark const tokens = t.tokenize("'quoted\\'\nx"); @@ -125,7 +125,7 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(0).length, 9); assert.equal(tokens.getItemAt(1).type, TokenType.Identifier); }); - test('Strings: escape at the end of double quoted string ', async () => { + test('Strings: escape at the end of double quoted string ', () => { const t = new Tokenizer(); const tokens = t.tokenize('"quoted\\"\nx'); assert.equal(tokens.count, 2); @@ -133,7 +133,28 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(0).length, 9); assert.equal(tokens.getItemAt(1).type, TokenType.Identifier); }); - test('Comments', async () => { + test('Strings: b/u/r-string', () => { + const t = new Tokenizer(); + const tokens = t.tokenize('b"b" u"u" br"br" ur"ur"'); + assert.equal(tokens.count, 4); + assert.equal(tokens.getItemAt(0).type, TokenType.String); + assert.equal(tokens.getItemAt(0).length, 4); + assert.equal(tokens.getItemAt(1).type, TokenType.String); + assert.equal(tokens.getItemAt(1).length, 4); + assert.equal(tokens.getItemAt(2).type, TokenType.String); + assert.equal(tokens.getItemAt(2).length, 6); + assert.equal(tokens.getItemAt(3).type, TokenType.String); + assert.equal(tokens.getItemAt(3).length, 6); + }); + test('Strings: escape at the end of double quoted string ', () => { + const t = new Tokenizer(); + const tokens = t.tokenize('"quoted\\"\nx'); + assert.equal(tokens.count, 2); + assert.equal(tokens.getItemAt(0).type, TokenType.String); + assert.equal(tokens.getItemAt(0).length, 9); + assert.equal(tokens.getItemAt(1).type, TokenType.Identifier); + }); + test('Comments', () => { const t = new Tokenizer(); const tokens = t.tokenize(' #co"""mment1\n\t\n#comm\'ent2 '); assert.equal(tokens.count, 2); @@ -145,7 +166,7 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(i).type, TokenType.Comment); } }); - test('Period to operator token', async () => { + test('Period to operator token', () => { const t = new Tokenizer(); const tokens = t.tokenize('x.y'); assert.equal(tokens.count, 3); @@ -154,7 +175,7 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(1).type, TokenType.Operator); assert.equal(tokens.getItemAt(2).type, TokenType.Identifier); }); - test('@ to operator token', async () => { + test('@ to operator token', () => { const t = new Tokenizer(); const tokens = t.tokenize('@x'); assert.equal(tokens.count, 2); @@ -162,14 +183,14 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(0).type, TokenType.Operator); assert.equal(tokens.getItemAt(1).type, TokenType.Identifier); }); - test('Unknown token', async () => { + test('Unknown token', () => { const t = new Tokenizer(); const tokens = t.tokenize('~$'); assert.equal(tokens.count, 1); assert.equal(tokens.getItemAt(0).type, TokenType.Unknown); }); - test('Hex number', async () => { + test('Hex number', () => { const t = new Tokenizer(); const tokens = t.tokenize('1 0X2 0x3 0x'); assert.equal(tokens.count, 4); @@ -186,7 +207,7 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(3).type, TokenType.Unknown); assert.equal(tokens.getItemAt(3).length, 2); }); - test('Binary number', async () => { + test('Binary number', () => { const t = new Tokenizer(); const tokens = t.tokenize('1 0B1 0b010 0b3 0b'); assert.equal(tokens.count, 6); @@ -209,10 +230,10 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(5).type, TokenType.Unknown); assert.equal(tokens.getItemAt(5).length, 2); }); - test('Octal number', async () => { + test('Octal number', () => { const t = new Tokenizer(); - const tokens = t.tokenize('1 0o4 0o077 0o9 0oO'); - assert.equal(tokens.count, 6); + const tokens = t.tokenize('1 0o4 0o077 -0o200 0o9 0oO'); + assert.equal(tokens.count, 7); assert.equal(tokens.getItemAt(0).type, TokenType.Number); assert.equal(tokens.getItemAt(0).length, 1); @@ -224,21 +245,69 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(2).length, 5); assert.equal(tokens.getItemAt(3).type, TokenType.Number); - assert.equal(tokens.getItemAt(3).length, 1); + assert.equal(tokens.getItemAt(3).length, 6); - assert.equal(tokens.getItemAt(4).type, TokenType.Identifier); - assert.equal(tokens.getItemAt(4).length, 2); + assert.equal(tokens.getItemAt(4).type, TokenType.Number); + assert.equal(tokens.getItemAt(4).length, 1); - assert.equal(tokens.getItemAt(5).type, TokenType.Unknown); - assert.equal(tokens.getItemAt(5).length, 3); + assert.equal(tokens.getItemAt(5).type, TokenType.Identifier); + assert.equal(tokens.getItemAt(5).length, 2); + + assert.equal(tokens.getItemAt(6).type, TokenType.Unknown); + assert.equal(tokens.getItemAt(6).length, 3); + }); + test('Decimal number', () => { + const t = new Tokenizer(); + const tokens = t.tokenize('-2147483647 ++2147483647'); + assert.equal(tokens.count, 3); + + assert.equal(tokens.getItemAt(0).type, TokenType.Number); + assert.equal(tokens.getItemAt(0).length, 11); + + assert.equal(tokens.getItemAt(1).type, TokenType.Operator); + assert.equal(tokens.getItemAt(1).length, 1); + + assert.equal(tokens.getItemAt(2).type, TokenType.Number); + assert.equal(tokens.getItemAt(2).length, 11); + }); + test('Decimal number operator', () => { + const t = new Tokenizer(); + const tokens = t.tokenize('a[: -1]'); + assert.equal(tokens.count, 5); + + assert.equal(tokens.getItemAt(3).type, TokenType.Number); + assert.equal(tokens.getItemAt(3).length, 2); + }); + test('Floating point number', () => { + const t = new Tokenizer(); + const tokens = t.tokenize('3.0 .2 ++.3e+12 --.4e1'); + assert.equal(tokens.count, 6); + + assert.equal(tokens.getItemAt(0).type, TokenType.Number); + assert.equal(tokens.getItemAt(0).length, 3); + + assert.equal(tokens.getItemAt(1).type, TokenType.Number); + assert.equal(tokens.getItemAt(1).length, 2); + + assert.equal(tokens.getItemAt(2).type, TokenType.Operator); + assert.equal(tokens.getItemAt(2).length, 1); + + assert.equal(tokens.getItemAt(3).type, TokenType.Number); + assert.equal(tokens.getItemAt(3).length, 7); + + assert.equal(tokens.getItemAt(4).type, TokenType.Operator); + assert.equal(tokens.getItemAt(4).length, 1); + + assert.equal(tokens.getItemAt(5).type, TokenType.Number); + assert.equal(tokens.getItemAt(5).length, 5); }); - test('Operators', async () => { + test('Operators', () => { const text = '< <> << <<= ' + '== != > >> >>= >= <=' + '+ -' + '* ** / /= //=' + '*= += -= **= ' + - '& &= | |= ^ ^='; + '& &= | |= ^ ^= ->'; const tokens = new Tokenizer().tokenize(text); const lengths = [ 1, 2, 2, 3, @@ -246,7 +315,7 @@ suite('Language.Tokenizer', () => { 1, 1, 1, 2, 1, 2, 3, 2, 2, 2, 3, - 1, 2, 1, 2, 1, 2]; + 1, 2, 1, 2, 1, 2, 2]; assert.equal(tokens.count, lengths.length); for (let i = 0; i < tokens.count; i += 1) { const t = tokens.getItemAt(i); diff --git a/src/test/pythonFiles/formatting/pythonGrammar.py b/src/test/pythonFiles/formatting/pythonGrammar.py new file mode 100644 index 000000000000..32b82285c12f --- /dev/null +++ b/src/test/pythonFiles/formatting/pythonGrammar.py @@ -0,0 +1,1572 @@ +# Python test set -- part 1, grammar. +# This just tests whether the parser accepts them all. + +from test.support import check_syntax_error +import inspect +import unittest +import sys +# testing import * +from sys import * + +# different import patterns to check that __annotations__ does not interfere +# with import machinery +import test.ann_module as ann_module +import typing +from collections import ChainMap +from test import ann_module2 +import test + +# These are shared with test_tokenize and other test modules. +# +# Note: since several test cases filter out floats by looking for "e" and ".", +# don't add hexadecimal literals that contain "e" or "E". +VALID_UNDERSCORE_LITERALS = [ + '0_0_0', + '4_2', + '1_0000_0000', + '0b1001_0100', + '0xffff_ffff', + '0o5_7_7', + '1_00_00.5', + '1_00_00.5e5', + '1_00_00e5_1', + '1e1_0', + '.1_4', + '.1_4e1', + '0b_0', + '0x_f', + '0o_5', + '1_00_00j', + '1_00_00.5j', + '1_00_00e5_1j', + '.1_4j', + '(1_2.5+3_3j)', + '(.5_6j)', +] +INVALID_UNDERSCORE_LITERALS = [ + # Trailing underscores: + '0_', + '42_', + '1.4j_', + '0x_', + '0b1_', + '0xf_', + '0o5_', + '0 if 1_Else 1', + # Underscores in the base selector: + '0_b0', + '0_xf', + '0_o5', + # Old-style octal, still disallowed: + '0_7', + '09_99', + # Multiple consecutive underscores: + '4_______2', + '0.1__4', + '0.1__4j', + '0b1001__0100', + '0xffff__ffff', + '0x___', + '0o5__77', + '1e1__0', + '1e1__0j', + # Underscore right before a dot: + '1_.4', + '1_.4j', + # Underscore right after a dot: + '1._4', + '1._4j', + '._5', + '._5j', + # Underscore right after a sign: + '1.0e+_1', + '1.0e+_1j', + # Underscore right before j: + '1.4_j', + '1.4e5_j', + # Underscore right before e: + '1_e1', + '1.4_e1', + '1.4_e1j', + # Underscore right after e: + '1e_1', + '1.4e_1', + '1.4e_1j', + # Complex cases with parens: + '(1+1.5_j_)', + '(1+1.5_j)', +] + + +class TokenTests(unittest.TestCase): + + def test_backslash(self): + # Backslash means line continuation: + x = 1 \ + + 1 + self.assertEqual(x, 2, 'backslash for line continuation') + + # Backslash does not means continuation in comments :\ + x = 0 + self.assertEqual(x, 0, 'backslash ending comment') + + def test_plain_integers(self): + self.assertEqual(type(000), type(0)) + self.assertEqual(0xff, 255) + self.assertEqual(0o377, 255) + self.assertEqual(2147483647, 0o17777777777) + self.assertEqual(0b1001, 9) + # "0x" is not a valid literal + self.assertRaises(SyntaxError, eval, "0x") + from sys import maxsize + if maxsize == 2147483647: + self.assertEqual(-2147483647 - 1, -0o20000000000) + # XXX -2147483648 + self.assertTrue(0o37777777777 > 0) + self.assertTrue(0xffffffff > 0) + self.assertTrue(0b1111111111111111111111111111111 > 0) + for s in ('2147483648', '0o40000000000', '0x100000000', + '0b10000000000000000000000000000000'): + try: + x = eval(s) + except OverflowError: + self.fail("OverflowError on huge integer literal %r" % s) + elif maxsize == 9223372036854775807: + self.assertEqual(-9223372036854775807 - 1, -0o1000000000000000000000) + self.assertTrue(0o1777777777777777777777 > 0) + self.assertTrue(0xffffffffffffffff > 0) + self.assertTrue(0b11111111111111111111111111111111111111111111111111111111111111 > 0) + for s in '9223372036854775808', '0o2000000000000000000000', \ + '0x10000000000000000', \ + '0b100000000000000000000000000000000000000000000000000000000000000': + try: + x = eval(s) + except OverflowError: + self.fail("OverflowError on huge integer literal %r" % s) + else: + self.fail('Weird maxsize value %r' % maxsize) + + def test_long_integers(self): + x = 0 + x = 0xffffffffffffffff + x = 0Xffffffffffffffff + x = 0o77777777777777777 + x = 0O77777777777777777 + x = 123456789012345678901234567890 + x = 0b100000000000000000000000000000000000000000000000000000000000000000000 + x = 0B111111111111111111111111111111111111111111111111111111111111111111111 + + def test_floats(self): + x = 3.14 + x = 314. + x = 0.314 + # XXX x = 000.314 + x = .314 + x = 3e14 + x = 3E14 + x = 3e-14 + x = 3e+14 + x = 3.e14 + x = .3e14 + x = 3.1e4 + + def test_float_exponent_tokenization(self): + # See issue 21642. + self.assertEqual(1 if 1 else 0, 1) + self.assertEqual(1 if 0 else 0, 0) + self.assertRaises(SyntaxError, eval, "0 if 1Else 0") + + def test_underscore_literals(self): + for lit in VALID_UNDERSCORE_LITERALS: + self.assertEqual(eval(lit), eval(lit.replace('_', ''))) + for lit in INVALID_UNDERSCORE_LITERALS: + self.assertRaises(SyntaxError, eval, lit) + # Sanity check: no literal begins with an underscore + self.assertRaises(NameError, eval, "_0") + + def test_string_literals(self): + x = ''; y = ""; self.assertTrue(len(x) == 0 and x == y) + x = '\''; y = "'"; self.assertTrue(len(x) == 1 and x == y and ord(x) == 39) + x = '"'; y = "\""; self.assertTrue(len(x) == 1 and x == y and ord(x) == 34) + x = "doesn't \"shrink\" does it" + y = 'doesn\'t "shrink" does it' + self.assertTrue(len(x) == 24 and x == y) + x = "does \"shrink\" doesn't it" + y = 'does "shrink" doesn\'t it' + self.assertTrue(len(x) == 24 and x == y) + x = """ +The "quick" +brown fox +jumps over +the 'lazy' dog. +""" + y = '\nThe "quick"\nbrown fox\njumps over\nthe \'lazy\' dog.\n' + self.assertEqual(x, y) + y = ''' +The "quick" +brown fox +jumps over +the 'lazy' dog. +''' + self.assertEqual(x, y) + y = "\n\ +The \"quick\"\n\ +brown fox\n\ +jumps over\n\ +the 'lazy' dog.\n\ +" + self.assertEqual(x, y) + y = '\n\ +The \"quick\"\n\ +brown fox\n\ +jumps over\n\ +the \'lazy\' dog.\n\ +' + self.assertEqual(x, y) + + def test_ellipsis(self): + x = ... + self.assertTrue(x is Ellipsis) + self.assertRaises(SyntaxError, eval, ".. .") + + def test_eof_error(self): + samples = ("def foo(", "\ndef foo(", "def foo(\n") + for s in samples: + with self.assertRaises(SyntaxError) as cm: + compile(s, "", "exec") + self.assertIn("unexpected EOF", str(cm.exception)) + +var_annot_global: int # a global annotated is necessary for test_var_annot + +# custom namespace for testing __annotations__ + +class CNS: + def __init__(self): + self._dct = {} + def __setitem__(self, item, value): + self._dct[item.lower()] = value + def __getitem__(self, item): + return self._dct[item] + + +class GrammarTests(unittest.TestCase): + + check_syntax_error = check_syntax_error + + # single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE + # XXX can't test in a script -- this rule is only used when interactive + + # file_input: (NEWLINE | stmt)* ENDMARKER + # Being tested as this very moment this very module + + # expr_input: testlist NEWLINE + # XXX Hard to test -- used only in calls to input() + + def test_eval_input(self): + # testlist ENDMARKER + x = eval('1, 0 or 1') + + def test_var_annot_basics(self): + # all these should be allowed + var1: int = 5 + var2: [int, str] + my_lst = [42] + def one(): + return 1 + int.new_attr: int + [list][0]: type + my_lst[one() - 1]: int = 5 + self.assertEqual(my_lst, [5]) + + def test_var_annot_syntax_errors(self): + # parser pass + check_syntax_error(self, "def f: int") + check_syntax_error(self, "x: int: str") + check_syntax_error(self, "def f():\n" + " nonlocal x: int\n") + # AST pass + check_syntax_error(self, "[x, 0]: int\n") + check_syntax_error(self, "f(): int\n") + check_syntax_error(self, "(x,): int") + check_syntax_error(self, "def f():\n" + " (x, y): int = (1, 2)\n") + # symtable pass + check_syntax_error(self, "def f():\n" + " x: int\n" + " global x\n") + check_syntax_error(self, "def f():\n" + " global x\n" + " x: int\n") + + def test_var_annot_basic_semantics(self): + # execution order + with self.assertRaises(ZeroDivisionError): + no_name[does_not_exist]: no_name_again = 1 / 0 + with self.assertRaises(NameError): + no_name[does_not_exist]: 1 / 0 = 0 + global var_annot_global + + # function semantics + def f(): + st: str = "Hello" + a.b: int = (1, 2) + return st + self.assertEqual(f.__annotations__, {}) + def f_OK(): + x: 1 / 0 + f_OK() + def fbad(): + x: int + print(x) + with self.assertRaises(UnboundLocalError): + fbad() + def f2bad(): + (no_such_global): int + print(no_such_global) + try: + f2bad() + except Exception as e: + self.assertIs(type(e), NameError) + + # class semantics + class C: + __foo: int + s: str = "attr" + z = 2 + def __init__(self, x): + self.x: int = x + self.assertEqual(C.__annotations__, {'_C__foo': int, 's': str}) + with self.assertRaises(NameError): + class CBad: + no_such_name_defined.attr: int = 0 + with self.assertRaises(NameError): + class Cbad2(C): + x: int + x.y: list = [] + + def test_var_annot_metaclass_semantics(self): + class CMeta(type): + @classmethod + def __prepare__(metacls, name, bases, **kwds): + return {'__annotations__': CNS()} + class CC(metaclass=CMeta): + XX: 'ANNOT' + self.assertEqual(CC.__annotations__['xx'], 'ANNOT') + + def test_var_annot_module_semantics(self): + with self.assertRaises(AttributeError): + print(test.__annotations__) + self.assertEqual(ann_module.__annotations__, + {1: 2, 'x': int, 'y': str, 'f': typing.Tuple[int, int]}) + self.assertEqual(ann_module.M.__annotations__, + {'123': 123, 'o': type}) + self.assertEqual(ann_module2.__annotations__, {}) + + def test_var_annot_in_module(self): + # check that functions fail the same way when executed + # outside of module where they were defined + from test.ann_module3 import f_bad_ann, g_bad_ann, D_bad_ann + with self.assertRaises(NameError): + f_bad_ann() + with self.assertRaises(NameError): + g_bad_ann() + with self.assertRaises(NameError): + D_bad_ann(5) + + def test_var_annot_simple_exec(self): + gns = {}; lns = {} + exec("'docstring'\n" + "__annotations__[1] = 2\n" + "x: int = 5\n", gns, lns) + self.assertEqual(lns["__annotations__"], {1: 2, 'x': int}) + with self.assertRaises(KeyError): + gns['__annotations__'] + + def test_var_annot_custom_maps(self): + # tests with custom locals() and __annotations__ + ns = {'__annotations__': CNS()} + exec('X: int; Z: str = "Z"; (w): complex = 1j', ns) + self.assertEqual(ns['__annotations__']['x'], int) + self.assertEqual(ns['__annotations__']['z'], str) + with self.assertRaises(KeyError): + ns['__annotations__']['w'] + nonloc_ns = {} + class CNS2: + def __init__(self): + self._dct = {} + def __setitem__(self, item, value): + nonlocal nonloc_ns + self._dct[item] = value + nonloc_ns[item] = value + def __getitem__(self, item): + return self._dct[item] + exec('x: int = 1', {}, CNS2()) + self.assertEqual(nonloc_ns['__annotations__']['x'], int) + + def test_var_annot_refleak(self): + # complex case: custom locals plus custom __annotations__ + # this was causing refleak + cns = CNS() + nonloc_ns = {'__annotations__': cns} + class CNS2: + def __init__(self): + self._dct = {'__annotations__': cns} + def __setitem__(self, item, value): + nonlocal nonloc_ns + self._dct[item] = value + nonloc_ns[item] = value + def __getitem__(self, item): + return self._dct[item] + exec('X: str', {}, CNS2()) + self.assertEqual(nonloc_ns['__annotations__']['x'], str) + + def test_funcdef(self): + ### [decorators] 'def' NAME parameters ['->' test] ':' suite + ### decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE + ### decorators: decorator+ + ### parameters: '(' [typedargslist] ')' + ### typedargslist: ((tfpdef ['=' test] ',')* + ### ('*' [tfpdef] (',' tfpdef ['=' test])* [',' '**' tfpdef] | '**' tfpdef) + ### | tfpdef ['=' test] (',' tfpdef ['=' test])* [',']) + ### tfpdef: NAME [':' test] + ### varargslist: ((vfpdef ['=' test] ',')* + ### ('*' [vfpdef] (',' vfpdef ['=' test])* [',' '**' vfpdef] | '**' vfpdef) + ### | vfpdef ['=' test] (',' vfpdef ['=' test])* [',']) + ### vfpdef: NAME + def f1(): pass + f1() + f1(*()) + f1(*(), **{}) + def f2(one_argument): pass + def f3(two, arguments): pass + self.assertEqual(f2.__code__.co_varnames, ('one_argument',)) + self.assertEqual(f3.__code__.co_varnames, ('two', 'arguments')) + def a1(one_arg,): pass + def a2(two, args,): pass + def v0(*rest): pass + def v1(a, *rest): pass + def v2(a, b, *rest): pass + + f1() + f2(1) + f2(1,) + f3(1, 2) + f3(1, 2,) + v0() + v0(1) + v0(1,) + v0(1, 2) + v0(1, 2, 3, 4, 5, 6, 7, 8, 9, 0) + v1(1) + v1(1,) + v1(1, 2) + v1(1, 2, 3) + v1(1, 2, 3, 4, 5, 6, 7, 8, 9, 0) + v2(1, 2) + v2(1, 2, 3) + v2(1, 2, 3, 4) + v2(1, 2, 3, 4, 5, 6, 7, 8, 9, 0) + + def d01(a=1): pass + d01() + d01(1) + d01(*(1,)) + d01(*[] or [2]) + d01(*() or (), *{} and (), **() or {}) + d01(**{'a': 2}) + d01(**{'a': 2} or {}) + def d11(a, b=1): pass + d11(1) + d11(1, 2) + d11(1, **{'b': 2}) + def d21(a, b, c=1): pass + d21(1, 2) + d21(1, 2, 3) + d21(*(1, 2, 3)) + d21(1, *(2, 3)) + d21(1, 2, *(3,)) + d21(1, 2, **{'c': 3}) + def d02(a=1, b=2): pass + d02() + d02(1) + d02(1, 2) + d02(*(1, 2)) + d02(1, *(2,)) + d02(1, **{'b': 2}) + d02(**{'a': 1, 'b': 2}) + def d12(a, b=1, c=2): pass + d12(1) + d12(1, 2) + d12(1, 2, 3) + def d22(a, b, c=1, d=2): pass + d22(1, 2) + d22(1, 2, 3) + d22(1, 2, 3, 4) + def d01v(a=1, *rest): pass + d01v() + d01v(1) + d01v(1, 2) + d01v(*(1, 2, 3, 4)) + d01v(*(1,)) + d01v(**{'a': 2}) + def d11v(a, b=1, *rest): pass + d11v(1) + d11v(1, 2) + d11v(1, 2, 3) + def d21v(a, b, c=1, *rest): pass + d21v(1, 2) + d21v(1, 2, 3) + d21v(1, 2, 3, 4) + d21v(*(1, 2, 3, 4)) + d21v(1, 2, **{'c': 3}) + def d02v(a=1, b=2, *rest): pass + d02v() + d02v(1) + d02v(1, 2) + d02v(1, 2, 3) + d02v(1, *(2, 3, 4)) + d02v(**{'a': 1, 'b': 2}) + def d12v(a, b=1, c=2, *rest): pass + d12v(1) + d12v(1, 2) + d12v(1, 2, 3) + d12v(1, 2, 3, 4) + d12v(*(1, 2, 3, 4)) + d12v(1, 2, *(3, 4, 5)) + d12v(1, *(2,), **{'c': 3}) + def d22v(a, b, c=1, d=2, *rest): pass + d22v(1, 2) + d22v(1, 2, 3) + d22v(1, 2, 3, 4) + d22v(1, 2, 3, 4, 5) + d22v(*(1, 2, 3, 4)) + d22v(1, 2, *(3, 4, 5)) + d22v(1, *(2, 3), **{'d': 4}) + + # keyword argument type tests + try: + str('x', **{b'foo': 1}) + except TypeError: + pass + else: + self.fail('Bytes should not work as keyword argument names') + # keyword only argument tests + def pos0key1(*, key): return key + pos0key1(key=100) + def pos2key2(p1, p2, *, k1, k2=100): return p1, p2, k1, k2 + pos2key2(1, 2, k1=100) + pos2key2(1, 2, k1=100, k2=200) + pos2key2(1, 2, k2=100, k1=200) + def pos2key2dict(p1, p2, *, k1=100, k2, **kwarg): return p1, p2, k1, k2, kwarg + pos2key2dict(1, 2, k2=100, tokwarg1=100, tokwarg2=200) + pos2key2dict(1, 2, tokwarg1=100, tokwarg2=200, k2=100) + + self.assertRaises(SyntaxError, eval, "def f(*): pass") + self.assertRaises(SyntaxError, eval, "def f(*,): pass") + self.assertRaises(SyntaxError, eval, "def f(*, **kwds): pass") + + # keyword arguments after *arglist + def f(*args, **kwargs): + return args, kwargs + self.assertEqual(f(1, x=2, *[3, 4], y=5), ((1, 3, 4), + {'x': 2, 'y': 5})) + self.assertEqual(f(1, *(2, 3), 4), ((1, 2, 3, 4), {})) + self.assertRaises(SyntaxError, eval, "f(1, x=2, *(3,4), x=5)") + self.assertEqual(f(**{'eggs': 'scrambled', 'spam': 'fried'}), + ((), {'eggs': 'scrambled', 'spam': 'fried'})) + self.assertEqual(f(spam='fried', **{'eggs': 'scrambled'}), + ((), {'eggs': 'scrambled', 'spam': 'fried'})) + + # Check ast errors in *args and *kwargs + check_syntax_error(self, "f(*g(1=2))") + check_syntax_error(self, "f(**g(1=2))") + + # argument annotation tests + def f(x) -> list: pass + self.assertEqual(f.__annotations__, {'return': list}) + def f(x: int): pass + self.assertEqual(f.__annotations__, {'x': int}) + def f(*x: str): pass + self.assertEqual(f.__annotations__, {'x': str}) + def f(**x: float): pass + self.assertEqual(f.__annotations__, {'x': float}) + def f(x, y: 1 + 2): pass + self.assertEqual(f.__annotations__, {'y': 3}) + def f(a, b: 1, c: 2, d): pass + self.assertEqual(f.__annotations__, {'b': 1, 'c': 2}) + def f(a, b: 1, c: 2, d, e: 3 = 4, f=5, *g: 6): pass + self.assertEqual(f.__annotations__, + {'b': 1, 'c': 2, 'e': 3, 'g': 6}) + def f(a, b: 1, c: 2, d, e: 3 = 4, f=5, *g: 6, h: 7, i=8, j: 9 = 10, + **k: 11) -> 12: pass + self.assertEqual(f.__annotations__, + {'b': 1, 'c': 2, 'e': 3, 'g': 6, 'h': 7, 'j': 9, + 'k': 11, 'return': 12}) + # Check for issue #20625 -- annotations mangling + class Spam: + def f(self, *, __kw: 1): + pass + class Ham(Spam): pass + self.assertEqual(Spam.f.__annotations__, {'_Spam__kw': 1}) + self.assertEqual(Ham.f.__annotations__, {'_Spam__kw': 1}) + # Check for SF Bug #1697248 - mixing decorators and a return annotation + def null(x): return x + @null + def f(x) -> list: pass + self.assertEqual(f.__annotations__, {'return': list}) + + # test closures with a variety of opargs + closure = 1 + def f(): return closure + def f(x=1): return closure + def f(*, k=1): return closure + def f() -> int: return closure + + # Check trailing commas are permitted in funcdef argument list + def f(a,): pass + def f(*args,): pass + def f(**kwds,): pass + def f(a, *args,): pass + def f(a, **kwds,): pass + def f(*args, b,): pass + def f(*, b,): pass + def f(*args, **kwds,): pass + def f(a, *args, b,): pass + def f(a, *, b,): pass + def f(a, *args, **kwds,): pass + def f(*args, b, **kwds,): pass + def f(*, b, **kwds,): pass + def f(a, *args, b, **kwds,): pass + def f(a, *, b, **kwds,): pass + + def test_lambdef(self): + ### lambdef: 'lambda' [varargslist] ':' test + l1 = lambda: 0 + self.assertEqual(l1(), 0) + l2 = lambda: a[d] # XXX just testing the expression + l3 = lambda: [2 < x for x in [-1, 3, 0]] + self.assertEqual(l3(), [0, 1, 0]) + l4 = lambda x = lambda y = lambda z = 1: z: y(): x() + self.assertEqual(l4(), 1) + l5 = lambda x, y, z=2: x + y + z + self.assertEqual(l5(1, 2), 5) + self.assertEqual(l5(1, 2, 3), 6) + check_syntax_error(self, "lambda x: x = 2") + check_syntax_error(self, "lambda (None,): None") + l6 = lambda x, y, *, k=20: x + y + k + self.assertEqual(l6(1, 2), 1 + 2 + 20) + self.assertEqual(l6(1, 2, k=10), 1 + 2 + 10) + + # check that trailing commas are permitted + l10 = lambda a,: 0 + l11 = lambda *args,: 0 + l12 = lambda **kwds,: 0 + l13 = lambda a, *args,: 0 + l14 = lambda a, **kwds,: 0 + l15 = lambda *args, b,: 0 + l16 = lambda *, b,: 0 + l17 = lambda *args, **kwds,: 0 + l18 = lambda a, *args, b,: 0 + l19 = lambda a, *, b,: 0 + l20 = lambda a, *args, **kwds,: 0 + l21 = lambda *args, b, **kwds,: 0 + l22 = lambda *, b, **kwds,: 0 + l23 = lambda a, *args, b, **kwds,: 0 + l24 = lambda a, *, b, **kwds,: 0 + + + ### stmt: simple_stmt | compound_stmt + # Tested below + + def test_simple_stmt(self): + ### simple_stmt: small_stmt (';' small_stmt)* [';'] + x = 1; pass; del x + def foo(): + # verify statements that end with semi-colons + x = 1; pass; del x; + foo() + + ### small_stmt: expr_stmt | pass_stmt | del_stmt | flow_stmt | import_stmt | global_stmt | access_stmt + # Tested below + + def test_expr_stmt(self): + # (exprlist '=')* exprlist + 1 + 1, 2, 3 + x = 1 + x = 1, 2, 3 + x = y = z = 1, 2, 3 + x, y, z=1, 2, 3 + abc=a, b, c=x, y, z=xyz = 1, 2, (3, 4) + + check_syntax_error(self, "x + 1 = 1") + check_syntax_error(self, "a + 1 = b + 2") + + # Check the heuristic for print & exec covers significant cases + # As well as placing some limits on false positives + def test_former_statements_refer_to_builtins(self): + keywords = "print", "exec" + # Cases where we want the custom error + cases = [ + "{} foo", + "{} {{1:foo}}", + "if 1: {} foo", + "if 1: {} {{1:foo}}", + "if 1:\n {} foo", + "if 1:\n {} {{1:foo}}", + ] + for keyword in keywords: + custom_msg = "call to '{}'".format(keyword) + for case in cases: + source = case.format(keyword) + with self.subTest(source=source): + with self.assertRaisesRegex(SyntaxError, custom_msg): + exec(source) + source = source.replace("foo", "(foo.)") + with self.subTest(source=source): + with self.assertRaisesRegex(SyntaxError, "invalid syntax"): + exec(source) + + def test_del_stmt(self): + # 'del' exprlist + abc = [1, 2, 3] + x, y, z=abc + xyz = x, y, z + + del abc + del x, y, (z, xyz) + + def test_pass_stmt(self): + # 'pass' + pass + + # flow_stmt: break_stmt | continue_stmt | return_stmt | raise_stmt + # Tested below + + def test_break_stmt(self): + # 'break' + while 1: break + + def test_continue_stmt(self): + # 'continue' + i = 1 + while i: i = 0; continue + + msg = "" + while not msg: + msg = "ok" + try: + continue + msg = "continue failed to continue inside try" + except: + msg = "continue inside try called except block" + if msg != "ok": + self.fail(msg) + + msg = "" + while not msg: + msg = "finally block not called" + try: + continue + finally: + msg = "ok" + if msg != "ok": + self.fail(msg) + + def test_break_continue_loop(self): + # This test warrants an explanation. It is a test specifically for SF bugs + # #463359 and #462937. The bug is that a 'break' statement executed or + # exception raised inside a try/except inside a loop, *after* a continue + # statement has been executed in that loop, will cause the wrong number of + # arguments to be popped off the stack and the instruction pointer reset to + # a very small number (usually 0.) Because of this, the following test + # *must* written as a function, and the tracking vars *must* be function + # arguments with default values. Otherwise, the test will loop and loop. + + def test_inner(extra_burning_oil=1, count=0): + big_hippo = 2 + while big_hippo: + count += 1 + try: + if extra_burning_oil and big_hippo == 1: + extra_burning_oil -= 1 + break + big_hippo -= 1 + continue + except: + raise + if count > 2 or big_hippo != 1: + self.fail("continue then break in try/except in loop broken!") + test_inner() + + def test_return(self): + # 'return' [testlist] + def g1(): return + def g2(): return 1 + g1() + x = g2() + check_syntax_error(self, "class foo:return 1") + + def test_break_in_finally(self): + count = 0 + while count < 2: + count += 1 + try: + pass + finally: + break + self.assertEqual(count, 1) + + count = 0 + while count < 2: + count += 1 + try: + continue + finally: + break + self.assertEqual(count, 1) + + count = 0 + while count < 2: + count += 1 + try: + 1 / 0 + finally: + break + self.assertEqual(count, 1) + + for count in [0, 1]: + self.assertEqual(count, 0) + try: + pass + finally: + break + self.assertEqual(count, 0) + + for count in [0, 1]: + self.assertEqual(count, 0) + try: + continue + finally: + break + self.assertEqual(count, 0) + + for count in [0, 1]: + self.assertEqual(count, 0) + try: + 1 / 0 + finally: + break + self.assertEqual(count, 0) + + def test_continue_in_finally(self): + count = 0 + while count < 2: + count += 1 + try: + pass + finally: + continue + break + self.assertEqual(count, 2) + + count = 0 + while count < 2: + count += 1 + try: + break + finally: + continue + self.assertEqual(count, 2) + + count = 0 + while count < 2: + count += 1 + try: + 1 / 0 + finally: + continue + break + self.assertEqual(count, 2) + + for count in [0, 1]: + try: + pass + finally: + continue + break + self.assertEqual(count, 1) + + for count in [0, 1]: + try: + break + finally: + continue + self.assertEqual(count, 1) + + for count in [0, 1]: + try: + 1 / 0 + finally: + continue + break + self.assertEqual(count, 1) + + def test_return_in_finally(self): + def g1(): + try: + pass + finally: + return 1 + self.assertEqual(g1(), 1) + + def g2(): + try: + return 2 + finally: + return 3 + self.assertEqual(g2(), 3) + + def g3(): + try: + 1 / 0 + finally: + return 4 + self.assertEqual(g3(), 4) + + def test_yield(self): + # Allowed as standalone statement + def g(): yield 1 + def g(): yield from () + # Allowed as RHS of assignment + def g(): x = yield 1 + def g(): x = yield from () + # Ordinary yield accepts implicit tuples + def g(): yield 1, 1 + def g(): x = yield 1, 1 + # 'yield from' does not + check_syntax_error(self, "def g(): yield from (), 1") + check_syntax_error(self, "def g(): x = yield from (), 1") + # Requires parentheses as subexpression + def g(): 1, (yield 1) + def g(): 1, (yield from ()) + check_syntax_error(self, "def g(): 1, yield 1") + check_syntax_error(self, "def g(): 1, yield from ()") + # Requires parentheses as call argument + def g(): f((yield 1)) + def g(): f((yield 1), 1) + def g(): f((yield from ())) + def g(): f((yield from ()), 1) + check_syntax_error(self, "def g(): f(yield 1)") + check_syntax_error(self, "def g(): f(yield 1, 1)") + check_syntax_error(self, "def g(): f(yield from ())") + check_syntax_error(self, "def g(): f(yield from (), 1)") + # Not allowed at top level + check_syntax_error(self, "yield") + check_syntax_error(self, "yield from") + # Not allowed at class scope + check_syntax_error(self, "class foo:yield 1") + check_syntax_error(self, "class foo:yield from ()") + # Check annotation refleak on SyntaxError + check_syntax_error(self, "def g(a:(yield)): pass") + + def test_yield_in_comprehensions(self): + # Check yield in comprehensions + def g(): [x for x in [(yield 1)]] + def g(): [x for x in [(yield from ())]] + + check = self.check_syntax_error + check("def g(): [(yield x) for x in ()]", + "'yield' inside list comprehension") + check("def g(): [x for x in () if not (yield x)]", + "'yield' inside list comprehension") + check("def g(): [y for x in () for y in [(yield x)]]", + "'yield' inside list comprehension") + check("def g(): {(yield x) for x in ()}", + "'yield' inside set comprehension") + check("def g(): {(yield x): x for x in ()}", + "'yield' inside dict comprehension") + check("def g(): {x: (yield x) for x in ()}", + "'yield' inside dict comprehension") + check("def g(): ((yield x) for x in ())", + "'yield' inside generator expression") + check("def g(): [(yield from x) for x in ()]", + "'yield' inside list comprehension") + check("class C: [(yield x) for x in ()]", + "'yield' inside list comprehension") + check("[(yield x) for x in ()]", + "'yield' inside list comprehension") + + def test_raise(self): + # 'raise' test [',' test] + try: raise RuntimeError('just testing') + except RuntimeError: pass + try: raise KeyboardInterrupt + except KeyboardInterrupt: pass + + def test_import(self): + # 'import' dotted_as_names + import sys + import time, sys + # 'from' dotted_name 'import' ('*' | '(' import_as_names ')' | import_as_names) + from time import time + from time import (time) + # not testable inside a function, but already done at top of the module + # from sys import * + from sys import path, argv + from sys import (path, argv) + from sys import (path, argv,) + + def test_global(self): + # 'global' NAME (',' NAME)* + global a + global a, b + global one, two, three, four, five, six, seven, eight, nine, ten + + def test_nonlocal(self): + # 'nonlocal' NAME (',' NAME)* + x = 0 + y = 0 + def f(): + nonlocal x + nonlocal x, y + + def test_assert(self): + # assertTruestmt: 'assert' test [',' test] + assert 1 + assert 1, 1 + assert lambda x: x + assert 1, lambda x: x + 1 + + try: + assert True + except AssertionError as e: + self.fail("'assert True' should not have raised an AssertionError") + + try: + assert True, 'this should always pass' + except AssertionError as e: + self.fail("'assert True, msg' should not have " + "raised an AssertionError") + + # these tests fail if python is run with -O, so check __debug__ + @unittest.skipUnless(__debug__, "Won't work if __debug__ is False") + def testAssert2(self): + try: + assert 0, "msg" + except AssertionError as e: + self.assertEqual(e.args[0], "msg") + else: + self.fail("AssertionError not raised by assert 0") + + try: + assert False + except AssertionError as e: + self.assertEqual(len(e.args), 0) + else: + self.fail("AssertionError not raised by 'assert False'") + + + ### compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | funcdef | classdef + # Tested below + + def test_if(self): + # 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite] + if 1: pass + if 1: pass + else: pass + if 0: pass + elif 0: pass + if 0: pass + elif 0: pass + elif 0: pass + elif 0: pass + else: pass + + def test_while(self): + # 'while' test ':' suite ['else' ':' suite] + while 0: pass + while 0: pass + else: pass + + # Issue1920: "while 0" is optimized away, + # ensure that the "else" clause is still present. + x = 0 + while 0: + x = 1 + else: + x = 2 + self.assertEqual(x, 2) + + def test_for(self): + # 'for' exprlist 'in' exprlist ':' suite ['else' ':' suite] + for i in 1, 2, 3: pass + for i, j, k in (): pass + else: pass + class Squares: + def __init__(self, max): + self.max = max + self.sofar = [] + def __len__(self): return len(self.sofar) + def __getitem__(self, i): + if not 0 <= i < self.max: raise IndexError + n = len(self.sofar) + while n <= i: + self.sofar.append(n * n) + n = n + 1 + return self.sofar[i] + n = 0 + for x in Squares(10): n = n + x + if n != 285: + self.fail('for over growing sequence') + + result = [] + for x, in [(1,), (2,), (3,)]: + result.append(x) + self.assertEqual(result, [1, 2, 3]) + + def test_try(self): + ### try_stmt: 'try' ':' suite (except_clause ':' suite)+ ['else' ':' suite] + ### | 'try' ':' suite 'finally' ':' suite + ### except_clause: 'except' [expr ['as' expr]] + try: + 1 / 0 + except ZeroDivisionError: + pass + else: + pass + try: 1 / 0 + except EOFError: pass + except TypeError as msg: pass + except: pass + else: pass + try: 1 / 0 + except (EOFError, TypeError, ZeroDivisionError): pass + try: 1 / 0 + except (EOFError, TypeError, ZeroDivisionError) as msg: pass + try: pass + finally: pass + + def test_suite(self): + # simple_stmt | NEWLINE INDENT NEWLINE* (stmt NEWLINE*)+ DEDENT + if 1: pass + if 1: + pass + if 1: + # + # + # + pass + pass + # + pass + # + + def test_test(self): + ### and_test ('or' and_test)* + ### and_test: not_test ('and' not_test)* + ### not_test: 'not' not_test | comparison + if not 1: pass + if 1 and 1: pass + if 1 or 1: pass + if not not not 1: pass + if not 1 and 1 and 1: pass + if 1 and 1 or 1 and 1 and 1 or not 1 and 1: pass + + def test_comparison(self): + ### comparison: expr (comp_op expr)* + ### comp_op: '<'|'>'|'=='|'>='|'<='|'!='|'in'|'not' 'in'|'is'|'is' 'not' + if 1: pass + x = (1 == 1) + if 1 == 1: pass + if 1 != 1: pass + if 1 < 1: pass + if 1 > 1: pass + if 1 <= 1: pass + if 1 >= 1: pass + if 1 is 1: pass + if 1 is not 1: pass + if 1 in (): pass + if 1 not in (): pass + if 1 < 1 > 1 == 1 >= 1 <= 1 != 1 in 1 not in 1 is 1 is not 1: pass + + def test_binary_mask_ops(self): + x = 1 & 1 + x = 1 ^ 1 + x = 1 | 1 + + def test_shift_ops(self): + x = 1 << 1 + x = 1 >> 1 + x = 1 << 1 >> 1 + + def test_additive_ops(self): + x = 1 + x = 1 + 1 + x = 1 - 1 - 1 + x = 1 - 1 + 1 - 1 + 1 + + def test_multiplicative_ops(self): + x = 1 * 1 + x = 1 / 1 + x = 1 % 1 + x = 1 / 1 * 1 % 1 + + def test_unary_ops(self): + x = +1 + x = -1 + x = ~1 + x = ~1 ^ 1 & 1 | 1 & 1 ^ -1 + x = -1 * 1 / 1 + 1 * 1 - -1 * 1 + + def test_selectors(self): + ### trailer: '(' [testlist] ')' | '[' subscript ']' | '.' NAME + ### subscript: expr | [expr] ':' [expr] + + import sys, time + c = sys.path[0] + x = time.time() + x = sys.modules['time'].time() + a = '01234' + c = a[0] + c = a[-1] + s = a[0:5] + s = a[:5] + s = a[0:] + s = a[:] + s = a[-5:] + s = a[:-1] + s = a[-4:-3] + # A rough test of SF bug 1333982. http://python.org/sf/1333982 + # The testing here is fairly incomplete. + # Test cases should include: commas with 1 and 2 colons + d = {} + d[1] = 1 + d[1,] = 2 + d[1, 2] = 3 + d[1, 2, 3] = 4 + L = list(d) + L.sort(key=lambda x: (type(x).__name__, x)) + self.assertEqual(str(L), '[1, (1,), (1, 2), (1, 2, 3)]') + + def test_atoms(self): + ### atom: '(' [testlist] ')' | '[' [testlist] ']' | '{' [dictsetmaker] '}' | NAME | NUMBER | STRING + ### dictsetmaker: (test ':' test (',' test ':' test)* [',']) | (test (',' test)* [',']) + + x = (1) + x = (1 or 2 or 3) + x = (1 or 2 or 3, 2, 3) + + x = [] + x = [1] + x = [1 or 2 or 3] + x = [1 or 2 or 3, 2, 3] + x = [] + + x = {} + x = {'one': 1} + x = {'one': 1,} + x = {'one' or 'two': 1 or 2} + x = {'one': 1, 'two': 2} + x = {'one': 1, 'two': 2,} + x = {'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6} + + x = {'one'} + x = {'one', 1,} + x = {'one', 'two', 'three'} + x = {2, 3, 4,} + + x = x + x = 'x' + x = 123 + + ### exprlist: expr (',' expr)* [','] + ### testlist: test (',' test)* [','] + # These have been exercised enough above + + def test_classdef(self): + # 'class' NAME ['(' [testlist] ')'] ':' suite + class B: pass + class B2(): pass + class C1(B): pass + class C2(B): pass + class D(C1, C2, B): pass + class C: + def meth1(self): pass + def meth2(self, arg): pass + def meth3(self, a1, a2): pass + + # decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE + # decorators: decorator+ + # decorated: decorators (classdef | funcdef) + def class_decorator(x): return x + @class_decorator + class G: pass + + def test_dictcomps(self): + # dictorsetmaker: ( (test ':' test (comp_for | + # (',' test ':' test)* [','])) | + # (test (comp_for | (',' test)* [','])) ) + nums = [1, 2, 3] + self.assertEqual({i: i + 1 for i in nums}, {1: 2, 2: 3, 3: 4}) + + def test_listcomps(self): + # list comprehension tests + nums = [1, 2, 3, 4, 5] + strs = ["Apple", "Banana", "Coconut"] + spcs = [" Apple", " Banana ", "Coco nut "] + + self.assertEqual([s.strip() for s in spcs], ['Apple', 'Banana', 'Coco nut']) + self.assertEqual([3 * x for x in nums], [3, 6, 9, 12, 15]) + self.assertEqual([x for x in nums if x > 2], [3, 4, 5]) + self.assertEqual([(i, s) for i in nums for s in strs], + [(1, 'Apple'), (1, 'Banana'), (1, 'Coconut'), + (2, 'Apple'), (2, 'Banana'), (2, 'Coconut'), + (3, 'Apple'), (3, 'Banana'), (3, 'Coconut'), + (4, 'Apple'), (4, 'Banana'), (4, 'Coconut'), + (5, 'Apple'), (5, 'Banana'), (5, 'Coconut')]) + self.assertEqual([(i, s) for i in nums for s in [f for f in strs if "n" in f]], + [(1, 'Banana'), (1, 'Coconut'), (2, 'Banana'), (2, 'Coconut'), + (3, 'Banana'), (3, 'Coconut'), (4, 'Banana'), (4, 'Coconut'), + (5, 'Banana'), (5, 'Coconut')]) + self.assertEqual([(lambda a:[a ** i for i in range(a + 1)])(j) for j in range(5)], + [[1], [1, 1], [1, 2, 4], [1, 3, 9, 27], [1, 4, 16, 64, 256]]) + + def test_in_func(l): + return [0 < x < 3 for x in l if x > 2] + + self.assertEqual(test_in_func(nums), [False, False, False]) + + def test_nested_front(): + self.assertEqual([[y for y in [x, x + 1]] for x in [1, 3, 5]], + [[1, 2], [3, 4], [5, 6]]) + + test_nested_front() + + check_syntax_error(self, "[i, s for i in nums for s in strs]") + check_syntax_error(self, "[x if y]") + + suppliers = [ + (1, "Boeing"), + (2, "Ford"), + (3, "Macdonalds") + ] + + parts = [ + (10, "Airliner"), + (20, "Engine"), + (30, "Cheeseburger") + ] + + suppart = [ + (1, 10), (1, 20), (2, 20), (3, 30) + ] + + x = [ + (sname, pname) + for (sno, sname) in suppliers + for (pno, pname) in parts + for (sp_sno, sp_pno) in suppart + if sno == sp_sno and pno == sp_pno + ] + + self.assertEqual(x, [('Boeing', 'Airliner'), ('Boeing', 'Engine'), ('Ford', 'Engine'), + ('Macdonalds', 'Cheeseburger')]) + + def test_genexps(self): + # generator expression tests + g = ([x for x in range(10)] for x in range(1)) + self.assertEqual(next(g), [x for x in range(10)]) + try: + next(g) + self.fail('should produce StopIteration exception') + except StopIteration: + pass + + a = 1 + try: + g = (a for d in a) + next(g) + self.fail('should produce TypeError') + except TypeError: + pass + + self.assertEqual(list((x, y) for x in 'abcd' for y in 'abcd'), [(x, y) for x in 'abcd' for y in 'abcd']) + self.assertEqual(list((x, y) for x in 'ab' for y in 'xy'), [(x, y) for x in 'ab' for y in 'xy']) + + a = [x for x in range(10)] + b = (x for x in (y for y in a)) + self.assertEqual(sum(b), sum([x for x in range(10)])) + + self.assertEqual(sum(x ** 2 for x in range(10)), sum([x ** 2 for x in range(10)])) + self.assertEqual(sum(x * x for x in range(10) if x % 2), sum([x * x for x in range(10) if x % 2])) + self.assertEqual(sum(x for x in (y for y in range(10))), sum([x for x in range(10)])) + self.assertEqual(sum(x for x in (y for y in (z for z in range(10)))), sum([x for x in range(10)])) + self.assertEqual(sum(x for x in [y for y in (z for z in range(10))]), sum([x for x in range(10)])) + self.assertEqual(sum(x for x in (y for y in (z for z in range(10) if True)) if True), sum([x for x in range(10)])) + self.assertEqual(sum(x for x in (y for y in (z for z in range(10) if True) if False) if True), 0) + check_syntax_error(self, "foo(x for x in range(10), 100)") + check_syntax_error(self, "foo(100, x for x in range(10))") + + def test_comprehension_specials(self): + # test for outmost iterable precomputation + x = 10; g = (i for i in range(x)); x = 5 + self.assertEqual(len(list(g)), 10) + + # This should hold, since we're only precomputing outmost iterable. + x = 10; t = False; g = ((i, j) for i in range(x) if t for j in range(x)) + x = 5; t = True; + self.assertEqual([(i, j) for i in range(10) for j in range(5)], list(g)) + + # Grammar allows multiple adjacent 'if's in listcomps and genexps, + # even though it's silly. Make sure it works (ifelse broke this.) + self.assertEqual([x for x in range(10) if x % 2 if x % 3], [1, 5, 7]) + self.assertEqual(list(x for x in range(10) if x % 2 if x % 3), [1, 5, 7]) + + # verify unpacking single element tuples in listcomp/genexp. + self.assertEqual([x for x, in [(4,), (5,), (6,)]], [4, 5, 6]) + self.assertEqual(list(x for x, in [(7,), (8,), (9,)]), [7, 8, 9]) + + def test_with_statement(self): + class manager(object): + def __enter__(self): + return (1, 2) + def __exit__(self, *args): + pass + + with manager(): + pass + with manager() as x: + pass + with manager() as (x, y): + pass + with manager(), manager(): + pass + with manager() as x, manager() as y: + pass + with manager() as x, manager(): + pass + + def test_if_else_expr(self): + # Test ifelse expressions in various cases + def _checkeval(msg, ret): + "helper to check that evaluation of expressions is done correctly" + print(msg) + return ret + + # the next line is not allowed anymore + #self.assertEqual([ x() for x in lambda: True, lambda: False if x() ], [True]) + self.assertEqual([x() for x in (lambda:True, lambda:False) if x()], [True]) + self.assertEqual([x(False) for x in (lambda x:False if x else True, lambda x:True if x else False) if x(False)], [True]) + self.assertEqual((5 if 1 else _checkeval("check 1", 0)), 5) + self.assertEqual((_checkeval("check 2", 0) if 0 else 5), 5) + self.assertEqual((5 and 6 if 0 else 1), 1) + self.assertEqual(((5 and 6) if 0 else 1), 1) + self.assertEqual((5 and (6 if 1 else 1)), 6) + self.assertEqual((0 or _checkeval("check 3", 2) if 0 else 3), 3) + self.assertEqual((1 or _checkeval("check 4", 2) if 1 else _checkeval("check 5", 3)), 1) + self.assertEqual((0 or 5 if 1 else _checkeval("check 6", 3)), 5) + self.assertEqual((not 5 if 1 else 1), False) + self.assertEqual((not 5 if 0 else 1), 1) + self.assertEqual((6 + 1 if 1 else 2), 7) + self.assertEqual((6 - 1 if 1 else 2), 5) + self.assertEqual((6 * 2 if 1 else 4), 12) + self.assertEqual((6 / 2 if 1 else 3), 3) + self.assertEqual((6 < 4 if 0 else 2), 2) + + def test_paren_evaluation(self): + self.assertEqual(16 // (4 // 2), 8) + self.assertEqual((16 // 4) // 2, 2) + self.assertEqual(16 // 4 // 2, 2) + self.assertTrue(False is (2 is 3)) + self.assertFalse((False is 2) is 3) + self.assertFalse(False is 2 is 3) + + def test_matrix_mul(self): + # This is not intended to be a comprehensive test, rather just to be few + # samples of the @ operator in test_grammar.py. + class M: + def __matmul__(self, o): + return 4 + def __imatmul__(self, o): + self.other = o + return self + m = M() + self.assertEqual(m@m, 4) + m @= 42 + self.assertEqual(m.other, 42) + + def test_async_await(self): + async def test(): + def sum(): + pass + if 1: + await someobj() + + self.assertEqual(test.__name__, 'test') + self.assertTrue(bool(test.__code__.co_flags & inspect.CO_COROUTINE)) + + def decorator(func): + setattr(func, '_marked', True) + return func + + @decorator + async def test2(): + return 22 + self.assertTrue(test2._marked) + self.assertEqual(test2.__name__, 'test2') + self.assertTrue(bool(test2.__code__.co_flags & inspect.CO_COROUTINE)) + + def test_async_for(self): + class Done(Exception): pass + + class AIter: + def __aiter__(self): + return self + async def __anext__(self): + raise StopAsyncIteration + + async def foo(): + async for i in AIter(): + pass + async for i, j in AIter(): + pass + async for i in AIter(): + pass + else: + pass + raise Done + + with self.assertRaises(Done): + foo().send(None) + + def test_async_with(self): + class Done(Exception): pass + + class manager: + async def __aenter__(self): + return (1, 2) + async def __aexit__(self, *exc): + return False + + async def foo(): + async with manager(): + pass + async with manager() as x: + pass + async with manager() as (x, y): + pass + async with manager(), manager(): + pass + async with manager() as x, manager() as y: + pass + async with manager() as x, manager(): + pass + raise Done + + with self.assertRaises(Done): + foo().send(None) + + +if __name__ == '__main__': + unittest.main() diff --git a/src/test/signature/signature.ptvs.test.ts b/src/test/signature/signature.ptvs.test.ts index ad8e58508342..8e2f630756d4 100644 --- a/src/test/signature/signature.ptvs.test.ts +++ b/src/test/signature/signature.ptvs.test.ts @@ -79,8 +79,7 @@ suite('Signatures (Analysis Engine)', () => { new SignatureHelpResult(0, 8, 1, 1, 'stop'), new SignatureHelpResult(0, 9, 1, 1, 'stop'), new SignatureHelpResult(0, 10, 1, 1, 'stop'), - new SignatureHelpResult(0, 11, 1, 2, 'step'), - new SignatureHelpResult(1, 0, 1, 2, 'step') + new SignatureHelpResult(0, 11, 1, 2, 'step') ]; const document = await openDocument(path.join(autoCompPath, 'basicSig.py')); From bcb67903142490e6c4e2ad2b29bbec8319c0054e Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 23 Apr 2018 13:25:29 -0700 Subject: [PATCH 144/433] Enable retries remote debugging unit tests --- src/test/debugger/attach.ptvsd.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/debugger/attach.ptvsd.test.ts b/src/test/debugger/attach.ptvsd.test.ts index fd67270f7edc..ddce60e21ab0 100644 --- a/src/test/debugger/attach.ptvsd.test.ts +++ b/src/test/debugger/attach.ptvsd.test.ts @@ -125,8 +125,7 @@ suite('Attach Debugger - Experimental', () => { debugClient.waitForEvent('terminated') ]); } - test('Confirm we are able to attach to a running program', async function () { - this.retries(0); + test('Confirm we are able to attach to a running program', async () => { await testAttachingToRemoteProcess(path.dirname(fileToDebug), path.dirname(fileToDebug), IS_WINDOWS); }); test('Confirm local and remote paths are translated', async () => { From 27643e645ea182e3e14e287dcd649756acc9b3ae Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 23 Apr 2018 13:43:32 -0700 Subject: [PATCH 145/433] Test plan template --- .github/test_plan.md | 224 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 .github/test_plan.md diff --git a/.github/test_plan.md b/.github/test_plan.md new file mode 100644 index 000000000000..deae21ca34ba --- /dev/null +++ b/.github/test_plan.md @@ -0,0 +1,224 @@ +# Test plan + +## Environment + +- OS: XXX +- Python + - Distribution: XXX + - Version: XXX + +## Tests + +**ALWAYS check the `Output` window under `Python` for logged errors!** + +### [Environment](https://code.visualstudio.com/docs/python/environments) +#### Interpreters + +- [ ] Interpreter is [shown in the status bar](https://code.visualstudio.com/docs/python/environments#_choosing-an-environment) +- [ ] An interpreter can be manually specified using the [`Select Interpreter` command](https://code.visualstudio.com/docs/python/environments#_choosing-an-environment) +- [ ] Detected system-installed interpreters +- [ ] Detected an Anaconda installation +- [ ] (Linux/macOS) Detected all interpreters installed w/ [pyenv](https://github.com/pyenv/pyenv) detected +- [ ] [`"python.pythonPath"`](https://code.visualstudio.com/docs/python/environments#_manually-specifying-an-interpreter) triggers an update in the status bar +- [ ] `Run Python File in Terminal` +- [ ] `Run Selection/Line in Python Terminal` + +#### Virtual environments + +**ALWAYS create environments with a space in their name.*** + +- [ ] Detected a single virtual environment at the top-level of the workspace folder + - [ ] Appropriate suffix label specified in status bar + - [ ] Prompt to install Pylint uses `--user` + - [ ] `"python.terminal.activateEnvironments": false` deactivates detection + - [ ] [`Create Terminal`](https://code.visualstudio.com/docs/python/environments#_activating-an-environment-in-the-terminal) works +- [ ] Detect multiple virtual environments in a directory specified by `"python.venvPath"` +- [ ] Detected all [conda environments created with an interpreter](https://code.visualstudio.com/docs/python/environments#_conda-environments) + - [ ] Appropriate suffix label specified in status bar + - [ ] Prompt to install Pylint installs into the conda environment + - [ ] `"python.terminal.activateEnvironments": false` deactivates detection + - [ ] [`Create Terminal`](https://code.visualstudio.com/docs/python/environments#_activating-an-environment-in-the-terminal) works +- [ ] (Linux/macOS until [`-m` is supported](https://github.com/Microsoft/vscode-python/issues/978)) Detected the virtual environment created by [pipenv](https://docs.pipenv.org/) + - [ ] Appropriate suffix label specified in status bar + - [ ] Prompt to install Pylint uses `pipenv install --dev` + - [ ] `"python.terminal.activateEnvironments": false` deactivates detection + - [ ] [`Create Terminal`](https://code.visualstudio.com/docs/python/environments#_activating-an-environment-in-the-terminal) works +- [ ] (Linux/macOS) Detected virtual environments created under `{workspaceFolder}/.direnv/python-{python_version}` for [direnv](https://direnv.net/) and its [`layout python3`](https://github.com/direnv/direnv/blob/master/stdlib.sh) support + - [ ] Appropriate suffix label specified in status bar + - [ ] `"python.terminal.activateEnvironments": false` deactivates detection + +#### [Environment files](https://code.visualstudio.com/docs/python/environments#_environment-variable-definitions-file) +Sample files: +```python3 +# example.py +import os +print('Hello,', os.environ.get('WHO'), '!') +``` +``` +# .env +WHO=world +``` + +- [ ] Environment variables in a `.env` file are exposed when running under the debugger +- [ ] `"python.envFile"` allows for specifying an environment file manually + +#### [Debugging](https://code.visualstudio.com/docs/python/environments#_python-interpreter-for-debugging) + +- [ ] `pythonPath` setting in your `launch.json` overrides your `python.pythonPath` default setting + +### [Linting](https://code.visualstudio.com/docs/python/linting) + +**ALWAYS check under the `Problems` tab to see e.g. if a linter is raising errors!** + +#### Pylint/default linting +[Prompting to install Pylint is covered under `Environments` above] + +For testing the disablement of the default linting rules for Pylint: +```ini +# pylintrc +[MESSAGES CONTROL] +enable=bad-names +``` +```python3 +# example.py +foo = 42 # Marked as a blacklisted name. +``` +- [ ] Installation via the prompt installs Pylint as appropriate +- [ ] Pylint works +- [ ] `"python.linting.pylintUseMinimalCheckers": false` turns off the default rules w/ no `pylintrc` file present +- [ ] The existense of a `pylintrc` file turns off the default rules + +#### Other linters + +- [ ] flake8 works +- [ ] mypy works +- [ ] pydocstyle works +- [ ] pep8 works +- [ ] prospector works +- [ ] pylama works +- [ ] 3 or more linters work simultaneously + - [ ] `Run Linting` runs all linters + - [ ] The `Select Linter` command lists all the above linters and prompts to install a linter when missing + - [ ] `"python.linting.enabled"` disables all linters + - [ ] The `Enable Linting` command changes `"python.linting.enabled"` + - [ ] `"python.linting.lintOnSave` works + +### [Editing](https://code.visualstudio.com/docs/python/editing) + +#### [IntelliSense](https://code.visualstudio.com/docs/python/editing#_autocomplete-and-intellisense) + +Please also test for general accuracy on the most "interesting" code you can find. + +- [ ] `"python.autoComplete.extraPaths"` works +- [ ] `"python.autoComplete.preloadModules"` works +- [ ] `"python.autocomplete.addBrackets": true` causes auto-completion of functions to append `()` + +#### [Formatting](https://code.visualstudio.com/docs/python/editing#_formatting) + +- [ ] autopep8 works +- [ ] yapf works +- [ ] `"editor.formatOnType": true` works and has expected results + +#### [Refactoring](https://code.visualstudio.com/docs/python/editing#_refactoring) + +- [ ] [`Extract Variable`](https://code.visualstudio.com/docs/python/editing#_extract-variable) works +- [ ] [`Extract method`](https://code.visualstudio.com/docs/python/editing#_extract-method) works +- [ ] [`Sort Imports`](https://code.visualstudio.com/docs/python/editing#_sort-imports) works + +### [Debugging](https://code.visualstudio.com/docs/python/debugging) + +Test **both** old and new debugger (and notice if the new debugger seems _at least_ as fast as the old debugger). + +- [ ] [Configurations](https://code.visualstudio.com/docs/python/debugging#_debugging-specific-app-types) work + - [ ] `Current File` + - [ ] `Module` + - [ ] `Attach` + - [ ] `Terminal (integrated)` + - [ ] `Terminal (external)` + - [ ] `Django` + - [ ] `Flask` + - [ ] `Pyramid` + - [ ] `Watson` + - [ ] `Scrapy` + - [ ] `PySpark` + - [ ] `All debug Options` with [appropriate values](https://code.visualstudio.com/docs/python/debugging#_standard-configuration-and-options) changed +- [ ] Breakpoints + - [ ] Set + - [ ] Hit + - [ ] Watch +- [ ] Stepping + - [ ] Over + - [ ] Into + - [ ] Out +- [ ] Can inspect variables + - [ ] Through hovering over variable in code + - [ ] `Variables` section of debugger sidebar +- [ ] [Remote debugging](https://code.visualstudio.com/docs/python/debugging#_remote-debugging) works + - [ ] ... over SSH +- [ ] [App Engine](https://code.visualstudio.com/docs/python/debugging#_google-app-engine-debugging) + +### [Unit testing](https://code.visualstudio.com/docs/python/unit-testing) + +#### [`unittest`](https://code.visualstudio.com/docs/python/unit-testing#_unittest-configuration-settings) +```python +import unittest + +class PassingTests(unittest.TestCase): + + def test_passing(self): + self.assertEqual(42, 42) + + def test_passing_still(self): + self.assertEqual("silly walk", "silly walk") + + +class FailingTests(unittest.TestCase): + + def test_failure(self): + self.assertEqual(42, -13) + + def test_failure_still(self): + self.assertEqual("I'm right!", "no, I am!") +``` +- [ ] `Run All Unit Tests` triggers the prompt to configure the test runner +- [ ] Tests are discovered (as shown by code lenses on each test) + +#### [`pytest`](https://code.visualstudio.com/docs/python/unit-testing#_pytest-configuration-settings) +```python +def test_passing(): + assert 42 == 42 + +def test_failure(): + assert 42 == -13 +``` + +- [ ] `Run All Unit Tests` triggers the prompt to configure the test runner + - [ ] Pytest gets installed +- [ ] Tests are discovered (as shown by code lenses on each test) + +#### [`nose`](https://code.visualstudio.com/docs/python/unit-testing#_nose-configuration-settings) +```python +def test_passing(): + assert 42 == 42 + +def test_failure(): + assert 42 == -13 +``` + +- [ ] `Run All Unit Tests` triggers the prompt to configure the test runner + - [ ] Nose gets installed +- [ ] Tests are discovered (as shown by code lenses on each test) + +#### General + +- [ ] Code lenses appears + - [ ] `Run Test` lens works (and status bar updates as appropriate) + - [ ] `Debug Test` lens works + - [ ] Appropriate ✔/❌ shown for each test +- [ ] Status bar is functioning + - [ ] Appropriate test results displayed + - [ ] `Run All Unit Tests` works + - [ ] `Discover Unit Tests` works (resets tests result display in status bar) + - [ ] `Run Unit Test Method ...` works + - [ ] `View Unit Test Output` works + - [ ] After having at least one failure, `Run Failed Tests` works From 206ef32b31564ec4e6f0e250197ff33f7b78dc48 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 23 Apr 2018 13:51:27 -0700 Subject: [PATCH 146/433] Prep for beta (#1453) --- CHANGELOG.md | 88 ++++++++++++++++++++++++++++++++++++++ news/2 Fixes/1033.md | 2 +- news/2 Fixes/1254.md | 2 +- news/3 Code Health/1253.md | 2 +- package.json | 2 +- 5 files changed, 92 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0494063d280b..afa0e8fc686c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,93 @@ # Changelog +## 2018.4.0-beta (23 Mar 2018) + +Thanks to the following projects which we fully rely on to provide some of +our features: +- [jedi 0.12.0](https://pypi.org/project/jedi/0.12.0/) + and [parso 0.2.0](https://pypi.org/project/parso/0.2.0/) +- [isort 4.2.15](https://pypi.org/project/isort/4.2.15/) +- [rope](https://pypi.org/project/rope/) (user-installed) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) + +And a special thanks to [Patryk Zawadzki](https://github.com/patrys) for all of +his help on [our issue tracker](https://github.com/Microsoft/vscode-python)! + +### Enhancements + +1. Add prelimnary support for remote debugging using the experimental debugger. +Attach to a Python program started using the command `python -m ptvsd --server --port 9091 --file pythonFile.py` ([#1229](https://github.com/Microsoft/vscode-python/issues/1229)) +1. Add support for [logpoints](https://code.visualstudio.com/docs/editor/debugging#_logpoints) in the experimental debugger. + ([#1306](https://github.com/Microsoft/vscode-python/issues/1306)) +1. Set focus to the terminal upon creation of a terminal using the `Python: Create Terminal` command. + ([#1315](https://github.com/Microsoft/vscode-python/issues/1315)) +1. Added support for source references (remote debugging without having the source code locally) in the experimental debugger. + ([#1333](https://github.com/Microsoft/vscode-python/issues/1333)) +1. Settings configured within the `debugOptions` property of `launch.json` for the old debugger are now defined as individual (boolean) properties in the new experimental debugger (e.g. `"debugOptions": ["RedirectOutput"]` becomes `"redirectOutput": true`). + ([#1395](https://github.com/Microsoft/vscode-python/issues/1395)) +1. Intergrate Jedi 0.12. See https://github.com/davidhalter/jedi/issues/1063#issuecomment-381417297 for details. ([#1400](https://github.com/Microsoft/vscode-python/issues/1400)) +1. Add prelimnary support for remote debugging using the experimental debugger. ([#907](https://github.com/Microsoft/vscode-python/issues/907)) + Attach to a Python program after having imported `ptvsd` and enabling the debugger to attach as follows: + ```python + import ptvsd + ptvsd.enable_attach(('0.0.0.0', 5678)) + ``` + Additional capabilities: + * `ptvsd.break_into_debugger()` to break into the attached debugger. + * `ptvsd.wait_for_attach(timeout)` to cause the program to wait untill a debugger attaches. + * `ptvsd.is_attached()` to determine whether a debugger is attached to the program. + +### Fixes + +1. Use an existing method to identify the active interpreter. ([#1015](https://github.com/Microsoft/vscode-python/issues/1015)) +1. Fix go to definition functionality across files. ([#1033](https://github.com/Microsoft/vscode-python/issues/1033)) +1. IntelliSense under Python 2 for inherited attributes works again (thanks to an upgraded Jedi). + ([#1072](https://github.com/Microsoft/vscode-python/issues/1072)) +1. Reverted change that ended up considering symlinked interpreters as duplicate interpreter. + ([#1192](https://github.com/Microsoft/vscode-python/issues/1192)) +1. Display errors returned by the PipEnv command when identifying the corresonding environment. + ([#1254](https://github.com/Microsoft/vscode-python/issues/1254)) +1. When `editor.formatOnType` is on, don't add a space for `*args` or `**kwargs` + ([#1257](https://github.com/Microsoft/vscode-python/issues/1257)) +1. When `editor.formatOnType` is on, don't add a space between a string type specifier and the string literal + ([#1257](https://github.com/Microsoft/vscode-python/issues/1257)) +1. Ensure interpreter file exists on the file system before including into list of interpreters. + ([#1305](https://github.com/Microsoft/vscode-python/issues/1305)) +1. Do not have the formatter consider single-quoted string multiline even if it is not terminated. + ([#1364](https://github.com/Microsoft/vscode-python/issues/1364)) +1. IntelliSense works in module-level `if` statements (thanks to Jedi 0.12.0 upgrade). + ([#142](https://github.com/Microsoft/vscode-python/issues/142)) +1. IntelliSense works appropriately when a project contains multiple files with the same name (thanks to Jedi 0.12.0 update). + ([#178](https://github.com/Microsoft/vscode-python/issues/178)) +1. Provide type details appropriate for the iterable in a `for` loop when the line has a `# type` comment. + ([#338](https://github.com/Microsoft/vscode-python/issues/338)) +1. Parameter hints following an f-string work again. + ([#344](https://github.com/Microsoft/vscode-python/issues/344)) +1. When `editor.formatOnType` is on, don't indent after a single-line statement block + ([#726](https://github.com/Microsoft/vscode-python/issues/726)) + +### Code Health + +1. Improved developer experience of the Python Extension on Windows. ([#1216](https://github.com/Microsoft/vscode-python/issues/1216)) +1. Parallelize jobs (unit tests) on CI server. + ([#1247](https://github.com/Microsoft/vscode-python/issues/1247)) +1. Run CI tests against the release version and master branch of PTVSD (experimental debugger), allowing tests to fail against the mastre branch of PTVSD. + ([#1253](https://github.com/Microsoft/vscode-python/issues/1253)) +1. Only trigger the extension for `file` and `untitled` in preparation for +[Visual Studio Live Share](https://aka.ms/vsls) +(thanks to [Jonathan Carter](https://github.com/lostintangent)) + ([#1298](https://github.com/Microsoft/vscode-python/issues/1298)) +1. Ensure all unit tests run on Travis use the right Python interpreter. + ([#1318](https://github.com/Microsoft/vscode-python/issues/1318)) +1. Pin all production dependencies. + ([#1374](https://github.com/Microsoft/vscode-python/issues/1374)) +1. Add support for [hit count breakpoints](https://code.visualstudio.com/docs/editor/debugging#_advanced-breakpoint-topics) in the experimental debugger. + ([#1409](https://github.com/Microsoft/vscode-python/issues/1409)) +1. Ensure custom environment variables defined in `.env` file are passed onto the `pipenv` command. + ([#1428](https://github.com/Microsoft/vscode-python/issues/1428)) + + + ## 2018.3.1 (29 Mar 2018) ### Fixes diff --git a/news/2 Fixes/1033.md b/news/2 Fixes/1033.md index 31fec8720909..6c66d9582d82 100644 --- a/news/2 Fixes/1033.md +++ b/news/2 Fixes/1033.md @@ -1 +1 @@ -Fix go to definition functionality across files. \ No newline at end of file +Fix `go to definition` functionality across files. diff --git a/news/2 Fixes/1254.md b/news/2 Fixes/1254.md index 06f1f135aa71..fce864eb6f9f 100644 --- a/news/2 Fixes/1254.md +++ b/news/2 Fixes/1254.md @@ -1 +1 @@ -Dislay errors returned by the PipEnv command when identifying the corresonding environment. +Display errors returned by the PipEnv command when identifying the corresonding environment. diff --git a/news/3 Code Health/1253.md b/news/3 Code Health/1253.md index 5dcc6d79bfec..aabafccfc97a 100644 --- a/news/3 Code Health/1253.md +++ b/news/3 Code Health/1253.md @@ -1 +1 @@ -Run CI tests against the release version and master branch of PTVSD (experimental debugger), allowing tests to fail against the mastre branch of PTVSD. +Run CI tests against the release version and master branch of PTVSD (experimental debugger), allowing tests to fail against the master branch of PTVSD. diff --git a/package.json b/package.json index 0b518adeb974..c8953f53c71c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2018.4.0-alpha", + "version": "2018.4.0-beta", "publisher": "ms-python", "author": { "name": "Microsoft Corporation" From fd45e81f6d275ddcb60ae17f443799c837d222cb Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 23 Apr 2018 14:04:40 -0700 Subject: [PATCH 147/433] Thanks ptvsd --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index afa0e8fc686c..d30ed6dbcaa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ our features: - [jedi 0.12.0](https://pypi.org/project/jedi/0.12.0/) and [parso 0.2.0](https://pypi.org/project/parso/0.2.0/) - [isort 4.2.15](https://pypi.org/project/isort/4.2.15/) +- [ptvsd 3.0.0](https://pypi.org/project/ptvsd/3.0.0/) and [4.1.1a1](https://pypi.org/project/ptvsd/4.1.1a1/) - [rope](https://pypi.org/project/rope/) (user-installed) - [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) From 46b2dec3b5175816ee77d55a40e62d525af27d77 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 23 Apr 2018 14:05:23 -0700 Subject: [PATCH 148/433] Alphabetize --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d30ed6dbcaa3..f49f3e669a29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,12 @@ Thanks to the following projects which we fully rely on to provide some of our features: +- [isort 4.2.15](https://pypi.org/project/isort/4.2.15/) - [jedi 0.12.0](https://pypi.org/project/jedi/0.12.0/) and [parso 0.2.0](https://pypi.org/project/parso/0.2.0/) -- [isort 4.2.15](https://pypi.org/project/isort/4.2.15/) - [ptvsd 3.0.0](https://pypi.org/project/ptvsd/3.0.0/) and [4.1.1a1](https://pypi.org/project/ptvsd/4.1.1a1/) -- [rope](https://pypi.org/project/rope/) (user-installed) - [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) And a special thanks to [Patryk Zawadzki](https://github.com/patrys) for all of his help on [our issue tracker](https://github.com/Microsoft/vscode-python)! From 16818125d0bda28a95c8bfa3e1c8476a69c5a630 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 23 Apr 2018 14:07:04 -0700 Subject: [PATCH 149/433] Enhancements to running code in a terminal (#1432) Fixes #1207 Fixes #1316 Fixes #1349 Fixes #259 --- news/1 Enhancements/1207.md | 1 + news/1 Enhancements/1316.md | 1 + news/1 Enhancements/1349.md | 1 + news/2 Fixes/259.md | 1 + package.json | 6 + src/client/common/configSettings.ts | 2 +- src/client/common/types.ts | 2 +- .../codeExecution/codeExecutionManager.ts | 5 +- .../codeExecution/djangoShellCodeExecution.ts | 2 +- src/client/terminals/codeExecution/helper.ts | 36 ++++-- .../codeExecution/terminalCodeExecution.ts | 11 +- src/client/terminals/types.ts | 3 +- .../terminalExec/sample1_normalized.py | 22 ++++ .../pythonFiles/terminalExec/sample1_raw.py | 24 ++++ .../terminalExec/sample2_normalized.py | 7 ++ .../pythonFiles/terminalExec/sample2_raw.py | 8 ++ .../terminalExec/sample3_normalized.py | 4 + .../pythonFiles/terminalExec/sample3_raw.py | 5 + .../terminalExec/sample4_normalized.py | 6 + .../pythonFiles/terminalExec/sample4_raw.py | 7 ++ .../terminalExec/sample5_normalized.py | 8 ++ .../pythonFiles/terminalExec/sample5_raw.py | 11 ++ .../codeExecutionManager.test.ts | 19 +-- .../terminals/codeExecution/helper.test.ts | 114 ++++++++++++++++-- 24 files changed, 270 insertions(+), 36 deletions(-) create mode 100644 news/1 Enhancements/1207.md create mode 100644 news/1 Enhancements/1316.md create mode 100644 news/1 Enhancements/1349.md create mode 100644 news/2 Fixes/259.md create mode 100644 src/test/pythonFiles/terminalExec/sample1_normalized.py create mode 100644 src/test/pythonFiles/terminalExec/sample1_raw.py create mode 100644 src/test/pythonFiles/terminalExec/sample2_normalized.py create mode 100644 src/test/pythonFiles/terminalExec/sample2_raw.py create mode 100644 src/test/pythonFiles/terminalExec/sample3_normalized.py create mode 100644 src/test/pythonFiles/terminalExec/sample3_raw.py create mode 100644 src/test/pythonFiles/terminalExec/sample4_normalized.py create mode 100644 src/test/pythonFiles/terminalExec/sample4_raw.py create mode 100644 src/test/pythonFiles/terminalExec/sample5_normalized.py create mode 100644 src/test/pythonFiles/terminalExec/sample5_raw.py diff --git a/news/1 Enhancements/1207.md b/news/1 Enhancements/1207.md new file mode 100644 index 000000000000..f668cdf00aae --- /dev/null +++ b/news/1 Enhancements/1207.md @@ -0,0 +1 @@ +Remove empty spaces from the selected text of the active editor when executing in a terminal. diff --git a/news/1 Enhancements/1316.md b/news/1 Enhancements/1316.md new file mode 100644 index 000000000000..ca6c6a80eb77 --- /dev/null +++ b/news/1 Enhancements/1316.md @@ -0,0 +1 @@ +Save the python file before running it in the terminal using the command/menu `Run Python File in Terminal`. diff --git a/news/1 Enhancements/1349.md b/news/1 Enhancements/1349.md new file mode 100644 index 000000000000..7bcbcd8f1a88 --- /dev/null +++ b/news/1 Enhancements/1349.md @@ -0,0 +1 @@ +Add `Ctrl+Enter` keyboard shortcut for `Run Selection/Line in Python Terminal`. diff --git a/news/2 Fixes/259.md b/news/2 Fixes/259.md new file mode 100644 index 000000000000..8579bc07f4cc --- /dev/null +++ b/news/2 Fixes/259.md @@ -0,0 +1 @@ +Add blank lines to seprate blocks of indented code (function defs, classes, and the like) to ensure the code can be run within a Python interactive prompt. diff --git a/package.json b/package.json index c8953f53c71c..74a1832f6939 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,12 @@ "path": "./snippets/python.json" } ], + "keybindings":[ + { + "command": "python.execSelectionInTerminal", + "key": "ctrl+enter" + } + ], "commands": [ { "command": "python.sortImports", diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index 49b8c53332fc..99c111d64f70 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -37,7 +37,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { public formatting?: IFormattingSettings; public autoComplete?: IAutoCompleteSettings; public unitTest?: IUnitTestSettings; - public terminal?: ITerminalSettings; + public terminal!: ITerminalSettings; public sortImports?: ISortImportSettings; public workspaceSymbols?: IWorkspaceSymbolSettings; public disableInstallationChecks = false; diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 5e16a4557786..240b6f5809e3 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -106,7 +106,7 @@ export interface IPythonSettings { readonly formatting?: IFormattingSettings; readonly unitTest?: IUnitTestSettings; readonly autoComplete?: IAutoCompleteSettings; - readonly terminal?: ITerminalSettings; + readonly terminal: ITerminalSettings; readonly sortImports?: ISortImportSettings; readonly workspaceSymbols?: IWorkspaceSymbolSettings; readonly envFile: string; diff --git a/src/client/terminals/codeExecution/codeExecutionManager.ts b/src/client/terminals/codeExecution/codeExecutionManager.ts index 5a6159a50aae..04ccf5e61c19 100644 --- a/src/client/terminals/codeExecution/codeExecutionManager.ts +++ b/src/client/terminals/codeExecution/codeExecutionManager.ts @@ -35,6 +35,7 @@ export class CodeExecutionManager implements ICodeExecutionManager { if (!fileToExecute) { return; } + await codeExecutionHelper.saveFileIfDirty(fileToExecute); const executionService = this.serviceContainer.get(ICodeExecutionService, 'standard'); await executionService.executeFile(fileToExecute); } @@ -59,11 +60,11 @@ export class CodeExecutionManager implements ICodeExecutionManager { } const codeExecutionHelper = this.serviceContainer.get(ICodeExecutionHelper); const codeToExecute = await codeExecutionHelper.getSelectedTextToExecute(activeEditor!); - const normalizedCode = codeExecutionHelper.normalizeLines(codeToExecute!); + const normalizedCode = await codeExecutionHelper.normalizeLines(codeToExecute!); if (!normalizedCode || normalizedCode.trim().length === 0) { return; } - await executionService.execute(codeToExecute!, activeEditor!.document.uri); + await executionService.execute(normalizedCode, activeEditor!.document.uri); } } diff --git a/src/client/terminals/codeExecution/djangoShellCodeExecution.ts b/src/client/terminals/codeExecution/djangoShellCodeExecution.ts index 4fc230b6e21a..3066ec27fb71 100644 --- a/src/client/terminals/codeExecution/djangoShellCodeExecution.ts +++ b/src/client/terminals/codeExecution/djangoShellCodeExecution.ts @@ -32,7 +32,7 @@ export class DjangoShellCodeExecutionProvider extends TerminalCodeExecutionProvi public getReplCommandArgs(resource?: Uri): { command: string; args: string[] } { const pythonSettings = this.configurationService.getSettings(resource); const command = this.platformService.isWindows ? pythonSettings.pythonPath.replace(/\\/g, '/') : pythonSettings.pythonPath; - const args = pythonSettings.terminal!.launchArgs.slice(); + const args = pythonSettings.terminal.launchArgs.slice(); const workspaceUri = resource ? this.workspace.getWorkspaceFolder(resource) : undefined; const defaultWorkspace = Array.isArray(this.workspace.workspaceFolders) && this.workspace.workspaceFolders.length > 0 ? this.workspace.workspaceFolders[0].uri.fsPath : ''; diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index efa468dcbc95..f5c2e0baeb27 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -2,23 +2,34 @@ // Licensed under the MIT License. import { inject, injectable } from 'inversify'; -import { EOL } from 'os'; import { Range, TextEditor, Uri } from 'vscode'; import { IApplicationShell, IDocumentManager } from '../../common/application/types'; import { PythonLanguage } from '../../common/constants'; import '../../common/extensions'; +import { IServiceContainer } from '../../ioc/types'; import { ICodeExecutionHelper } from '../types'; @injectable() export class CodeExecutionHelper implements ICodeExecutionHelper { - constructor( @inject(IDocumentManager) private documentManager: IDocumentManager, - @inject(IApplicationShell) private applicationShell: IApplicationShell) { - + private readonly documentManager: IDocumentManager; + private readonly applicationShell: IApplicationShell; + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { + this.documentManager = serviceContainer.get(IDocumentManager); + this.applicationShell = serviceContainer.get(IApplicationShell); } - public normalizeLines(code: string): string { - const codeLines = code.splitLines({ trim: false, removeEmptyEntries: false }); - const codeLinesWithoutEmptyLines = codeLines.filter(line => line.trim().length > 0); - return codeLinesWithoutEmptyLines.join(EOL); + public async normalizeLines(code: string, resource?: Uri): Promise { + try { + if (code.trim().length === 0) { + return ''; + } + const regex = /(\n)([ \t]*\r?\n)([ \t]+\S+)/gm; + return code.replace(regex, (_, a, b, c) => { + return `${a}${c}`; + }); + } catch (ex) { + console.error(ex, 'Python: Failed to normalize code for execution in terminal'); + return code; + } } public async getFileToExecute(): Promise { @@ -35,6 +46,9 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { this.applicationShell.showErrorMessage('The active file is not a Python source file'); return; } + if (activeEditor.document.isDirty) { + await activeEditor.document.save(); + } return activeEditor.document.uri; } @@ -53,4 +67,10 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { } return code; } + public async saveFileIfDirty(file: Uri): Promise { + const docs = this.documentManager.textDocuments.filter(d => d.uri.path === file.path); + if (docs.length === 1 && docs[0].isDirty) { + await docs[0].save(); + } + } } diff --git a/src/client/terminals/codeExecution/terminalCodeExecution.ts b/src/client/terminals/codeExecution/terminalCodeExecution.ts index d6ab0601c885..7e541ee12126 100644 --- a/src/client/terminals/codeExecution/terminalCodeExecution.ts +++ b/src/client/terminals/codeExecution/terminalCodeExecution.ts @@ -10,16 +10,15 @@ import { IWorkspaceService } from '../../common/application/types'; import '../../common/extensions'; import { IPlatformService } from '../../common/platform/types'; import { ITerminalService, ITerminalServiceFactory } from '../../common/terminal/types'; -import { IConfigurationService } from '../../common/types'; -import { IDisposableRegistry } from '../../common/types'; +import { IConfigurationService, IDisposableRegistry } from '../../common/types'; import { ICodeExecutionService } from '../../terminals/types'; @injectable() export class TerminalCodeExecutionProvider implements ICodeExecutionService { - protected terminalTitle: string; - private _terminalService: ITerminalService; + protected terminalTitle!: string; + private _terminalService!: ITerminalService; private replActive?: Promise; - constructor( @inject(ITerminalServiceFactory) protected readonly terminalServiceFactory: ITerminalServiceFactory, + constructor(@inject(ITerminalServiceFactory) protected readonly terminalServiceFactory: ITerminalServiceFactory, @inject(IConfigurationService) protected readonly configurationService: IConfigurationService, @inject(IWorkspaceService) protected readonly workspace: IWorkspaceService, @inject(IDisposableRegistry) protected readonly disposables: Disposable[], @@ -60,7 +59,7 @@ export class TerminalCodeExecutionProvider implements ICodeExecutionService { await this.replActive; } - public getReplCommandArgs(resource?: Uri): { command: string, args: string[] } { + public getReplCommandArgs(resource?: Uri): { command: string; args: string[] } { const pythonSettings = this.configurationService.getSettings(resource); const command = this.platformService.isWindows ? pythonSettings.pythonPath.replace(/\\/g, '/') : pythonSettings.pythonPath; const args = pythonSettings.terminal.launchArgs.slice(); diff --git a/src/client/terminals/types.ts b/src/client/terminals/types.ts index 67c50d4b887b..cd9e1bc96a9b 100644 --- a/src/client/terminals/types.ts +++ b/src/client/terminals/types.ts @@ -14,8 +14,9 @@ export interface ICodeExecutionService { export const ICodeExecutionHelper = Symbol('ICodeExecutionHelper'); export interface ICodeExecutionHelper { - normalizeLines(code: string): string; + normalizeLines(code: string): Promise; getFileToExecute(): Promise; + saveFileIfDirty(file: Uri): Promise; getSelectedTextToExecute(textEditor: TextEditor): Promise; } diff --git a/src/test/pythonFiles/terminalExec/sample1_normalized.py b/src/test/pythonFiles/terminalExec/sample1_normalized.py new file mode 100644 index 000000000000..0896de65d22f --- /dev/null +++ b/src/test/pythonFiles/terminalExec/sample1_normalized.py @@ -0,0 +1,22 @@ +# Sample block 1 +def square(x): + return x**2 + +print('hello') +# Sample block 2 +a = 2 +if a < 2: + print('less than 2') +else: + print('more than 2') + +print('hello') + +# Sample block 3 +for i in range(5): + print(i) + print(i) + print(i) + print(i) + +print('complete') diff --git a/src/test/pythonFiles/terminalExec/sample1_raw.py b/src/test/pythonFiles/terminalExec/sample1_raw.py new file mode 100644 index 000000000000..fe050c7af289 --- /dev/null +++ b/src/test/pythonFiles/terminalExec/sample1_raw.py @@ -0,0 +1,24 @@ +# Sample block 1 +def square(x): + return x**2 + +print('hello') +# Sample block 2 +a = 2 +if a < 2: + print('less than 2') +else: + print('more than 2') + +print('hello') + +# Sample block 3 +for i in range(5): + print(i) + + print(i) + print(i) + + print(i) + +print('complete') diff --git a/src/test/pythonFiles/terminalExec/sample2_normalized.py b/src/test/pythonFiles/terminalExec/sample2_normalized.py new file mode 100644 index 000000000000..a333d4e0daae --- /dev/null +++ b/src/test/pythonFiles/terminalExec/sample2_normalized.py @@ -0,0 +1,7 @@ +def add(x, y): + """Adds x to y""" + # Some comment + return x + y + +v = add(1, 7) +print(v) diff --git a/src/test/pythonFiles/terminalExec/sample2_raw.py b/src/test/pythonFiles/terminalExec/sample2_raw.py new file mode 100644 index 000000000000..6ab7e67637f4 --- /dev/null +++ b/src/test/pythonFiles/terminalExec/sample2_raw.py @@ -0,0 +1,8 @@ +def add(x, y): + """Adds x to y""" + # Some comment + + return x + y + +v = add(1, 7) +print(v) diff --git a/src/test/pythonFiles/terminalExec/sample3_normalized.py b/src/test/pythonFiles/terminalExec/sample3_normalized.py new file mode 100644 index 000000000000..e4f028b0b778 --- /dev/null +++ b/src/test/pythonFiles/terminalExec/sample3_normalized.py @@ -0,0 +1,4 @@ +if True: + print(1) + print(2) +print(3) diff --git a/src/test/pythonFiles/terminalExec/sample3_raw.py b/src/test/pythonFiles/terminalExec/sample3_raw.py new file mode 100644 index 000000000000..5865e6d2cbde --- /dev/null +++ b/src/test/pythonFiles/terminalExec/sample3_raw.py @@ -0,0 +1,5 @@ +if True: + print(1) + + print(2) +print(3) diff --git a/src/test/pythonFiles/terminalExec/sample4_normalized.py b/src/test/pythonFiles/terminalExec/sample4_normalized.py new file mode 100644 index 000000000000..2c49d10253ff --- /dev/null +++ b/src/test/pythonFiles/terminalExec/sample4_normalized.py @@ -0,0 +1,6 @@ +class pc(object): + def __init__(self, pcname, model): + self.pcname = pcname + self.model = model + def print_name(self): + print('Workstation name is', self.pcname, 'model is', self.model) diff --git a/src/test/pythonFiles/terminalExec/sample4_raw.py b/src/test/pythonFiles/terminalExec/sample4_raw.py new file mode 100644 index 000000000000..fbf0d68fe5f8 --- /dev/null +++ b/src/test/pythonFiles/terminalExec/sample4_raw.py @@ -0,0 +1,7 @@ +class pc(object): + def __init__(self, pcname, model): + self.pcname = pcname + self.model = model + + def print_name(self): + print('Workstation name is', self.pcname, 'model is', self.model) diff --git a/src/test/pythonFiles/terminalExec/sample5_normalized.py b/src/test/pythonFiles/terminalExec/sample5_normalized.py new file mode 100644 index 000000000000..822d51bd15d9 --- /dev/null +++ b/src/test/pythonFiles/terminalExec/sample5_normalized.py @@ -0,0 +1,8 @@ +for i in range(10): + print('a') + for j in range(5): + print('b') + print('b2') + for k in range(2): + print('c') + print('done with first loop') diff --git a/src/test/pythonFiles/terminalExec/sample5_raw.py b/src/test/pythonFiles/terminalExec/sample5_raw.py new file mode 100644 index 000000000000..19caa9cf26a6 --- /dev/null +++ b/src/test/pythonFiles/terminalExec/sample5_raw.py @@ -0,0 +1,11 @@ +for i in range(10): + print('a') + for j in range(5): + print('b') + + print('b2') + + for k in range(2): + print('c') + + print('done with first loop') diff --git a/src/test/terminals/codeExecution/codeExecutionManager.test.ts b/src/test/terminals/codeExecution/codeExecutionManager.test.ts index 8dec4d7f5025..05c395a1e571 100644 --- a/src/test/terminals/codeExecution/codeExecutionManager.test.ts +++ b/src/test/terminals/codeExecution/codeExecutionManager.test.ts @@ -69,7 +69,7 @@ suite('Terminal - Code Execution Manager', () => { serviceContainer.setup(s => s.get(TypeMoq.It.isValue(ICodeExecutionHelper))).returns(() => helper.object); await commandHandler!(); - helper.verify(async h => await h.getFileToExecute(), TypeMoq.Times.once()); + helper.verify(async h => h.getFileToExecute(), TypeMoq.Times.once()); }); test('Ensure executeFileInterTerminal will use provided file', async () => { @@ -96,8 +96,8 @@ suite('Terminal - Code Execution Manager', () => { const fileToExecute = Uri.file('x'); await commandHandler!(fileToExecute); - helper.verify(async h => await h.getFileToExecute(), TypeMoq.Times.never()); - executionService.verify(async e => await e.executeFile(TypeMoq.It.isValue(fileToExecute)), TypeMoq.Times.once()); + helper.verify(async h => h.getFileToExecute(), TypeMoq.Times.never()); + executionService.verify(async e => e.executeFile(TypeMoq.It.isValue(fileToExecute)), TypeMoq.Times.once()); }); test('Ensure executeFileInterTerminal will use active file', async () => { @@ -119,12 +119,12 @@ suite('Terminal - Code Execution Manager', () => { const fileToExecute = Uri.file('x'); const helper = TypeMoq.Mock.ofType(); serviceContainer.setup(s => s.get(TypeMoq.It.isValue(ICodeExecutionHelper))).returns(() => helper.object); - helper.setup(async h => await h.getFileToExecute()).returns(() => Promise.resolve(fileToExecute)); + helper.setup(async h => h.getFileToExecute()).returns(() => Promise.resolve(fileToExecute)); const executionService = TypeMoq.Mock.ofType(); serviceContainer.setup(s => s.get(TypeMoq.It.isValue(ICodeExecutionService), TypeMoq.It.isValue('standard'))).returns(() => executionService.object); await commandHandler!(fileToExecute); - executionService.verify(async e => await e.executeFile(TypeMoq.It.isValue(fileToExecute)), TypeMoq.Times.once()); + executionService.verify(async e => e.executeFile(TypeMoq.It.isValue(fileToExecute)), TypeMoq.Times.once()); }); async function testExecutionOfSelectionWithoutAnyActiveDocument(commandId: string, executionSericeId: string) { @@ -150,7 +150,7 @@ suite('Terminal - Code Execution Manager', () => { documentManager.setup(d => d.activeTextEditor).returns(() => undefined); await commandHandler!(); - executionService.verify(async e => await e.execute(TypeMoq.It.isAny()), TypeMoq.Times.never()); + executionService.verify(async e => e.execute(TypeMoq.It.isAny()), TypeMoq.Times.never()); } test('Ensure executeSelectionInTerminal will do nothing if theres no active document', async () => { @@ -186,7 +186,7 @@ suite('Terminal - Code Execution Manager', () => { documentManager.setup(d => d.activeTextEditor).returns(() => { return {} as any; }); await commandHandler!(); - executionService.verify(async e => await e.execute(TypeMoq.It.isAny()), TypeMoq.Times.never()); + executionService.verify(async e => e.execute(TypeMoq.It.isAny()), TypeMoq.Times.never()); } test('Ensure executeSelectionInTerminal will do nothing if no text is selected', async () => { @@ -218,7 +218,7 @@ suite('Terminal - Code Execution Manager', () => { const helper = TypeMoq.Mock.ofType(); serviceContainer.setup(s => s.get(TypeMoq.It.isValue(ICodeExecutionHelper))).returns(() => helper.object); helper.setup(h => h.getSelectedTextToExecute).returns(() => () => Promise.resolve(textSelected)); - helper.setup(h => h.normalizeLines).returns(() => () => textSelected); + helper.setup(h => h.normalizeLines).returns(() => () => Promise.resolve(textSelected)).verifiable(TypeMoq.Times.once()); const executionService = TypeMoq.Mock.ofType(); serviceContainer.setup(s => s.get(TypeMoq.It.isValue(ICodeExecutionService), TypeMoq.It.isValue(executionServiceId))).returns(() => executionService.object); const document = TypeMoq.Mock.ofType(); @@ -228,7 +228,8 @@ suite('Terminal - Code Execution Manager', () => { documentManager.setup(d => d.activeTextEditor).returns(() => activeEditor.object); await commandHandler!(); - executionService.verify(async e => await e.execute(TypeMoq.It.isValue(textSelected), TypeMoq.It.isValue(activeDocumentUri)), TypeMoq.Times.once()); + executionService.verify(async e => e.execute(TypeMoq.It.isValue(textSelected), TypeMoq.It.isValue(activeDocumentUri)), TypeMoq.Times.once()); + helper.verifyAll(); } test('Ensure executeSelectionInTerminal will normalize selected text and send it to the terminal', async () => { await testExecutionOfSelectionIsSentToTerminal(Commands.Exec_Selection_In_Terminal, 'standard'); diff --git a/src/test/terminals/codeExecution/helper.test.ts b/src/test/terminals/codeExecution/helper.test.ts index 064ca0d55e72..a9344344d548 100644 --- a/src/test/terminals/codeExecution/helper.test.ts +++ b/src/test/terminals/codeExecution/helper.test.ts @@ -4,14 +4,19 @@ // tslint:disable:no-multiline-string no-trailing-whitespace import { expect } from 'chai'; +import * as fs from 'fs-extra'; import { EOL } from 'os'; +import * as path from 'path'; import * as TypeMoq from 'typemoq'; import { Range, Selection, TextDocument, TextEditor, TextLine, Uri } from 'vscode'; import { IApplicationShell, IDocumentManager } from '../../../client/common/application/types'; -import { PythonLanguage } from '../../../client/common/constants'; +import { EXTENSION_ROOT_DIR, PythonLanguage } from '../../../client/common/constants'; +import { IServiceContainer } from '../../../client/ioc/types'; import { CodeExecutionHelper } from '../../../client/terminals/codeExecution/helper'; import { ICodeExecutionHelper } from '../../../client/terminals/types'; +const TEST_FILES_PATH = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'terminalExec'); + // tslint:disable-next-line:max-func-body-length suite('Terminal - Code Execution Helper', () => { let documentManager: TypeMoq.IMock; @@ -20,20 +25,43 @@ suite('Terminal - Code Execution Helper', () => { let document: TypeMoq.IMock; let editor: TypeMoq.IMock; setup(() => { + const serviceContainer = TypeMoq.Mock.ofType(); documentManager = TypeMoq.Mock.ofType(); applicationShell = TypeMoq.Mock.ofType(); - helper = new CodeExecutionHelper(documentManager.object, applicationShell.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IDocumentManager), TypeMoq.It.isAny())).returns(() => documentManager.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IApplicationShell), TypeMoq.It.isAny())).returns(() => applicationShell.object); + helper = new CodeExecutionHelper(serviceContainer.object); document = TypeMoq.Mock.ofType(); editor = TypeMoq.Mock.ofType(); editor.setup(e => e.document).returns(() => document.object); }); - test('Ensure blank lines are removed', async () => { - const code = ['import sys', '', '', '', 'print(sys.executable)', '', 'print("1234")', '', '', 'print(1)', 'print(2)']; - const expectedCode = code.filter(line => line.trim().length > 0).join(EOL); - const normalizedZCode = helper.normalizeLines(code.join(EOL)); - expect(normalizedZCode).to.be.equal(expectedCode); + async function ensureBlankLinesAreRemoved(source: string, expectedSource: string) { + const normalizedZCode = await helper.normalizeLines(source); + expect(normalizedZCode).to.be.equal(expectedSource); + } + test('Ensure blank lines are NOT removed when code is not indented (simple)', async () => { + const code = ['import sys', '', 'print(sys.executable)', '', 'print("1234")', '', 'print(1)', 'print(2)']; + const expectedCode = code.join(EOL); + await ensureBlankLinesAreRemoved(code.join(EOL), expectedCode); + }); + ['sample1', 'sample2', 'sample3', 'sample4', 'sample5'].forEach(fileName => { + test(`Ensure blank lines are removed (${fileName})`, async () => { + const code = await fs.readFile(path.join(TEST_FILES_PATH, `${fileName}_raw.py`), 'utf8'); + const expectedCode = await fs.readFile(path.join(TEST_FILES_PATH, `${fileName}_normalized.py`), 'utf8'); + await ensureBlankLinesAreRemoved(code, expectedCode); + }); + // test(`Ensure blank lines are removed, including leading empty lines (${fileName})`, async () => { + // const code = await fs.readFile(path.join(TEST_FILES_PATH, `${fileName}_raw.py`), 'utf8'); + // const expectedCode = await fs.readFile(path.join(TEST_FILES_PATH, `${fileName}_normalized.py`), 'utf8'); + // await ensureBlankLinesAreRemoved(['', '', ''].join(EOL) + EOL + code, expectedCode); + // }); + }); + test('Ensure blank lines are removed (sample2)', async () => { + const code = await fs.readFile(path.join(TEST_FILES_PATH, 'sample2_raw.py'), 'utf8'); + const expectedCode = await fs.readFile(path.join(TEST_FILES_PATH, 'sample2_normalized.py'), 'utf8'); + await ensureBlankLinesAreRemoved(code, expectedCode); }); test('Display message if there\s no active file', async () => { documentManager.setup(doc => doc.activeTextEditor).returns(() => undefined); @@ -73,6 +101,45 @@ suite('Terminal - Code Execution Helper', () => { expect(uri).to.be.deep.equal(expectedUri); }); + test('Returns file uri even if saving fails', async () => { + document.setup(doc => doc.isUntitled).returns(() => false); + document.setup(doc => doc.isDirty).returns(() => true); + document.setup(doc => doc.languageId).returns(() => PythonLanguage.language); + document.setup(doc => doc.save()).returns(() => Promise.resolve(false)); + const expectedUri = Uri.file('one.py'); + document.setup(doc => doc.uri).returns(() => expectedUri); + documentManager.setup(doc => doc.activeTextEditor).returns(() => editor.object); + + const uri = await helper.getFileToExecute(); + expect(uri).to.be.deep.equal(expectedUri); + }); + + test('Dirty files are saved', async () => { + document.setup(doc => doc.isUntitled).returns(() => false); + document.setup(doc => doc.isDirty).returns(() => true); + document.setup(doc => doc.languageId).returns(() => PythonLanguage.language); + const expectedUri = Uri.file('one.py'); + document.setup(doc => doc.uri).returns(() => expectedUri); + documentManager.setup(doc => doc.activeTextEditor).returns(() => editor.object); + + const uri = await helper.getFileToExecute(); + expect(uri).to.be.deep.equal(expectedUri); + document.verify(doc => doc.save(), TypeMoq.Times.once()); + }); + + test('Non-Dirty files are not-saved', async () => { + document.setup(doc => doc.isUntitled).returns(() => false); + document.setup(doc => doc.isDirty).returns(() => false); + document.setup(doc => doc.languageId).returns(() => PythonLanguage.language); + const expectedUri = Uri.file('one.py'); + document.setup(doc => doc.uri).returns(() => expectedUri); + documentManager.setup(doc => doc.activeTextEditor).returns(() => editor.object); + + const uri = await helper.getFileToExecute(); + expect(uri).to.be.deep.equal(expectedUri); + document.verify(doc => doc.save(), TypeMoq.Times.never()); + }); + test('Returns current line if nothing is selected', async () => { const lineContents = 'Line Contents'; editor.setup(e => e.selection).returns(() => new Selection(3, 0, 3, 0)); @@ -94,4 +161,37 @@ suite('Terminal - Code Execution Helper', () => { const content = await helper.getSelectedTextToExecute(editor.object); expect(content).to.be.equal('3.0.10.5'); }); + + test('saveFileIfDirty will not fail if file is not opened', async () => { + documentManager.setup(d => d.textDocuments).returns(() => []).verifiable(TypeMoq.Times.once()); + + await helper.saveFileIfDirty(Uri.file(`${__filename}.py`)); + documentManager.verifyAll(); + }); + + test('File will be saved if file is dirty', async () => { + documentManager.setup(d => d.textDocuments).returns(() => [document.object]).verifiable(TypeMoq.Times.once()); + document.setup(doc => doc.isUntitled).returns(() => false); + document.setup(doc => doc.isDirty).returns(() => true); + document.setup(doc => doc.languageId).returns(() => PythonLanguage.language); + const expectedUri = Uri.file('one.py'); + document.setup(doc => doc.uri).returns(() => expectedUri); + + await helper.saveFileIfDirty(expectedUri); + documentManager.verifyAll(); + document.verify(doc => doc.save(), TypeMoq.Times.once()); + }); + + test('File will be not saved if file is not dirty', async () => { + documentManager.setup(d => d.textDocuments).returns(() => [document.object]).verifiable(TypeMoq.Times.once()); + document.setup(doc => doc.isUntitled).returns(() => false); + document.setup(doc => doc.isDirty).returns(() => false); + document.setup(doc => doc.languageId).returns(() => PythonLanguage.language); + const expectedUri = Uri.file('one.py'); + document.setup(doc => doc.uri).returns(() => expectedUri); + + await helper.saveFileIfDirty(expectedUri); + documentManager.verifyAll(); + document.verify(doc => doc.save(), TypeMoq.Times.never()); + }); }); From d90f21751307769c101b1dd061a9c243ac062bfd Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 23 Apr 2018 15:20:32 -0700 Subject: [PATCH 150/433] Break down how to execute "run selection" --- .github/test_plan.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/test_plan.md b/.github/test_plan.md index deae21ca34ba..a399cc36d17b 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -22,6 +22,9 @@ - [ ] [`"python.pythonPath"`](https://code.visualstudio.com/docs/python/environments#_manually-specifying-an-interpreter) triggers an update in the status bar - [ ] `Run Python File in Terminal` - [ ] `Run Selection/Line in Python Terminal` + - [ ] Right-click + - [ ] Command + - [ ] `Ctrl-Enter` #### Virtual environments From 803cbfff4005459c1881c6cd8cb50d49539e588d Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 23 Apr 2018 15:22:03 -0700 Subject: [PATCH 151/433] `Create Terminal` steals focus --- .github/test_plan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/test_plan.md b/.github/test_plan.md index a399cc36d17b..78d25f7bed5f 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -34,7 +34,7 @@ - [ ] Appropriate suffix label specified in status bar - [ ] Prompt to install Pylint uses `--user` - [ ] `"python.terminal.activateEnvironments": false` deactivates detection - - [ ] [`Create Terminal`](https://code.visualstudio.com/docs/python/environments#_activating-an-environment-in-the-terminal) works + - [ ] [`Create Terminal`](https://code.visualstudio.com/docs/python/environments#_activating-an-environment-in-the-terminal) works and steals focus - [ ] Detect multiple virtual environments in a directory specified by `"python.venvPath"` - [ ] Detected all [conda environments created with an interpreter](https://code.visualstudio.com/docs/python/environments#_conda-environments) - [ ] Appropriate suffix label specified in status bar From b737bc920bd064b72f584116443d0aef6555aadd Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 23 Apr 2018 15:26:46 -0700 Subject: [PATCH 152/433] We don't install into `--user` in a venv --- .github/test_plan.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/test_plan.md b/.github/test_plan.md index 78d25f7bed5f..bad5d628135e 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -32,7 +32,6 @@ - [ ] Detected a single virtual environment at the top-level of the workspace folder - [ ] Appropriate suffix label specified in status bar - - [ ] Prompt to install Pylint uses `--user` - [ ] `"python.terminal.activateEnvironments": false` deactivates detection - [ ] [`Create Terminal`](https://code.visualstudio.com/docs/python/environments#_activating-an-environment-in-the-terminal) works and steals focus - [ ] Detect multiple virtual environments in a directory specified by `"python.venvPath"` @@ -87,6 +86,8 @@ enable=bad-names foo = 42 # Marked as a blacklisted name. ``` - [ ] Installation via the prompt installs Pylint as appropriate + - [ ] Uses `--user` for system-install of Python + - [ ] Installs into a virtual environment environment directly - [ ] Pylint works - [ ] `"python.linting.pylintUseMinimalCheckers": false` turns off the default rules w/ no `pylintrc` file present - [ ] The existense of a `pylintrc` file turns off the default rules From 4a6bd5e29babccaadec8a25a49ea05d2146e1173 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 23 Apr 2018 15:30:08 -0700 Subject: [PATCH 153/433] Clarify what python.terminal.activateEnvironment does --- .github/test_plan.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/test_plan.md b/.github/test_plan.md index bad5d628135e..16c3c55a8514 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -32,8 +32,9 @@ - [ ] Detected a single virtual environment at the top-level of the workspace folder - [ ] Appropriate suffix label specified in status bar - - [ ] `"python.terminal.activateEnvironments": false` deactivates detection - - [ ] [`Create Terminal`](https://code.visualstudio.com/docs/python/environments#_activating-an-environment-in-the-terminal) works and steals focus + - [ ] [`Create Terminal`](https://code.visualstudio.com/docs/python/environments#_activating-an-environment-in-the-terminal) works + - [ ] Steals focus + - [ ] `"python.terminal.activateEnvironment": false` turns off automatic activation of the environment - [ ] Detect multiple virtual environments in a directory specified by `"python.venvPath"` - [ ] Detected all [conda environments created with an interpreter](https://code.visualstudio.com/docs/python/environments#_conda-environments) - [ ] Appropriate suffix label specified in status bar From 635ff483a250424092e40c655c34ca800bd567de Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 23 Apr 2018 15:48:09 -0700 Subject: [PATCH 154/433] Break down conda install for Pylint --- .github/test_plan.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/test_plan.md b/.github/test_plan.md index 16c3c55a8514..86a799e3d3cc 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -38,7 +38,9 @@ - [ ] Detect multiple virtual environments in a directory specified by `"python.venvPath"` - [ ] Detected all [conda environments created with an interpreter](https://code.visualstudio.com/docs/python/environments#_conda-environments) - [ ] Appropriate suffix label specified in status bar - - [ ] Prompt to install Pylint installs into the conda environment + - [ ] Prompted to install Pylint + - [ ] Asked whether to install using conda or pip + - [ ] Installs into environment - [ ] `"python.terminal.activateEnvironments": false` deactivates detection - [ ] [`Create Terminal`](https://code.visualstudio.com/docs/python/environments#_activating-an-environment-in-the-terminal) works - [ ] (Linux/macOS until [`-m` is supported](https://github.com/Microsoft/vscode-python/issues/978)) Detected the virtual environment created by [pipenv](https://docs.pipenv.org/) From bc8012f9e30b1203e963844746a1c713f91d5922 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 23 Apr 2018 15:55:15 -0700 Subject: [PATCH 155/433] Clarify direnv instructions --- .github/test_plan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/test_plan.md b/.github/test_plan.md index 86a799e3d3cc..6e7c07e42d09 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -48,7 +48,7 @@ - [ ] Prompt to install Pylint uses `pipenv install --dev` - [ ] `"python.terminal.activateEnvironments": false` deactivates detection - [ ] [`Create Terminal`](https://code.visualstudio.com/docs/python/environments#_activating-an-environment-in-the-terminal) works -- [ ] (Linux/macOS) Detected virtual environments created under `{workspaceFolder}/.direnv/python-{python_version}` for [direnv](https://direnv.net/) and its [`layout python3`](https://github.com/direnv/direnv/blob/master/stdlib.sh) support +- [ ] (Linux/macOS) Virtual environments created under `{workspaceFolder}/.direnv/python-{python_version}` are detected (for [direnv](https://direnv.net/) and its [`layout python3`](https://github.com/direnv/direnv/blob/master/stdlib.sh) support) - [ ] Appropriate suffix label specified in status bar - [ ] `"python.terminal.activateEnvironments": false` deactivates detection From d336a7db090e54938d257c7d521c43be4951866a Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 23 Apr 2018 16:03:23 -0700 Subject: [PATCH 156/433] Suggest people reset their environment --- .github/test_plan.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/test_plan.md b/.github/test_plan.md index 6e7c07e42d09..89b144e63a01 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -64,6 +64,8 @@ print('Hello,', os.environ.get('WHO'), '!') WHO=world ``` +Make sure to use `Reload Window` between tests to reset your environment. + - [ ] Environment variables in a `.env` file are exposed when running under the debugger - [ ] `"python.envFile"` allows for specifying an environment file manually From 63eb37b6325067fc431ac35e1615c9ff95c64f1a Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 23 Apr 2018 16:10:04 -0700 Subject: [PATCH 157/433] Clear up .env checks --- .github/test_plan.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/test_plan.md b/.github/test_plan.md index 89b144e63a01..04ba0d5cfe41 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -62,12 +62,14 @@ print('Hello,', os.environ.get('WHO'), '!') ``` # .env WHO=world -``` +PYTHONPATH=some/path/somewhere +```` Make sure to use `Reload Window` between tests to reset your environment. - [ ] Environment variables in a `.env` file are exposed when running under the debugger -- [ ] `"python.envFile"` allows for specifying an environment file manually +- [ ] `"python.envFile"` allows for specifying an environment file manually (e.g. Jedi picks up `PYTHONPATH` changes) +- [ ] `envFile` in a `launch.json` configuration works #### [Debugging](https://code.visualstudio.com/docs/python/environments#_python-interpreter-for-debugging) From 9aae1de2063283b730a8b0722928551d7027ace9 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 23 Apr 2018 16:16:33 -0700 Subject: [PATCH 158/433] Emphasize --- .github/test_plan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/test_plan.md b/.github/test_plan.md index 04ba0d5cfe41..a3293778792b 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -65,7 +65,7 @@ WHO=world PYTHONPATH=some/path/somewhere ```` -Make sure to use `Reload Window` between tests to reset your environment. +**Make sure to use `Reload Window` between tests to reset your environment!** - [ ] Environment variables in a `.env` file are exposed when running under the debugger - [ ] `"python.envFile"` allows for specifying an environment file manually (e.g. Jedi picks up `PYTHONPATH` changes) From deac55e3a17044549915dcdaa68a7d215262ed82 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 23 Apr 2018 16:20:30 -0700 Subject: [PATCH 159/433] Clarify a setting --- .github/test_plan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/test_plan.md b/.github/test_plan.md index a3293778792b..15214d09cfa6 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -110,7 +110,7 @@ foo = 42 # Marked as a blacklisted name. - [ ] 3 or more linters work simultaneously - [ ] `Run Linting` runs all linters - [ ] The `Select Linter` command lists all the above linters and prompts to install a linter when missing - - [ ] `"python.linting.enabled"` disables all linters + - [ ] `"python.linting.enabled": false` disables all linters - [ ] The `Enable Linting` command changes `"python.linting.enabled"` - [ ] `"python.linting.lintOnSave` works From 51d8d7f15c7ceb63a698ebb61928ad742088c0af Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 23 Apr 2018 16:21:39 -0700 Subject: [PATCH 160/433] Add a note about immediately triggering a new linter --- .github/test_plan.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/test_plan.md b/.github/test_plan.md index 15214d09cfa6..2d62719556ac 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -101,6 +101,8 @@ foo = 42 # Marked as a blacklisted name. #### Other linters +You can always use the `Run Linting` command to immediately trigger a newly installed linter. + - [ ] flake8 works - [ ] mypy works - [ ] pydocstyle works From c55fbaf39da50f7d99230792ad67fc741a7cac82 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 23 Apr 2018 16:25:23 -0700 Subject: [PATCH 161/433] Alphabetize linters --- .github/test_plan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/test_plan.md b/.github/test_plan.md index 2d62719556ac..d52a06f496b3 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -105,9 +105,9 @@ You can always use the `Run Linting` command to immediately trigger a newly inst - [ ] flake8 works - [ ] mypy works -- [ ] pydocstyle works - [ ] pep8 works - [ ] prospector works +- [ ] pydocstyle works - [ ] pylama works - [ ] 3 or more linters work simultaneously - [ ] `Run Linting` runs all linters From 7d548819f9bb7de9873c7a395d489409b18b757d Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 23 Apr 2018 16:59:46 -0700 Subject: [PATCH 162/433] More steps in formatting --- .github/test_plan.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/test_plan.md b/.github/test_plan.md index d52a06f496b3..14a1c512cc3b 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -128,6 +128,9 @@ Please also test for general accuracy on the most "interesting" code you can fin #### [Formatting](https://code.visualstudio.com/docs/python/editing#_formatting) +- [ ] Prompted to install a formatter if none installed and `Format Document` is run + - [ ] Installing `autopep8` works + - [ ] Installing `yapf` works - [ ] autopep8 works - [ ] yapf works - [ ] `"editor.formatOnType": true` works and has expected results From 54a0d3a47aae7fcf4bb1e064c2c0e1304ae57f1c Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 23 Apr 2018 17:04:14 -0700 Subject: [PATCH 163/433] Add a formatter example --- .github/test_plan.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/test_plan.md b/.github/test_plan.md index 14a1c512cc3b..87c4a7ba3ab8 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -127,6 +127,11 @@ Please also test for general accuracy on the most "interesting" code you can fin - [ ] `"python.autocomplete.addBrackets": true` causes auto-completion of functions to append `()` #### [Formatting](https://code.visualstudio.com/docs/python/editing#_formatting) +Sample file: +```python +# There should be _some_ change after running `Format Document`. +def foo():pass +``` - [ ] Prompted to install a formatter if none installed and `Format Document` is run - [ ] Installing `autopep8` works From 3c091435c40b2914f65920c7f6768c31288e1289 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 23 Apr 2018 17:19:53 -0700 Subject: [PATCH 164/433] Installing rope --- .github/test_plan.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/test_plan.md b/.github/test_plan.md index 87c4a7ba3ab8..ec3e464ee3e8 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -145,6 +145,7 @@ def foo():pass - [ ] [`Extract Variable`](https://code.visualstudio.com/docs/python/editing#_extract-variable) works - [ ] [`Extract method`](https://code.visualstudio.com/docs/python/editing#_extract-method) works - [ ] [`Sort Imports`](https://code.visualstudio.com/docs/python/editing#_sort-imports) works +- [ ] You are prompted to install `rope` when running any of these commands if it is not already installed ### [Debugging](https://code.visualstudio.com/docs/python/debugging) From ff2fb120d904b1aeaf964d852c8f64d547e38dd4 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 23 Apr 2018 17:24:05 -0700 Subject: [PATCH 165/433] Make the rope installation check more explicit --- .github/test_plan.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/test_plan.md b/.github/test_plan.md index ec3e464ee3e8..608511c088f8 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -143,9 +143,10 @@ def foo():pass #### [Refactoring](https://code.visualstudio.com/docs/python/editing#_refactoring) - [ ] [`Extract Variable`](https://code.visualstudio.com/docs/python/editing#_extract-variable) works + - [ ] You are prompted to install `rope` if it is not already available - [ ] [`Extract method`](https://code.visualstudio.com/docs/python/editing#_extract-method) works + - [ ] You are prompted to install `rope` if it is not already available - [ ] [`Sort Imports`](https://code.visualstudio.com/docs/python/editing#_sort-imports) works -- [ ] You are prompted to install `rope` when running any of these commands if it is not already installed ### [Debugging](https://code.visualstudio.com/docs/python/debugging) From c9bf5ba7612e548ad498e79e709e5ce77a167168 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 23 Apr 2018 17:33:20 -0700 Subject: [PATCH 166/433] Use VS Code terminology --- .github/test_plan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/test_plan.md b/.github/test_plan.md index 608511c088f8..d6a3cb2e9e73 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -168,7 +168,7 @@ Test **both** old and new debugger (and notice if the new debugger seems _at lea - [ ] Breakpoints - [ ] Set - [ ] Hit - - [ ] Watch + - [ ] Conditional - [ ] Stepping - [ ] Over - [ ] Into From 8b7da2592001821666bd76fb77d7a1e37adb6eeb Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 23 Apr 2018 17:38:07 -0700 Subject: [PATCH 167/433] Break down conditional expressions --- .github/test_plan.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/test_plan.md b/.github/test_plan.md index d6a3cb2e9e73..e7819ef60cdf 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -169,6 +169,9 @@ Test **both** old and new debugger (and notice if the new debugger seems _at lea - [ ] Set - [ ] Hit - [ ] Conditional + - [ ] Expression + - [ ] Hit count + - [ ] Log points (experimental debugger only) - [ ] Stepping - [ ] Over - [ ] Into From 2e58f46c026c8d7ba49544e3049c94c974a9e3e8 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 23 Apr 2018 17:39:18 -0700 Subject: [PATCH 168/433] Tweak wording --- .github/test_plan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/test_plan.md b/.github/test_plan.md index e7819ef60cdf..48c51f286c66 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -164,7 +164,7 @@ Test **both** old and new debugger (and notice if the new debugger seems _at lea - [ ] `Watson` - [ ] `Scrapy` - [ ] `PySpark` - - [ ] `All debug Options` with [appropriate values](https://code.visualstudio.com/docs/python/debugging#_standard-configuration-and-options) changed + - [ ] `All debug Options` with [appropriate values](https://code.visualstudio.com/docs/python/debugging#_standard-configuration-and-options) edited to make values valid - [ ] Breakpoints - [ ] Set - [ ] Hit From 1e36ba18ae90715d96a3de86f67c3f6581e1787b Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 23 Apr 2018 17:41:26 -0700 Subject: [PATCH 169/433] Fix spelling mistake --- .github/test_plan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/test_plan.md b/.github/test_plan.md index 48c51f286c66..13944ee43e68 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -97,7 +97,7 @@ foo = 42 # Marked as a blacklisted name. - [ ] Installs into a virtual environment environment directly - [ ] Pylint works - [ ] `"python.linting.pylintUseMinimalCheckers": false` turns off the default rules w/ no `pylintrc` file present -- [ ] The existense of a `pylintrc` file turns off the default rules +- [ ] The existence of a `pylintrc` file turns off the default rules #### Other linters From 7fd3babdd45e7b2bfca5dbab98e1f9740adf776b Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 24 Apr 2018 11:11:44 -0700 Subject: [PATCH 170/433] Formatting tweak --- .github/test_plan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/test_plan.md b/.github/test_plan.md index 13944ee43e68..1f17ed7dacc5 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -219,7 +219,7 @@ def test_failure(): ``` - [ ] `Run All Unit Tests` triggers the prompt to configure the test runner - - [ ] Pytest gets installed + - [ ] `pytest` gets installed - [ ] Tests are discovered (as shown by code lenses on each test) #### [`nose`](https://code.visualstudio.com/docs/python/unit-testing#_nose-configuration-settings) From 0bae778ef86cce16e4a950c78a4e92cb3cd4e499 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 24 Apr 2018 11:21:50 -0700 Subject: [PATCH 171/433] Provide sys_path to jedi (#1471) Fixes #1445 Fixes #1469 Fixes #1460 --- pythonFiles/completion.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pythonFiles/completion.py b/pythonFiles/completion.py index 2a0d6e3d095b..ed74a830e095 100644 --- a/pythonFiles/completion.py +++ b/pythonFiles/completion.py @@ -551,7 +551,7 @@ def _process_request(self, request): self._normalize_request_path(request) path = self._get_top_level_module(request.get('path', '')) - if path not in sys.path: + if len(path) > 0 and path not in sys.path: sys.path.insert(0, path) lookup = request.get('lookup', 'completions') @@ -563,9 +563,10 @@ def _process_request(self, request): all_scopes=True), request['id']) - script = jedi.api.Script( + script = jedi.Script( source=request.get('source', None), line=request['line'] + 1, - column=request['column'], path=request.get('path', '')) + column=request['column'], path=request.get('path', ''), + sys_path=sys.path) if lookup == 'definitions': defs = [] From eefb2029aed99420c22435beb55c5f4dd3310122 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 24 Apr 2018 13:45:10 -0700 Subject: [PATCH 172/433] Ask for VS Code version --- .github/test_plan.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/test_plan.md b/.github/test_plan.md index 1f17ed7dacc5..2c60da7fa697 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -6,6 +6,7 @@ - Python - Distribution: XXX - Version: XXX +- VS Code version: XXX ## Tests From 8d76012f8c871b8506e3252b2c6f0eb7f92b9250 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 24 Apr 2018 13:49:07 -0700 Subject: [PATCH 173/433] Simplify some wording --- .github/test_plan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/test_plan.md b/.github/test_plan.md index 2c60da7fa697..c5c8199af19f 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -6,7 +6,7 @@ - Python - Distribution: XXX - Version: XXX -- VS Code version: XXX +- VS Code: XXX ## Tests From d069f8470c59b78d4bcc1f7da0424412b342fe08 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 24 Apr 2018 14:02:58 -0700 Subject: [PATCH 174/433] Mark a code block as `python` instead of `python3` --- .github/test_plan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/test_plan.md b/.github/test_plan.md index c5c8199af19f..2cf3227286fc 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -55,7 +55,7 @@ #### [Environment files](https://code.visualstudio.com/docs/python/environments#_environment-variable-definitions-file) Sample files: -```python3 +```python # example.py import os print('Hello,', os.environ.get('WHO'), '!') From 39622f21eb90c791246bc4bf3d6ae21701331a66 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 24 Apr 2018 14:18:54 -0700 Subject: [PATCH 175/433] Clarify some instructions --- .github/test_plan.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/test_plan.md b/.github/test_plan.md index 2cf3227286fc..333170acbfa9 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -29,9 +29,10 @@ #### Virtual environments -**ALWAYS create environments with a space in their name.*** +- **ALWAYS create an environment with a space in their path somewhere.** +- **For all automatic detection tests, make sure that you do not have `python.pythonPath` specified in your `settings.json`.** -- [ ] Detected a single virtual environment at the top-level of the workspace folder +- [ ] Detected a single virtual environment at the top-level of the workspace folder (if you created this _after_ opening VS Code, then run `Reload Window` to pick up the new environment) - [ ] Appropriate suffix label specified in status bar - [ ] [`Create Terminal`](https://code.visualstudio.com/docs/python/environments#_activating-an-environment-in-the-terminal) works - [ ] Steals focus @@ -66,7 +67,8 @@ WHO=world PYTHONPATH=some/path/somewhere ```` -**Make sure to use `Reload Window` between tests to reset your environment!** +- **Make sure to use `Reload Window` between tests to reset your environment!** +- **Note that environment files only apply under the debugger and Jedi.** - [ ] Environment variables in a `.env` file are exposed when running under the debugger - [ ] `"python.envFile"` allows for specifying an environment file manually (e.g. Jedi picks up `PYTHONPATH` changes) From a7217dd72d2eea1a775002f519c7e819f6618695 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 24 Apr 2018 14:30:01 -0700 Subject: [PATCH 176/433] Clean up ALWAYS notes --- .github/test_plan.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/test_plan.md b/.github/test_plan.md index 333170acbfa9..f3d596615dcb 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -10,7 +10,9 @@ ## Tests -**ALWAYS check the `Output` window under `Python` for logged errors!** +**ALWAYS**: +- Check the `Output` window under `Python` for logged errors +- Have `Developer Tools` open to detect any errors ### [Environment](https://code.visualstudio.com/docs/python/environments) #### Interpreters @@ -29,8 +31,9 @@ #### Virtual environments -- **ALWAYS create an environment with a space in their path somewhere.** -- **For all automatic detection tests, make sure that you do not have `python.pythonPath` specified in your `settings.json`.** +**ALWAYS**: +- Create an environment with a space in their path somewhere +- Make sure that you do not have `python.pythonPath` specified in your `settings.json` when testing automatic detection - [ ] Detected a single virtual environment at the top-level of the workspace folder (if you created this _after_ opening VS Code, then run `Reload Window` to pick up the new environment) - [ ] Appropriate suffix label specified in status bar @@ -67,8 +70,9 @@ WHO=world PYTHONPATH=some/path/somewhere ```` -- **Make sure to use `Reload Window` between tests to reset your environment!** -- **Note that environment files only apply under the debugger and Jedi.** +**ALWAYS**: +- Make sure to use `Reload Window` between tests to reset your environment +- Note that environment files only apply under the debugger and Jedi - [ ] Environment variables in a `.env` file are exposed when running under the debugger - [ ] `"python.envFile"` allows for specifying an environment file manually (e.g. Jedi picks up `PYTHONPATH` changes) @@ -80,7 +84,8 @@ PYTHONPATH=some/path/somewhere ### [Linting](https://code.visualstudio.com/docs/python/linting) -**ALWAYS check under the `Problems` tab to see e.g. if a linter is raising errors!** +**ALWAYS**: +- Check under the `Problems` tab to see e.g. if a linter is raising errors #### Pylint/default linting [Prompting to install Pylint is covered under `Environments` above] @@ -153,7 +158,9 @@ def foo():pass ### [Debugging](https://code.visualstudio.com/docs/python/debugging) -Test **both** old and new debugger (and notice if the new debugger seems _at least_ as fast as the old debugger). +**ALWAYS**: +- Test the current debugger +- Text the experimental debugger (and note whether it is _at least_ as fast as the old debugger) - [ ] [Configurations](https://code.visualstudio.com/docs/python/debugging#_debugging-specific-app-types) work - [ ] `Current File` From c4d6d5fcecfa5be89ed4fa8e456b81af79674254 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 24 Apr 2018 15:13:13 -0700 Subject: [PATCH 177/433] Clarify what venvFolders is for --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 74a1832f6939..69a02cc35ab1 100644 --- a/package.json +++ b/package.json @@ -1157,7 +1157,7 @@ ".pyenv", ".direnv" ], - "description": "Folders to look into for virtual environments.", + "description": "Folders in your home directory to look into for virtual environments.", "scope": "resource", "items": { "type": "string" From 1cea987d0c46b647fca98289b6755a8389fd6c11 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 24 Apr 2018 15:56:27 -0700 Subject: [PATCH 178/433] Make the tests a collapsed region --- .github/test_plan.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/test_plan.md b/.github/test_plan.md index f3d596615dcb..e6f3da6b893a 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -14,6 +14,9 @@ - Check the `Output` window under `Python` for logged errors - Have `Developer Tools` open to detect any errors +
+ Click to expand ... + ### [Environment](https://code.visualstudio.com/docs/python/environments) #### Interpreters @@ -258,3 +261,5 @@ def test_failure(): - [ ] `Run Unit Test Method ...` works - [ ] `View Unit Test Output` works - [ ] After having at least one failure, `Run Failed Tests` works + +
From 76f71a95a4d287fabe66e373a27d668dd1e580bd Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 24 Apr 2018 15:56:59 -0700 Subject: [PATCH 179/433] Tweak wording --- .github/test_plan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/test_plan.md b/.github/test_plan.md index e6f3da6b893a..841a39cb7821 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -15,7 +15,7 @@ - Have `Developer Tools` open to detect any errors
- Click to expand ... + Click to display/hide tests ... ### [Environment](https://code.visualstudio.com/docs/python/environments) #### Interpreters From ec6243fa4c96c12a62d88e3a05caa0add06d0553 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 24 Apr 2018 15:57:27 -0700 Subject: [PATCH 180/433] Tweak wording again --- .github/test_plan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/test_plan.md b/.github/test_plan.md index 841a39cb7821..d4ecff2e0aef 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -15,7 +15,7 @@ - Have `Developer Tools` open to detect any errors
- Click to display/hide tests ... + Scenarios ### [Environment](https://code.visualstudio.com/docs/python/environments) #### Interpreters From 38ac42468c5e3d0ba0da39165ad65e5037824cb4 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 24 Apr 2018 16:30:02 -0700 Subject: [PATCH 181/433] Clarify `activateEnvironment` setting --- .github/test_plan.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/test_plan.md b/.github/test_plan.md index d4ecff2e0aef..c4e3869a5779 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -37,28 +37,28 @@ **ALWAYS**: - Create an environment with a space in their path somewhere - Make sure that you do not have `python.pythonPath` specified in your `settings.json` when testing automatic detection +- Do note that the `Select Interpreter` drop-down window scrolls - [ ] Detected a single virtual environment at the top-level of the workspace folder (if you created this _after_ opening VS Code, then run `Reload Window` to pick up the new environment) - - [ ] Appropriate suffix label specified in status bar + - [ ] Appropriate suffix label specified in status bar (e.g. `(venv)`) - [ ] [`Create Terminal`](https://code.visualstudio.com/docs/python/environments#_activating-an-environment-in-the-terminal) works - [ ] Steals focus - - [ ] `"python.terminal.activateEnvironment": false` turns off automatic activation of the environment + - [ ] `"python.terminal.activateEnvironment": false` deactivates automatically running the activation script in the terminal - [ ] Detect multiple virtual environments in a directory specified by `"python.venvPath"` - [ ] Detected all [conda environments created with an interpreter](https://code.visualstudio.com/docs/python/environments#_conda-environments) - - [ ] Appropriate suffix label specified in status bar + - [ ] Appropriate suffix label specified in status bar (e.g. `(condaenv)`) - [ ] Prompted to install Pylint - [ ] Asked whether to install using conda or pip - [ ] Installs into environment - - [ ] `"python.terminal.activateEnvironments": false` deactivates detection - [ ] [`Create Terminal`](https://code.visualstudio.com/docs/python/environments#_activating-an-environment-in-the-terminal) works + - [ ] `"python.terminal.activateEnvironment": false` deactivates automatically running the activation script in the terminal - [ ] (Linux/macOS until [`-m` is supported](https://github.com/Microsoft/vscode-python/issues/978)) Detected the virtual environment created by [pipenv](https://docs.pipenv.org/) - - [ ] Appropriate suffix label specified in status bar + - [ ] Appropriate suffix label specified in status bar (e.g. `(pipenv)`) - [ ] Prompt to install Pylint uses `pipenv install --dev` - - [ ] `"python.terminal.activateEnvironments": false` deactivates detection - [ ] [`Create Terminal`](https://code.visualstudio.com/docs/python/environments#_activating-an-environment-in-the-terminal) works + - [ ] `"python.terminal.activateEnvironment": false` deactivates automatically running the activation script in the terminal - [ ] (Linux/macOS) Virtual environments created under `{workspaceFolder}/.direnv/python-{python_version}` are detected (for [direnv](https://direnv.net/) and its [`layout python3`](https://github.com/direnv/direnv/blob/master/stdlib.sh) support) - - [ ] Appropriate suffix label specified in status bar - - [ ] `"python.terminal.activateEnvironments": false` deactivates detection + - [ ] Appropriate suffix label specified in status bar (e.g. `(venv)`) #### [Environment files](https://code.visualstudio.com/docs/python/environments#_environment-variable-definitions-file) Sample files: From 52d1340f97deec48580ab5fe188b09ecbd943b94 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 24 Apr 2018 16:42:33 -0700 Subject: [PATCH 182/433] Better explain how to test the linters --- .github/test_plan.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/.github/test_plan.md b/.github/test_plan.md index c4e3869a5779..99200f6bff1e 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -112,20 +112,27 @@ foo = 42 # Marked as a blacklisted name. #### Other linters -You can always use the `Run Linting` command to immediately trigger a newly installed linter. +**Note**: +- You can use the `Run Linting` command to run a newly installed linter +- When the extension installs a new linter, it turns off all other linters - [ ] flake8 works + - [ ] `Select linter` lists the linter and installs it if necessary - [ ] mypy works + - [ ] `Select linter` lists the linter and installs it if necessary - [ ] pep8 works + - [ ] `Select linter` lists the linter and installs it if necessary - [ ] prospector works + - [ ] `Select linter` lists the linter and installs it if necessary - [ ] pydocstyle works + - [ ] `Select linter` lists the linter and installs it if necessary - [ ] pylama works -- [ ] 3 or more linters work simultaneously - - [ ] `Run Linting` runs all linters - - [ ] The `Select Linter` command lists all the above linters and prompts to install a linter when missing + - [ ] `Select linter` lists the linter and installs it if necessary +- [ ] 3 or more linters work simultaneously (make sure you have turned on the linters in your `settings.json`) + - [ ] `Run Linting` runs all activated linters - [ ] `"python.linting.enabled": false` disables all linters - [ ] The `Enable Linting` command changes `"python.linting.enabled"` - - [ ] `"python.linting.lintOnSave` works +- [ ] `"python.linting.lintOnSave` works ### [Editing](https://code.visualstudio.com/docs/python/editing) From 37022faa948f8224e1de4124a11f472aad863792 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 24 Apr 2018 16:53:46 -0700 Subject: [PATCH 183/433] Mention multi-folder workspaces --- .github/test_plan.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/test_plan.md b/.github/test_plan.md index 99200f6bff1e..c0ecbaf100c8 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -13,6 +13,7 @@ **ALWAYS**: - Check the `Output` window under `Python` for logged errors - Have `Developer Tools` open to detect any errors +- Consider running the tests in a multi-folder workspace
Scenarios From 7aed89dcfd03fefc760d9e0a7320a18fb6ab15e6 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 24 Apr 2018 17:34:43 -0700 Subject: [PATCH 184/433] Fix debug options for experimental debugger Fixes #1466 --- package.json | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 69a02cc35ab1..180c5c1caeff 100644 --- a/package.json +++ b/package.json @@ -788,10 +788,7 @@ "--noreload", "--nothreading" ], - "debugOptions": [ - "RedirectOutput", - "Django" - ] + "django": true } }, { @@ -810,10 +807,7 @@ "--no-debugger", "--no-reload" ], - "debugOptions": [ - "RedirectOutput", - "Jinja" - ] + "jinja": true } }, { @@ -876,9 +870,7 @@ "args": [ "^\"\\${workspaceFolder}/development.ini\"" ], - "debugOptions": [ - "Pyramid" - ] + "pyramid": true } }, { @@ -888,8 +880,6 @@ "name": "Attach (Remote Debug)", "type": "pythonExperimental", "request": "attach", - "localRoot": "^\"\\${workspaceFolder}\"", - "remoteRoot": "^\"\\${workspaceFolder}\"", "port": 3000, "host": "localhost" } @@ -1101,10 +1091,7 @@ "--noreload", "--nothreading" ], - "debugOptions": [ - "RedirectOutput", - "Django" - ] + "django": true }, { "name": "Python Experimental: Flask", @@ -1119,10 +1106,7 @@ "--no-debugger", "--no-reload" ], - "debugOptions": [ - "RedirectOutput", - "Jinja" - ] + "jinja": true }, { "name": "Python Experimental: Current File (External Terminal)", From 3f202f14b6e1181f77a0d6349e60252494341e22 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 25 Apr 2018 11:03:32 -0700 Subject: [PATCH 185/433] Check if python executable is valid before attempting to start the language server (#1488) Fixes #1487 --- news/2 Fixes/1487.md | 1 + src/client/providers/jediProxy.ts | 22 +++++++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 news/2 Fixes/1487.md diff --git a/news/2 Fixes/1487.md b/news/2 Fixes/1487.md new file mode 100644 index 000000000000..0d1bd5cfeb37 --- /dev/null +++ b/news/2 Fixes/1487.md @@ -0,0 +1 @@ +Check whether the selected python interpreter is valid before starting the language server. Failing to do so could result in the extension failing to load. diff --git a/src/client/providers/jediProxy.ts b/src/client/providers/jediProxy.ts index 7a8e47b62b1c..8bae330e49c9 100644 --- a/src/client/providers/jediProxy.ts +++ b/src/client/providers/jediProxy.ts @@ -305,10 +305,14 @@ export class JediProxy implements Disposable { // tslint:disable-next-line:max-func-body-length private async spawnProcess(cwd: string) { if (this.languageServerStarted && !this.languageServerStarted.completed) { - this.languageServerStarted.reject(); + this.languageServerStarted.reject(new Error('Language Server not started.')); } this.languageServerStarted = createDeferred(); const pythonProcess = await this.serviceContainer.get(IPythonExecutionFactory).create(Uri.file(this.workspacePath)); + // Check if the python path is valid. + if ((await pythonProcess.getExecutablePath().catch(() => '')).length === 0) { + return; + } const args = ['completion.py']; if (typeof this.pythonSettings.jediPath === 'string' && this.pythonSettings.jediPath.length > 0) { args.push('custom'); @@ -638,14 +642,14 @@ export class JediProxy implements Disposable { // Add support for paths relative to workspace. const extraPaths = this.pythonSettings.autoComplete ? this.pythonSettings.autoComplete.extraPaths.map(extraPath => { - if (path.isAbsolute(extraPath)) { - return extraPath; - } - if (typeof this.workspacePath !== 'string') { - return ''; - } - return path.join(this.workspacePath, extraPath); - }) : []; + if (path.isAbsolute(extraPath)) { + return extraPath; + } + if (typeof this.workspacePath !== 'string') { + return ''; + } + return path.join(this.workspacePath, extraPath); + }) : []; // Always add workspace path into extra paths. if (typeof this.workspacePath === 'string') { From bc3b57dbc0f710054c2dbc6ee52e50fdd1a953af Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 25 Apr 2018 15:36:31 -0700 Subject: [PATCH 186/433] Clicking 'Run Test' code lens for a test class should not run all tests in the file Fixes #1472 --- news/2 Fixes/1472.md | 1 + src/client/unittests/unittest/services/parserService.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 news/2 Fixes/1472.md diff --git a/news/2 Fixes/1472.md b/news/2 Fixes/1472.md new file mode 100644 index 000000000000..873641719abf --- /dev/null +++ b/news/2 Fixes/1472.md @@ -0,0 +1 @@ +Clicking the codelens `Run Test` on a test class should run that specific test class instead of all tests in the file. diff --git a/src/client/unittests/unittest/services/parserService.ts b/src/client/unittests/unittest/services/parserService.ts index 8c26d2ea05d0..dd746be00694 100644 --- a/src/client/unittests/unittest/services/parserService.ts +++ b/src/client/unittests/unittest/services/parserService.ts @@ -9,7 +9,7 @@ type UnitTestParserOptions = TestDiscoveryOptions & { startDirectory: string }; @injectable() export class TestsParser implements ITestsParser { - constructor( @inject(ITestsHelper) private testsHelper: ITestsHelper) { } + constructor(@inject(ITestsHelper) private testsHelper: ITestsHelper) { } public parse(content: string, options: UnitTestParserOptions): Tests { const testIds = this.getTestIds(content); let testsDirectory = options.cwd; @@ -83,7 +83,7 @@ export class TestsParser implements ITestsParser { suites: [] as TestSuite[], isUnitTest: true, isInstance: false, - nameToRun: classNameToRun, + nameToRun: `${path.parse(filePath).name}.${classNameToRun}`, xmlName: '', status: TestStatus.Idle, time: 0 From 82ae9706f01b63fcca65b65ffae5291d38edb9f7 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 25 Apr 2018 15:36:50 -0700 Subject: [PATCH 187/433] Remove unwanted pip dependencies and files (#1497) Fixes #1494 --- .travis.yml | 1 - news/3 Code Health/1494.md | 1 + requirements.txt | 2 -- src/test/pythonFiles/definition/decorators.py | 28 ------------------- 4 files changed, 1 insertion(+), 31 deletions(-) create mode 100644 news/3 Code Health/1494.md delete mode 100644 src/test/pythonFiles/definition/decorators.py diff --git a/.travis.yml b/.travis.yml index 52ae4e0b99f9..2c4a073a7db8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -40,7 +40,6 @@ before_install: | sh -e /etc/init.d/xvfb start; sleep 3; fi - git submodule update --init --recursive git clone https://github.com/creationix/nvm.git ./.nvm source ./.nvm/nvm.sh nvm install 8.9.1 diff --git a/news/3 Code Health/1494.md b/news/3 Code Health/1494.md new file mode 100644 index 000000000000..a23e50e189ea --- /dev/null +++ b/news/3 Code Health/1494.md @@ -0,0 +1 @@ +Remove unwanted python packages no longer used in unit tests. diff --git a/requirements.txt b/requirements.txt index 40d8dae202cb..71762bf0eae3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,8 +9,6 @@ prospector pydocstyle nose pytest -fabric -numba rope flask django diff --git a/src/test/pythonFiles/definition/decorators.py b/src/test/pythonFiles/definition/decorators.py deleted file mode 100644 index 6d772479b55d..000000000000 --- a/src/test/pythonFiles/definition/decorators.py +++ /dev/null @@ -1,28 +0,0 @@ -def identity(ob): - return ob - -@identity -def myfunc(): - print "my function" - -myfunc() - -# https://github.com/DonJayamanne/pythonVSCode/issues/1046 -from fabric.api import sudo -# currently go to definition of sudo will go to some decorator function -# works, if fabric package is not installed -sudo() - -from numba import jit - -# https://github.com/DonJayamanne/pythonVSCode/issues/478 -@jit() -def calculate_cash_flows(remaining_loan_term, remaining_io_term, - settle_balance, settle_date, payment_day, - ir_fixed, ir_accrual_day_count_basis, - amortizing_debt_service): - print("") - -# currently go to definition of sudo will go to some decorator function -# works, if fabric package is not installed -calculate_cash_flows(1,2,3,4,5,6,7,8) From da5f1459812c04e57fa9b06af2a8097d6bcf36f8 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 25 Apr 2018 15:37:06 -0700 Subject: [PATCH 188/433] Add conditional clauses for the keyboard shortcut of the command python.execSelectionInTerminal (#1498) Fixes #1493 --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 180c5c1caeff..6bc2960275f4 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,8 @@ "keybindings":[ { "command": "python.execSelectionInTerminal", - "key": "ctrl+enter" + "key": "ctrl+enter", + "when": "editorFocus && editorHasSelection && editorLangId == python" } ], "commands": [ From 645566cc26da7812ce1a7b7bd080c115a724ec75 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 25 Apr 2018 15:37:32 -0700 Subject: [PATCH 189/433] Run specific test in Nose instead of running all tests Fixes #1473 --- news/2 Fixes/1473.md | 1 + src/client/unittests/nosetest/runner.ts | 5 +++++ 2 files changed, 6 insertions(+) create mode 100644 news/2 Fixes/1473.md diff --git a/news/2 Fixes/1473.md b/news/2 Fixes/1473.md new file mode 100644 index 000000000000..80df87dc31df --- /dev/null +++ b/news/2 Fixes/1473.md @@ -0,0 +1 @@ +Clicking the codelens `Run Test` on a test class or method should run that specific test instead of all tests in the file. diff --git a/src/client/unittests/nosetest/runner.ts b/src/client/unittests/nosetest/runner.ts index 512f18b2a151..64a170dd5150 100644 --- a/src/client/unittests/nosetest/runner.ts +++ b/src/client/unittests/nosetest/runner.ts @@ -72,6 +72,11 @@ export function runTest(serviceContainer: IServiceContainer, testResultsService: token: options.token, workspaceFolder: options.workspaceFolder }; + + // Remove the directory argument, as we'll provide tests to be run. + if (testPaths.length > 0 && runOptions.args.length > 0 && !runOptions.args[0].trim().startsWith('-')) { + runOptions.args.shift(); + } return run(serviceContainer, 'nosetest', runOptions); } }).then(() => { From 5d22ace961cce347f390c4cffb08d6472ab1797b Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 26 Apr 2018 10:39:15 -0700 Subject: [PATCH 190/433] Alphabetize configuration options (#1502) --- package.json | 671 ++++++++++++++++++++++++++------------------------- 1 file changed, 336 insertions(+), 335 deletions(-) diff --git a/package.json b/package.json index 6bc2960275f4..7abba1b72240 100644 --- a/package.json +++ b/package.json @@ -1123,62 +1123,37 @@ "type": "object", "title": "Python Configuration", "properties": { - "python.pythonPath": { - "type": "string", - "default": "python", - "description": "Path to Python, you can use a custom version of Python by modifying this setting to include the full path.", + "python.autoComplete.addBrackets": { + "type": "boolean", + "default": false, + "description": "Automatically add brackets for functions.", "scope": "resource" }, - "python.venvPath": { - "type": "string", - "default": "", - "description": "Path to folder with a list of Virtual Environments (e.g. ~/.pyenv, ~/Envs, ~/.virtualenvs).", + "python.autoComplete.extraPaths": { + "type": "array", + "default": [], + "description": "List of paths to libraries and the like that need to be imported by auto complete engine. E.g. when using Google App SDK, the paths are not in system path, hence need to be added into this list.", "scope": "resource" }, - "python.venvFolders": { + "python.autoComplete.preloadModules": { "type": "array", - "default": [ - "envs", - ".pyenv", - ".direnv" - ], - "description": "Folders in your home directory to look into for virtual environments.", - "scope": "resource", "items": { "type": "string" - } - }, - "python.envFile": { - "type": "string", - "description": "Absolute path to a file containing environment variable definitions.", - "default": "${workspaceFolder}/.env", - "scope": "resource" - }, - "python.jediPath": { - "type": "string", - "default": "", - "description": "Path to directory containing the Jedi library (this path will contain the 'Jedi' sub directory).", - "scope": "resource" - }, - "python.jediMemoryLimit": { - "type": "number", - "default": 0, - "description": "Memory limit for the Jedi completion engine in megabytes. Zero (default) means 1024 MB. -1 means unlimited (disable memory limit check)", + }, + "default": [], + "description": "Comma delimited list of modules preloaded to speed up Auto Complete (e.g. add Numpy, Pandas, etc, items slow to load when autocompleting).", "scope": "resource" }, - "python.sortImports.path": { - "type": "string", - "description": "Path to isort script, default using inner version", - "default": "", + "python.autoComplete.showAdvancedMembers": { + "type": "boolean", + "default": false, + "description": "Controls appearance of methods with double underscores in the completion list.", "scope": "resource" }, - "python.sortImports.args": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, + "python.disableInstallationCheck": { + "type": "boolean", + "default": false, + "description": "Whether to check if Python is installed.", "scope": "resource" }, "python.disablePromptForFeatures": { @@ -1201,82 +1176,90 @@ }, "scope": "resource" }, - "python.disableInstallationCheck": { - "type": "boolean", - "default": false, - "description": "Whether to check if Python is installed.", + "python.envFile": { + "type": "string", + "description": "Absolute path to a file containing environment variable definitions.", + "default": "${workspaceFolder}/.env", "scope": "resource" }, - "python.globalModuleInstallation": { - "type": "boolean", - "default": false, - "description": "Whether to install Python modules globally.", + "python.formatting.autopep8Args": { + "type": "array", + "description": "Arguments passed in. Each argument is a separate item in the array.", + "default": [], + "items": { + "type": "string" + }, "scope": "resource" }, - "python.linting.enabled": { - "type": "boolean", - "default": true, - "description": "Whether to lint Python files.", + "python.formatting.autopep8Path": { + "type": "string", + "default": "autopep8", + "description": "Path to autopep8, you can use a custom version of autopep8 by modifying this setting to include the full path.", "scope": "resource" }, - "python.linting.prospectorEnabled": { - "type": "boolean", - "default": false, - "description": "Whether to lint Python files using prospector.", + "python.formatting.provider": { + "type": "string", + "default": "autopep8", + "description": "Provider for formatting. Possible options include 'autopep8' and 'yapf'.", + "enum": [ + "autopep8", + "yapf", + "none" + ], "scope": "resource" }, - "python.linting.pylintEnabled": { - "type": "boolean", - "default": true, - "description": "Whether to lint Python files using pylint.", + "python.formatting.yapfArgs": { + "type": "array", + "description": "Arguments passed in. Each argument is a separate item in the array.", + "default": [], + "items": { + "type": "string" + }, "scope": "resource" }, - "python.linting.pep8Enabled": { - "type": "boolean", - "default": false, - "description": "Whether to lint Python files using pep8", + "python.formatting.yapfPath": { + "type": "string", + "default": "yapf", + "description": "Path to yapf, you can use a custom version of yapf by modifying this setting to include the full path.", "scope": "resource" }, - "python.linting.flake8Enabled": { + "python.globalModuleInstallation": { "type": "boolean", "default": false, - "description": "Whether to lint Python files using flake8", + "description": "Whether to install Python modules globally.", "scope": "resource" }, - "python.linting.pydocstyleEnabled": { - "type": "boolean", - "default": false, - "description": "Whether to lint Python files using pydocstyle", + "python.jediMemoryLimit": { + "type": "number", + "default": 0, + "description": "Memory limit for the Jedi completion engine in megabytes. Zero (default) means 1024 MB. -1 means unlimited (disable memory limit check)", "scope": "resource" }, - "python.linting.mypyEnabled": { - "type": "boolean", - "default": false, - "description": "Whether to lint Python files using mypy.", + "python.jediPath": { + "type": "string", + "default": "", + "description": "Path to directory containing the Jedi library (this path will contain the 'Jedi' sub directory).", "scope": "resource" }, - "python.linting.lintOnSave": { + "python.linting.enabled": { "type": "boolean", "default": true, - "description": "Whether to lint Python files when saved.", - "scope": "resource" - }, - "python.linting.maxNumberOfProblems": { - "type": "number", - "default": 100, - "description": "Controls the maximum number of problems produced by the server.", + "description": "Whether to lint Python files.", "scope": "resource" }, - "python.linting.pylintUseMinimalCheckers": { - "type": "boolean", - "default": true, - "description": "Whether to run Pylint with minimal set of rules.", + "python.linting.flake8Args": { + "type": "array", + "description": "Arguments passed in. Each argument is a separate item in the array.", + "default": [], + "items": { + "type": "string" + }, "scope": "resource" }, - "python.linting.pylintCategorySeverity.convention": { + "python.linting.flake8CategorySeverity.E": { "type": "string", - "default": "Information", - "description": "Severity of Pylint message type 'Convention/C'.", + "default": "Error", + "description": "Severity of Flake8 message type 'E'.", "enum": [ "Hint", "Error", @@ -1285,10 +1268,10 @@ ], "scope": "resource" }, - "python.linting.pylintCategorySeverity.refactor": { + "python.linting.flake8CategorySeverity.F": { "type": "string", - "default": "Hint", - "description": "Severity of Pylint message type 'Refactor/R'.", + "default": "Error", + "description": "Severity of Flake8 message type 'F'.", "enum": [ "Hint", "Error", @@ -1297,10 +1280,10 @@ ], "scope": "resource" }, - "python.linting.pylintCategorySeverity.warning": { + "python.linting.flake8CategorySeverity.W": { "type": "string", "default": "Warning", - "description": "Severity of Pylint message type 'Warning/W'.", + "description": "Severity of Flake8 message type 'W'.", "enum": [ "Hint", "Error", @@ -1309,58 +1292,58 @@ ], "scope": "resource" }, - "python.linting.pylintCategorySeverity.error": { - "type": "string", - "default": "Error", - "description": "Severity of Pylint message type 'Error/E'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ], + "python.linting.flake8Enabled": { + "type": "boolean", + "default": false, + "description": "Whether to lint Python files using flake8", "scope": "resource" }, - "python.linting.pylintCategorySeverity.fatal": { + "python.linting.flake8Path": { "type": "string", - "default": "Error", - "description": "Severity of Pylint message type 'Fatal/F'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ], + "default": "flake8", + "description": "Path to flake8, you can use a custom version of flake8 by modifying this setting to include the full path.", "scope": "resource" }, - "python.linting.pep8CategorySeverity.W": { - "type": "string", - "default": "Warning", - "description": "Severity of Pep8 message type 'W'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" + "python.linting.ignorePatterns": { + "type": "array", + "description": "Patterns used to exclude files or folders from being linted.", + "default": [ + ".vscode/*.py", + "**/site-packages/**/*.py" ], + "items": { + "type": "string" + }, "scope": "resource" }, - "python.linting.pep8CategorySeverity.E": { - "type": "string", - "default": "Error", - "description": "Severity of Pep8 message type 'E'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" + "python.linting.lintOnSave": { + "type": "boolean", + "default": true, + "description": "Whether to lint Python files when saved.", + "scope": "resource" + }, + "python.linting.maxNumberOfProblems": { + "type": "number", + "default": 100, + "description": "Controls the maximum number of problems produced by the server.", + "scope": "resource" + }, + "python.linting.mypyArgs": { + "type": "array", + "description": "Arguments passed in. Each argument is a separate item in the array.", + "default": [ + "--ignore-missing-imports", + "--follow-imports=silent" ], + "items": { + "type": "string" + }, "scope": "resource" }, - "python.linting.flake8CategorySeverity.F": { + "python.linting.mypyCategorySeverity.error": { "type": "string", "default": "Error", - "description": "Severity of Flake8 message type 'F'.", + "description": "Severity of Mypy message type 'Error'.", "enum": [ "Hint", "Error", @@ -1369,10 +1352,10 @@ ], "scope": "resource" }, - "python.linting.flake8CategorySeverity.E": { + "python.linting.mypyCategorySeverity.note": { "type": "string", - "default": "Error", - "description": "Severity of Flake8 message type 'E'.", + "default": "Information", + "description": "Severity of Mypy message type 'Note'.", "enum": [ "Hint", "Error", @@ -1381,22 +1364,31 @@ ], "scope": "resource" }, - "python.linting.flake8CategorySeverity.W": { + "python.linting.mypyEnabled": { + "type": "boolean", + "default": false, + "description": "Whether to lint Python files using mypy.", + "scope": "resource" + }, + "python.linting.mypyPath": { "type": "string", - "default": "Warning", - "description": "Severity of Flake8 message type 'W'.", - "enum": [ - "Hint", - "Error", - "Information", - "Warning" - ], + "default": "mypy", + "description": "Path to mypy, you can use a custom version of mypy by modifying this setting to include the full path.", "scope": "resource" }, - "python.linting.mypyCategorySeverity.error": { + "python.linting.pep8Args": { + "type": "array", + "description": "Arguments passed in. Each argument is a separate item in the array.", + "default": [], + "items": { + "type": "string" + }, + "scope": "resource" + }, + "python.linting.pep8CategorySeverity.E": { "type": "string", "default": "Error", - "description": "Severity of Mypy message type 'Error'.", + "description": "Severity of Pep8 message type 'E'.", "enum": [ "Hint", "Error", @@ -1405,10 +1397,10 @@ ], "scope": "resource" }, - "python.linting.mypyCategorySeverity.note": { + "python.linting.pep8CategorySeverity.W": { "type": "string", - "default": "Information", - "description": "Severity of Mypy message type 'Note'.", + "default": "Warning", + "description": "Severity of Pep8 message type 'W'.", "enum": [ "Hint", "Error", @@ -1417,16 +1409,10 @@ ], "scope": "resource" }, - "python.linting.prospectorPath": { - "type": "string", - "default": "prospector", - "description": "Path to Prospector, you can use a custom version of prospector by modifying this setting to include the full path.", - "scope": "resource" - }, - "python.linting.pylintPath": { - "type": "string", - "default": "pylint", - "description": "Path to Pylint, you can use a custom version of pylint by modifying this setting to include the full path.", + "python.linting.pep8Enabled": { + "type": "boolean", + "default": false, + "description": "Whether to lint Python files using pep8", "scope": "resource" }, "python.linting.pep8Path": { @@ -1435,24 +1421,6 @@ "description": "Path to pep8, you can use a custom version of pep8 by modifying this setting to include the full path.", "scope": "resource" }, - "python.linting.flake8Path": { - "type": "string", - "default": "flake8", - "description": "Path to flake8, you can use a custom version of flake8 by modifying this setting to include the full path.", - "scope": "resource" - }, - "python.linting.pydocstylePath": { - "type": "string", - "default": "pydocstyle", - "description": "Path to pydocstyle, you can use a custom version of pydocstyle by modifying this setting to include the full path.", - "scope": "resource" - }, - "python.linting.mypyPath": { - "type": "string", - "default": "mypy", - "description": "Path to mypy, you can use a custom version of mypy by modifying this setting to include the full path.", - "scope": "resource" - }, "python.linting.prospectorArgs": { "type": "array", "description": "Arguments passed in. Each argument is a separate item in the array.", @@ -1462,31 +1430,16 @@ }, "scope": "resource" }, - "python.linting.pylintArgs": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.linting.pep8Args": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, + "python.linting.prospectorEnabled": { + "type": "boolean", + "default": false, + "description": "Whether to lint Python files using prospector.", "scope": "resource" }, - "python.linting.flake8Args": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, + "python.linting.prospectorPath": { + "type": "string", + "default": "prospector", + "description": "Path to Prospector, you can use a custom version of prospector by modifying this setting to include the full path.", "scope": "resource" }, "python.linting.pydocstyleArgs": { @@ -1498,42 +1451,19 @@ }, "scope": "resource" }, - "python.linting.mypyArgs": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [ - "--ignore-missing-imports", - "--follow-imports=silent" - ], - "items": { - "type": "string" - }, - "scope": "resource" - }, - "python.formatting.provider": { - "type": "string", - "default": "autopep8", - "description": "Provider for formatting. Possible options include 'autopep8' and 'yapf'.", - "enum": [ - "autopep8", - "yapf", - "none" - ], - "scope": "resource" - }, - "python.formatting.autopep8Path": { - "type": "string", - "default": "autopep8", - "description": "Path to autopep8, you can use a custom version of autopep8 by modifying this setting to include the full path.", + "python.linting.pydocstyleEnabled": { + "type": "boolean", + "default": false, + "description": "Whether to lint Python files using pydocstyle", "scope": "resource" }, - "python.formatting.yapfPath": { + "python.linting.pydocstylePath": { "type": "string", - "default": "yapf", - "description": "Path to yapf, you can use a custom version of yapf by modifying this setting to include the full path.", + "default": "pydocstyle", + "description": "Path to pydocstyle, you can use a custom version of pydocstyle by modifying this setting to include the full path.", "scope": "resource" }, - "python.formatting.autopep8Args": { + "python.linting.pylamaArgs": { "type": "array", "description": "Arguments passed in. Each argument is a separate item in the array.", "default": [], @@ -1542,7 +1472,19 @@ }, "scope": "resource" }, - "python.formatting.yapfArgs": { + "python.linting.pylamaEnabled": { + "type": "boolean", + "default": false, + "description": "Whether to lint Python files using pylama.", + "scope": "resource" + }, + "python.linting.pylamaPath": { + "type": "string", + "default": "pylama", + "description": "Path to pylama, you can use a custom version of pylama by modifying this setting to include the full path.", + "scope": "resource" + }, + "python.linting.pylintArgs": { "type": "array", "description": "Arguments passed in. Each argument is a separate item in the array.", "default": [], @@ -1551,96 +1493,121 @@ }, "scope": "resource" }, - "python.autoComplete.preloadModules": { - "type": "array", - "items": { - "type": "string" - }, - "default": [], - "description": "Comma delimited list of modules preloaded to speed up Auto Complete (e.g. add Numpy, Pandas, etc, items slow to load when autocompleting).", + "python.linting.pylintCategorySeverity.convention": { + "type": "string", + "default": "Information", + "description": "Severity of Pylint message type 'Convention/C'.", + "enum": [ + "Hint", + "Error", + "Information", + "Warning" + ], "scope": "resource" }, - "python.autoComplete.extraPaths": { - "type": "array", - "default": [], - "description": "List of paths to libraries and the like that need to be imported by auto complete engine. E.g. when using Google App SDK, the paths are not in system path, hence need to be added into this list.", + "python.linting.pylintCategorySeverity.error": { + "type": "string", + "default": "Error", + "description": "Severity of Pylint message type 'Error/E'.", + "enum": [ + "Hint", + "Error", + "Information", + "Warning" + ], "scope": "resource" }, - "python.autoComplete.addBrackets": { - "type": "boolean", - "default": false, - "description": "Automatically add brackets for functions.", + "python.linting.pylintCategorySeverity.fatal": { + "type": "string", + "default": "Error", + "description": "Severity of Pylint message type 'Fatal/F'.", + "enum": [ + "Hint", + "Error", + "Information", + "Warning" + ], "scope": "resource" }, - "python.autoComplete.showAdvancedMembers": { - "type": "boolean", - "default": false, - "description": "Controls appearance of methods with double underscores in the completion list.", + "python.linting.pylintCategorySeverity.refactor": { + "type": "string", + "default": "Hint", + "description": "Severity of Pylint message type 'Refactor/R'.", + "enum": [ + "Hint", + "Error", + "Information", + "Warning" + ], "scope": "resource" }, - "python.workspaceSymbols.tagFilePath": { + "python.linting.pylintCategorySeverity.warning": { "type": "string", - "default": "${workspaceFolder}/.vscode/tags", - "description": "Fully qualified path to tag file (exuberant ctag file), used to provide workspace symbols.", + "default": "Warning", + "description": "Severity of Pylint message type 'Warning/W'.", + "enum": [ + "Hint", + "Error", + "Information", + "Warning" + ], "scope": "resource" }, - "python.workspaceSymbols.enabled": { + "python.linting.pylintEnabled": { "type": "boolean", "default": true, - "description": "Set to 'false' to disable Workspace Symbol provider using ctags.", + "description": "Whether to lint Python files using pylint.", "scope": "resource" }, - "python.workspaceSymbols.rebuildOnStart": { - "type": "boolean", - "default": true, - "description": "Whether to re-build the tags file on start (defaults to true).", + "python.linting.pylintPath": { + "type": "string", + "default": "pylint", + "description": "Path to Pylint, you can use a custom version of pylint by modifying this setting to include the full path.", "scope": "resource" }, - "python.workspaceSymbols.rebuildOnFileSave": { + "python.linting.pylintUseMinimalCheckers": { "type": "boolean", "default": true, - "description": "Whether to re-build the tags file on when changes made to python files are saved.", + "description": "Whether to run Pylint with minimal set of rules.", "scope": "resource" }, - "python.workspaceSymbols.ctagsPath": { + "python.pythonPath": { "type": "string", - "default": "ctags", - "description": "Fully qualilified path to the ctags executable (else leave as ctags, assuming it is in current path).", + "default": "python", + "description": "Path to Python, you can use a custom version of Python by modifying this setting to include the full path.", "scope": "resource" }, - "python.workspaceSymbols.exclusionPatterns": { + "python.sortImports.args": { "type": "array", - "default": [ - "**/site-packages/**" - ], + "description": "Arguments passed in. Each argument is a separate item in the array.", + "default": [], "items": { "type": "string" }, - "description": "Pattern used to exclude files and folders from ctags See http://ctags.sourceforge.net/ctags.html.", "scope": "resource" }, - "python.unitTest.useExperimentalDebugger": { - "type": "boolean", - "default": false, - "description": "Use the experimental debugger when debugging unit tests.", + "python.sortImports.path": { + "type": "string", + "description": "Path to isort script, default using inner version", + "default": "", "scope": "resource" }, - "python.unitTest.promptToConfigure": { + "python.terminal.activateEnvironment": { "type": "boolean", "default": true, - "description": "Where to prompt to configure a test framework if potential tests directories are discovered.", + "description": "Activate Python Environment in Terminal created using the Extension.", "scope": "resource" }, - "python.unitTest.debugPort": { - "type": "number", - "default": 3000, - "description": "Port number used for debugging of unittests.", + "python.terminal.executeInFileDir": { + "type": "boolean", + "default": false, + "description": "When executing a file in the terminal, whether to use execute in the file's directory, instead of the current open folder.", "scope": "resource" }, - "python.unitTest.debugHost": { - "type": "number", - "default": "localhost", - "description": "IP Address of the of the local unit test server (default is localhost or use 127.0.0.1).", + "python.terminal.launchArgs": { + "type": "array", + "default": [], + "description": "Python launch arguments to use when executing a file in the terminal.", "scope": "resource" }, "python.unitTest.cwd": { @@ -1649,6 +1616,27 @@ "description": "Optional working directory for unit tests.", "scope": "resource" }, + "python.unitTest.debugHost": { + "type": "number", + "default": "localhost", + "description": "IP Address of the of the local unit test server (default is localhost or use 127.0.0.1).", + "scope": "resource" + }, + "python.unitTest.debugPort": { + "type": "number", + "default": 3000, + "description": "Port number used for debugging of unittests.", + "scope": "resource" + }, + "python.unitTest.nosetestArgs": { + "type": "array", + "description": "Arguments passed in. Each argument is a separate item in the array.", + "default": [], + "items": { + "type": "string" + }, + "scope": "resource" + }, "python.unitTest.nosetestsEnabled": { "type": "boolean", "default": false, @@ -1661,25 +1649,10 @@ "description": "Path to nosetests, you can use a custom version of nosetests by modifying this setting to include the full path.", "scope": "resource" }, - "python.unitTest.pyTestEnabled": { + "python.unitTest.promptToConfigure": { "type": "boolean", - "default": false, - "description": "Whether to enable or disable unit testing using pytest.", - "scope": "resource" - }, - "python.unitTest.pyTestPath": { - "type": "string", - "default": "pytest", - "description": "Path to pytest (pytest), you can use a custom version of pytest by modifying this setting to include the full path.", - "scope": "resource" - }, - "python.unitTest.nosetestArgs": { - "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], - "items": { - "type": "string" - }, + "default": true, + "description": "Where to prompt to configure a test framework if potential tests directories are discovered.", "scope": "resource" }, "python.unitTest.pyTestArgs": { @@ -1691,10 +1664,16 @@ }, "scope": "resource" }, - "python.unitTest.unittestEnabled": { + "python.unitTest.pyTestEnabled": { "type": "boolean", "default": false, - "description": "Whether to enable or disable unit testing using unittest.", + "description": "Whether to enable or disable unit testing using pytest.", + "scope": "resource" + }, + "python.unitTest.pyTestPath": { + "type": "string", + "default": "pytest", + "description": "Path to pytest (pytest), you can use a custom version of pytest by modifying this setting to include the full path.", "scope": "resource" }, "python.unitTest.unittestArgs": { @@ -1712,55 +1691,77 @@ }, "scope": "resource" }, - "python.linting.ignorePatterns": { + "python.unitTest.unittestEnabled": { + "type": "boolean", + "default": false, + "description": "Whether to enable or disable unit testing using unittest.", + "scope": "resource" + }, + "python.unitTest.useExperimentalDebugger": { + "type": "boolean", + "default": false, + "description": "Use the experimental debugger when debugging unit tests.", + "scope": "resource" + }, + "python.venvFolders": { "type": "array", - "description": "Patterns used to exclude files or folders from being linted.", "default": [ - ".vscode/*.py", - "**/site-packages/**/*.py" + "envs", + ".pyenv", + ".direnv" ], + "description": "Folders in your home directory to look into for virtual environments.", + "scope": "resource", "items": { "type": "string" - }, - "scope": "resource" + } }, - "python.linting.pylamaEnabled": { - "type": "boolean", - "default": false, - "description": "Whether to lint Python files using pylama.", + "python.venvPath": { + "type": "string", + "default": "", + "description": "Path to folder with a list of Virtual Environments (e.g. ~/.pyenv, ~/Envs, ~/.virtualenvs).", "scope": "resource" }, - "python.linting.pylamaPath": { + "python.workspaceSymbols.ctagsPath": { "type": "string", - "default": "pylama", - "description": "Path to pylama, you can use a custom version of pylama by modifying this setting to include the full path.", + "default": "ctags", + "description": "Fully qualilified path to the ctags executable (else leave as ctags, assuming it is in current path).", "scope": "resource" }, - "python.linting.pylamaArgs": { + "python.workspaceSymbols.enabled": { + "type": "boolean", + "default": true, + "description": "Set to 'false' to disable Workspace Symbol provider using ctags.", + "scope": "resource" + }, + "python.workspaceSymbols.exclusionPatterns": { "type": "array", - "description": "Arguments passed in. Each argument is a separate item in the array.", - "default": [], + "default": [ + "**/site-packages/**" + ], "items": { "type": "string" }, + "description": "Pattern used to exclude files and folders from ctags See http://ctags.sourceforge.net/ctags.html.", "scope": "resource" }, - "python.terminal.executeInFileDir": { + "python.workspaceSymbols.rebuildOnFileSave": { "type": "boolean", - "default": false, - "description": "When executing a file in the terminal, whether to use execute in the file's directory, instead of the current open folder.", + "default": true, + "description": "Whether to re-build the tags file on when changes made to python files are saved.", "scope": "resource" }, - "python.terminal.activateEnvironment": { + "python.workspaceSymbols.rebuildOnStart": { "type": "boolean", "default": true, - "description": "Activate Python Environment in Terminal created using the Extension.", + "description": "Whether to re-build the tags file on start (defaults to true).", "scope": "resource" }, - "python.terminal.launchArgs": { - "type": "array", - "default": [], - "description": "Python launch arguments to use when executing a file in the terminal.", + + "python.workspaceSymbols.tagFilePath": { + "type": "string", + "default": "${workspaceFolder}/.vscode/tags", + "description": "Fully qualified path to tag file (exuberant ctag file), used to provide workspace symbols.", "scope": "resource" } } From a6254cf7c6faa067cd8ea0334862c85402205d0c Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 26 Apr 2018 12:07:07 -0700 Subject: [PATCH 191/433] Fix debugging of pyramid applications Fixes #1467 Fixes #737 --- news/2 Fixes/737.md | 1 + .../debugger/configProviders/baseProvider.ts | 23 ++------ .../configurationProviderUtils.ts | 39 ++++++++++++ .../configProviders/pythonProvider.ts | 14 ++++- .../configProviders/pythonV2Provider.ts | 13 ++-- .../configProviders/serviceRegistry.ts | 3 + src/client/debugger/configProviders/types.ts | 12 ++++ .../debugger/configProvider/provider.test.ts | 59 ++++++++++++++----- 8 files changed, 125 insertions(+), 39 deletions(-) create mode 100644 news/2 Fixes/737.md create mode 100644 src/client/debugger/configProviders/configurationProviderUtils.ts create mode 100644 src/client/debugger/configProviders/types.ts diff --git a/news/2 Fixes/737.md b/news/2 Fixes/737.md new file mode 100644 index 000000000000..e72295893f94 --- /dev/null +++ b/news/2 Fixes/737.md @@ -0,0 +1 @@ +Fix debugging of Pyramid applications on Windows. diff --git a/src/client/debugger/configProviders/baseProvider.ts b/src/client/debugger/configProviders/baseProvider.ts index d1395d8dda6b..744dca9c4ea6 100644 --- a/src/client/debugger/configProviders/baseProvider.ts +++ b/src/client/debugger/configProviders/baseProvider.ts @@ -7,10 +7,9 @@ import { injectable, unmanaged } from 'inversify'; import * as path from 'path'; -import { CancellationToken, DebugConfiguration, DebugConfigurationProvider, ProviderResult, Uri, WorkspaceFolder } from 'vscode'; +import { CancellationToken, DebugConfiguration, DebugConfigurationProvider, Uri, WorkspaceFolder } from 'vscode'; import { IDocumentManager, IWorkspaceService } from '../../common/application/types'; import { PythonLanguage } from '../../common/constants'; -import { IFileSystem, IPlatformService } from '../../common/platform/types'; import { IConfigurationService } from '../../common/types'; import { IServiceContainer } from '../../ioc/types'; import { BaseAttachRequestArguments, BaseLaunchRequestArguments, DebuggerType, DebugOptions } from '../Common/Contracts'; @@ -21,11 +20,11 @@ export type PythonAttachDebugConfiguration @injectable() export abstract class BaseConfigurationProvider implements DebugConfigurationProvider { constructor(@unmanaged() public debugType: DebuggerType, protected serviceContainer: IServiceContainer) { } - public resolveDebugConfiguration(folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, token?: CancellationToken): ProviderResult { + public async resolveDebugConfiguration(folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, token?: CancellationToken): Promise { const workspaceFolder = this.getWorkspaceFolder(folder); if (debugConfiguration.request === 'attach') { - this.provideAttachDefaults(workspaceFolder, debugConfiguration as PythonAttachDebugConfiguration); + await this.provideAttachDefaults(workspaceFolder, debugConfiguration as PythonAttachDebugConfiguration); } else { const config = debugConfiguration as PythonLaunchDebugConfiguration; const numberOfSettings = Object.keys(config); @@ -40,7 +39,7 @@ export abstract class BaseConfigurationProvider): void { + protected async provideAttachDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonAttachDebugConfiguration): Promise { if (!Array.isArray(debugConfiguration.debugOptions)) { debugConfiguration.debugOptions = []; } @@ -57,7 +56,7 @@ export abstract class BaseConfigurationProvider): void { + protected async provideLaunchDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonLaunchDebugConfiguration): Promise { this.resolveAndUpdatePythonPath(workspaceFolder, debugConfiguration); if (typeof debugConfiguration.cwd !== 'string' && workspaceFolder) { debugConfiguration.cwd = workspaceFolder.fsPath; @@ -83,16 +82,6 @@ export abstract class BaseConfigurationProvider= 0) { - const platformService = this.serviceContainer.get(IPlatformService); - const fs = this.serviceContainer.get(IFileSystem); - const pserve = platformService.isWindows ? 'pserve.exe' : 'pserve'; - if (fs.fileExistsSync(debugConfiguration.pythonPath)) { - debugConfiguration.program = path.join(path.dirname(debugConfiguration.pythonPath), pserve); - } else { - debugConfiguration.program = pserve; - } - } } private getWorkspaceFolder(folder: WorkspaceFolder | undefined): Uri | undefined { if (folder) { diff --git a/src/client/debugger/configProviders/configurationProviderUtils.ts b/src/client/debugger/configProviders/configurationProviderUtils.ts new file mode 100644 index 000000000000..e5ce625517f5 --- /dev/null +++ b/src/client/debugger/configProviders/configurationProviderUtils.ts @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import * as path from 'path'; +import { Uri } from 'vscode'; +import { IApplicationShell } from '../../common/application/types'; +import { IFileSystem } from '../../common/platform/types'; +import { IPythonExecutionFactory } from '../../common/process/types'; +import { IServiceContainer } from '../../ioc/types'; +import { IConfigurationProviderUtils } from './types'; + +const PSERVE_SCRIPT_FILE_NAME = 'pserve.py'; + +@injectable() +export class ConfigurationProviderUtils implements IConfigurationProviderUtils { + private readonly executionFactory: IPythonExecutionFactory; + private readonly fs: IFileSystem; + constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { + this.executionFactory = this.serviceContainer.get(IPythonExecutionFactory); + this.fs = this.serviceContainer.get(IFileSystem); + } + public async getPyramidStartupScriptFilePath(resource?: Uri): Promise { + try { + const executionService = await this.executionFactory.create(resource); + const output = await executionService.exec(['-c', 'import pyramid;print(pyramid.__file__)'], { throwOnStdErr: true }); + const pserveFilePath = path.join(path.dirname(output.stdout.trim()), 'scripts', PSERVE_SCRIPT_FILE_NAME); + return await this.fs.fileExistsAsync(pserveFilePath) ? pserveFilePath : undefined; + } catch (ex) { + const message = 'Unable to locate \'pserve.py\' required for debugging of Pyramid applications.'; + console.error(message, ex); + const app = this.serviceContainer.get(IApplicationShell); + app.showErrorMessage(message); + return; + } + } +} diff --git a/src/client/debugger/configProviders/pythonProvider.ts b/src/client/debugger/configProviders/pythonProvider.ts index 69b2fe743319..2210e08b1a03 100644 --- a/src/client/debugger/configProviders/pythonProvider.ts +++ b/src/client/debugger/configProviders/pythonProvider.ts @@ -7,15 +7,23 @@ import { inject, injectable } from 'inversify'; import { Uri } from 'vscode'; import { IServiceContainer } from '../../ioc/types'; import { AttachRequestArgumentsV1, DebugOptions, LaunchRequestArgumentsV1 } from '../Common/Contracts'; -import { BaseConfigurationProvider, PythonAttachDebugConfiguration } from './baseProvider'; +import { BaseConfigurationProvider, PythonAttachDebugConfiguration, PythonLaunchDebugConfiguration } from './baseProvider'; +import { IConfigurationProviderUtils } from './types'; @injectable() export class PythonDebugConfigurationProvider extends BaseConfigurationProvider { constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { super('python', serviceContainer); } - protected provideAttachDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonAttachDebugConfiguration): void { - super.provideAttachDefaults(workspaceFolder, debugConfiguration); + protected async provideLaunchDefaults(workspaceFolder: Uri, debugConfiguration: PythonLaunchDebugConfiguration): Promise { + await super.provideLaunchDefaults(workspaceFolder, debugConfiguration); + if (debugConfiguration.debugOptions!.indexOf(DebugOptions.Pyramid) >= 0) { + const utils = this.serviceContainer.get(IConfigurationProviderUtils); + debugConfiguration.program = (await utils.getPyramidStartupScriptFilePath(workspaceFolder))!; + } + } + protected async provideAttachDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonAttachDebugConfiguration): Promise { + await super.provideAttachDefaults(workspaceFolder, debugConfiguration); const debugOptions = debugConfiguration.debugOptions!; // Always redirect output. if (debugOptions.indexOf(DebugOptions.RedirectOutput) === -1) { diff --git a/src/client/debugger/configProviders/pythonV2Provider.ts b/src/client/debugger/configProviders/pythonV2Provider.ts index e8953534c1b0..4b60be89a688 100644 --- a/src/client/debugger/configProviders/pythonV2Provider.ts +++ b/src/client/debugger/configProviders/pythonV2Provider.ts @@ -9,14 +9,15 @@ import { IPlatformService } from '../../common/platform/types'; import { IServiceContainer } from '../../ioc/types'; import { AttachRequestArguments, DebugOptions, LaunchRequestArguments } from '../Common/Contracts'; import { BaseConfigurationProvider, PythonAttachDebugConfiguration, PythonLaunchDebugConfiguration } from './baseProvider'; +import { IConfigurationProviderUtils } from './types'; @injectable() export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvider { constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { super('pythonExperimental', serviceContainer); } - protected provideLaunchDefaults(workspaceFolder: Uri, debugConfiguration: PythonLaunchDebugConfiguration): void { - super.provideLaunchDefaults(workspaceFolder, debugConfiguration); + protected async provideLaunchDefaults(workspaceFolder: Uri, debugConfiguration: PythonLaunchDebugConfiguration): Promise { + await super.provideLaunchDefaults(workspaceFolder, debugConfiguration); const debugOptions = debugConfiguration.debugOptions!; if (debugConfiguration.debugStdLib) { this.debugOption(debugOptions, DebugOptions.DebugStdLib); @@ -41,9 +42,13 @@ export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvide && debugConfiguration.jinja !== false) { this.debugOption(debugOptions, DebugOptions.Jinja); } + if (debugConfiguration.pyramid) { + const utils = this.serviceContainer.get(IConfigurationProviderUtils); + debugConfiguration.program = (await utils.getPyramidStartupScriptFilePath(workspaceFolder))!; + } } - protected provideAttachDefaults(workspaceFolder: Uri, debugConfiguration: PythonAttachDebugConfiguration): void { - super.provideAttachDefaults(workspaceFolder, debugConfiguration); + protected async provideAttachDefaults(workspaceFolder: Uri, debugConfiguration: PythonAttachDebugConfiguration): Promise { + await super.provideAttachDefaults(workspaceFolder, debugConfiguration); const debugOptions = debugConfiguration.debugOptions!; if (debugConfiguration.debugStdLib) { this.debugOption(debugOptions, DebugOptions.DebugStdLib); diff --git a/src/client/debugger/configProviders/serviceRegistry.ts b/src/client/debugger/configProviders/serviceRegistry.ts index d664bfac771c..3e6dbded95c4 100644 --- a/src/client/debugger/configProviders/serviceRegistry.ts +++ b/src/client/debugger/configProviders/serviceRegistry.ts @@ -8,8 +8,11 @@ import { DebugConfigurationProvider } from 'vscode'; import { PythonDebugConfigurationProvider, PythonV2DebugConfigurationProvider } from '..'; import { IServiceManager } from '../../ioc/types'; import { IDebugConfigurationProvider } from '../types'; +import { ConfigurationProviderUtils } from './configurationProviderUtils'; +import { IConfigurationProviderUtils } from './types'; export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IDebugConfigurationProvider, PythonDebugConfigurationProvider); serviceManager.addSingleton(IDebugConfigurationProvider, PythonV2DebugConfigurationProvider); + serviceManager.addSingleton(IConfigurationProviderUtils, ConfigurationProviderUtils); } diff --git a/src/client/debugger/configProviders/types.ts b/src/client/debugger/configProviders/types.ts new file mode 100644 index 000000000000..8ece886ab352 --- /dev/null +++ b/src/client/debugger/configProviders/types.ts @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { Uri } from 'vscode'; + +export const IConfigurationProviderUtils = Symbol('IConfigurationProviderUtils'); + +export interface IConfigurationProviderUtils { + getPyramidStartupScriptFilePath(resource?: Uri): Promise; +} diff --git a/src/test/debugger/configProvider/provider.test.ts b/src/test/debugger/configProvider/provider.test.ts index c8670b6f0e68..0ea18a47ac56 100644 --- a/src/test/debugger/configProvider/provider.test.ts +++ b/src/test/debugger/configProvider/provider.test.ts @@ -9,12 +9,15 @@ import { expect } from 'chai'; import * as path from 'path'; import * as TypeMoq from 'typemoq'; import { DebugConfiguration, DebugConfigurationProvider, TextDocument, TextEditor, Uri, WorkspaceFolder } from 'vscode'; -import { IDocumentManager, IWorkspaceService } from '../../../client/common/application/types'; +import { IApplicationShell, IDocumentManager, IWorkspaceService } from '../../../client/common/application/types'; import { PythonLanguage } from '../../../client/common/constants'; import { IFileSystem, IPlatformService } from '../../../client/common/platform/types'; +import { IPythonExecutionFactory, IPythonExecutionService } from '../../../client/common/process/types'; import { IConfigurationService, IPythonSettings } from '../../../client/common/types'; import { PythonDebugConfigurationProvider, PythonV2DebugConfigurationProvider } from '../../../client/debugger'; import { DebugOptions } from '../../../client/debugger/Common/Contracts'; +import { ConfigurationProviderUtils } from '../../../client/debugger/configProviders/configurationProviderUtils'; +import { IConfigurationProviderUtils } from '../../../client/debugger/configProviders/types'; import { IServiceContainer } from '../../../client/ioc/types'; [ @@ -26,6 +29,8 @@ import { IServiceContainer } from '../../../client/ioc/types'; let debugProvider: DebugConfigurationProvider; let platformService: TypeMoq.IMock; let fileSystem: TypeMoq.IMock; + let appShell: TypeMoq.IMock; + let pythonExecutionService: TypeMoq.IMock; setup(() => { serviceContainer = TypeMoq.Mock.ofType(); debugProvider = new provider.class(serviceContainer.object); @@ -39,9 +44,20 @@ import { IServiceContainer } from '../../../client/ioc/types'; const confgService = TypeMoq.Mock.ofType(); platformService = TypeMoq.Mock.ofType(); fileSystem = TypeMoq.Mock.ofType(); + appShell = TypeMoq.Mock.ofType(); + + pythonExecutionService = TypeMoq.Mock.ofType(); + pythonExecutionService.setup((x: any) => x.then).returns(() => undefined); + const factory = TypeMoq.Mock.ofType(); + factory.setup(f => f.create(TypeMoq.It.isAny())).returns(() => Promise.resolve(pythonExecutionService.object)); + + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPythonExecutionFactory))).returns(() => factory.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationService))).returns(() => confgService.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPlatformService))).returns(() => platformService.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IFileSystem))).returns(() => fileSystem.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IApplicationShell))).returns(() => appShell.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationProviderUtils))).returns(() => new ConfigurationProviderUtils(serviceContainer.object)); + const settings = TypeMoq.Mock.ofType(); settings.setup(s => s.pythonPath).returns(() => pythonPath); confgService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); @@ -295,26 +311,39 @@ import { IServiceContainer } from '../../../client/ioc/types'; } await testFixFilePathCase(false, true, false); }); - async function testPyramidConfiguration(isWindows: boolean, isLinux: boolean, isMac: boolean, addPyramidDebugOption: boolean = true, pythonPathExists = true, shouldWork = true) { + async function testPyramidConfiguration(isWindows: boolean, isLinux: boolean, isMac: boolean, addPyramidDebugOption: boolean = true, pyramidExists = true, shouldWork = true) { const workspacePath = path.join('usr', 'development', 'wksp1'); const pythonPath = path.join(workspacePath, 'env', 'bin', 'python'); - const pserveExecutableName = isWindows ? 'pserve.exe' : 'pserve'; - const pservePath = pythonPathExists ? path.join(path.dirname(pythonPath), pserveExecutableName) : pserveExecutableName; + const pyramidFilePath = path.join(path.dirname(pythonPath), 'lib', 'site_packages', 'pyramid', '__init__.py'); + const pserveFilePath = path.join(path.dirname(pyramidFilePath), 'scripts', 'pserve.py'); + const args = ['-c', 'import pyramid;print(pyramid.__file__)']; const workspaceFolder = createMoqWorkspaceFolder(workspacePath); const pythonFile = 'xyz.py'; + setupIoc(pythonPath, isWindows, isMac, isLinux); setupActiveEditor(pythonFile, PythonLanguage.language); - const options = addPyramidDebugOption ? { debugOptions: [DebugOptions.Pyramid] } : {}; - fileSystem.setup(fs => fs.fileExistsSync(TypeMoq.It.isValue(pythonPath))).returns(() => pythonPathExists); + const execOutput = pyramidExists ? Promise.resolve({ stdout: pyramidFilePath }) : Promise.reject(new Error('No Module')); + pythonExecutionService.setup(e => e.exec(TypeMoq.It.isValue(args), TypeMoq.It.isAny())) + .returns(() => execOutput) + .verifiable(TypeMoq.Times.exactly(addPyramidDebugOption ? 1 : 0)); + fileSystem.setup(f => f.fileExistsAsync(TypeMoq.It.isValue(pserveFilePath))) + .returns(() => Promise.resolve(pyramidExists)) + .verifiable(TypeMoq.Times.exactly(pyramidExists && addPyramidDebugOption ? 1 : 0)); + appShell.setup(a => a.showErrorMessage(TypeMoq.It.isAny())) + .verifiable(TypeMoq.Times.exactly(pyramidExists || !addPyramidDebugOption ? 0 : 1)); + const options = addPyramidDebugOption ? { debugOptions: [DebugOptions.Pyramid], pyramid: true } : {}; const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, options as any as DebugConfiguration); if (shouldWork) { - expect(debugConfig).to.have.property('program', pservePath); + expect(debugConfig).to.have.property('program', pserveFilePath); } else { - expect(debugConfig!.program).to.be.not.equal(pservePath); + expect(debugConfig!.program).to.be.not.equal(pserveFilePath); } - } + pythonExecutionService.verifyAll(); + fileSystem.verifyAll(); + appShell.verifyAll(); + } test('Program is set for Pyramid (windows)', async () => { await testPyramidConfiguration(true, false, false); }); @@ -333,14 +362,14 @@ import { IServiceContainer } from '../../../client/ioc/types'; test('Program is not set for Pyramid when DebugOption is not set (Mac)', async () => { await testPyramidConfiguration(false, false, true, false, false, false); }); - test('Program is set to executable name for Pyramid when python exec does not exist (windows)', async () => { - await testPyramidConfiguration(true, false, false, true, false, true); + test('Message is displayed when pyramid script does not exist (windows)', async () => { + await testPyramidConfiguration(true, false, false, true, false, false); }); - test('Program is set to executable name for Pyramid when python exec does not exist (Linux)', async () => { - await testPyramidConfiguration(false, true, false, true, false, true); + test('Message is displayed when pyramid script does not exist (Linux)', async () => { + await testPyramidConfiguration(false, true, false, true, false, false); }); - test('Program is set to executable name for Pyramid when python exec does not exist (Mac)', async () => { - await testPyramidConfiguration(false, false, true, true, false, true); + test('Message is displayed when pyramid script does not exist (Mac)', async () => { + await testPyramidConfiguration(false, false, true, true, false, false); }); test('Auto detect flask debugging', async () => { if (provider.debugType === 'python') { From 1efb4062f733d863eafb1e9e82ac8a255d103cd4 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 27 Apr 2018 11:45:31 -0700 Subject: [PATCH 192/433] Use latest version of Anaconda (#1518) Fixes #1517 --- .github/test_plan.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/test_plan.md b/.github/test_plan.md index c0ecbaf100c8..725b7b3b05e6 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -36,6 +36,7 @@ #### Virtual environments **ALWAYS**: +- Use the latest version of Anconda - Create an environment with a space in their path somewhere - Make sure that you do not have `python.pythonPath` specified in your `settings.json` when testing automatic detection - Do note that the `Select Interpreter` drop-down window scrolls From e312f9e4c4b27c6102683d95f9c80a75e841113e Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 27 Apr 2018 11:57:21 -0700 Subject: [PATCH 193/433] Check memory usage only if jedi is used and reduce frequency of these checks (#1510) Fixes #1277 * Use settimeout instead of setinterval (the requests were getting piled up with setinterval). * I've reduced the frequency of checks from 2 seconds to 15 seconds. * Check memory usage only if jedi is used> --- news/2 Fixes/1277.md | 1 + src/client/providers/jediProxy.ts | 58 +++++++++++++++++++++---------- 2 files changed, 41 insertions(+), 18 deletions(-) create mode 100644 news/2 Fixes/1277.md diff --git a/news/2 Fixes/1277.md b/news/2 Fixes/1277.md new file mode 100644 index 000000000000..3f12d5f3ec26 --- /dev/null +++ b/news/2 Fixes/1277.md @@ -0,0 +1 @@ +Reduce the frequency within which the memory usage of the language server is checked, also ensure memory usage is not checked unless language server functionality is used. diff --git a/src/client/providers/jediProxy.ts b/src/client/providers/jediProxy.ts index 8bae330e49c9..85714dd9cf50 100644 --- a/src/client/providers/jediProxy.ts +++ b/src/client/providers/jediProxy.ts @@ -6,7 +6,6 @@ import { ChildProcess } from 'child_process'; import * as fs from 'fs-extra'; import * as path from 'path'; import * as pidusage from 'pidusage'; -import { setInterval } from 'timers'; import { CancellationToken, CancellationTokenSource, CompletionItemKind, Disposable, SymbolKind, Uri } from 'vscode'; import { PythonSettings } from '../common/configSettings'; import { debounce, swallowExceptions } from '../common/decorators'; @@ -147,6 +146,8 @@ export class JediProxy implements Disposable { private logger: ILogger; private ignoreJediMemoryFootprint: boolean = false; private pidUsageFailures = { timer: new StopWatch(), counter: 0 }; + private lastCmdIdProcessed?: number; + private lastCmdIdProcessedForPidUsage?: number; public constructor(private extensionRootDir: string, workspacePath: string, private serviceContainer: IServiceContainer) { this.workspacePath = workspacePath; @@ -157,12 +158,7 @@ export class JediProxy implements Disposable { this.initialized = createDeferred(); this.startLanguageServer().then(() => this.initialized.resolve()).ignoreErrors(); - // Check memory footprint periodically. Do not check on every request due to - // the performance impact. See https://github.com/soyuka/pidusage - on Windows - // it is using wmic which means spawning cmd.exe process on every request. - if (this.shouldCheckJediMemoryFootprint()) { - setInterval(() => this.checkJediMemoryFootprint(), 2000); - } + this.checkJediMemoryFootprint().ignoreErrors(); } private static getProperty(o: object, name: string): T { @@ -220,32 +216,58 @@ export class JediProxy implements Disposable { if (this.ignoreJediMemoryFootprint || this.pythonSettings.jediMemoryLimit === -1) { return false; } + if (this.lastCmdIdProcessedForPidUsage && this.lastCmdIdProcessed && + this.lastCmdIdProcessedForPidUsage === this.lastCmdIdProcessed) { + // If no more commands were processed since the last time, + // then there's no need to check again. + return false; + } return true; } - private checkJediMemoryFootprint() { + private async checkJediMemoryFootprint() { + // Check memory footprint periodically. Do not check on every request due to + // the performance impact. See https://github.com/soyuka/pidusage - on Windows + // it is using wmic which means spawning cmd.exe process on every request. + if (this.pythonSettings.jediMemoryLimit === -1) { + return; + } + + await this.checkJediMemoryFootprintImpl(); + setTimeout(() => this.checkJediMemoryFootprint(), 15 * 1000); + } + private async checkJediMemoryFootprintImpl(): Promise { if (!this.proc || this.proc.killed) { return; } if (!this.shouldCheckJediMemoryFootprint()) { return; } + this.lastCmdIdProcessedForPidUsage = this.lastCmdIdProcessed; + + // Do not run pidusage over and over, wait for it to finish. + const deferred = createDeferred(); pidusage.stat(this.proc.pid, async (err, result) => { if (err) { this.pidUsageFailures.counter += 1; - // If this function fails 5 times in the last 30 seconds, lets not try ever again. - if (this.pidUsageFailures.timer.elapsedTime > 30 * 1000) { - this.ignoreJediMemoryFootprint = this.pidUsageFailures.counter > 5; + // If this function fails 2 times in the last 60 seconds, lets not try ever again. + if (this.pidUsageFailures.timer.elapsedTime > 60 * 1000) { + this.ignoreJediMemoryFootprint = this.pidUsageFailures.counter > 2; this.pidUsageFailures.counter = 0; this.pidUsageFailures.timer.reset(); } - return console.error('Python Extension: (pidusage)', err); - } - const limit = Math.min(Math.max(this.pythonSettings.jediMemoryLimit, 1024), 8192); - if (result && result.memory > limit * 1024 * 1024) { - this.logger.logWarning(`IntelliSense process memory consumption exceeded limit of ${limit} MB and process will be restarted.\nThe limit is controlled by the 'python.jediMemoryLimit' setting.`); - await this.restartLanguageServer(); + console.error('Python Extension: (pidusage)', err); + } else { + const limit = Math.min(Math.max(this.pythonSettings.jediMemoryLimit, 1024), 8192); + if (result && result.memory > limit * 1024 * 1024) { + this.logger.logWarning(`IntelliSense process memory consumption exceeded limit of ${limit} MB and process will be restarted.\nThe limit is controlled by the 'python.jediMemoryLimit' setting.`); + await this.restartLanguageServer(); + } } + + deferred.resolve(); }); + + return deferred.promise; } @swallowExceptions('JediProxy') @@ -374,7 +396,7 @@ export class JediProxy implements Disposable { if (cmd === null) { return; } - + this.lastCmdIdProcessed = cmd.id; if (JediProxy.getProperty(response, 'arguments')) { this.commandQueue.splice(this.commandQueue.indexOf(cmd.id), 1); return; From 087da4264ff5c8e6488b6f62642c36f5771d804f Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Fri, 27 Apr 2018 12:15:02 -0700 Subject: [PATCH 194/433] Improve detection of the function argument list in autoformat (#1508) * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * #1096 The if statement is automatically formatted incorrectly * Merge fix * Add more tests * More tests * Typo * Test * Also better handle multiline arguments * Add a couple missing periods [skip ci] * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * Merge fix * #1257 On type formatting errors for args and kwargs * Handle f-strings * Stop importing from test code * #1308 Single line statements leading to an indentation on the next line * #726 editing python after inline if statement invalid indent * Undo change * Move constant * Harden LS startup error checks * #1364 Intellisense doesn't work after specific const string * Telemetry for the analysis enging * PR feedback * Fix typo * Test baseline update * Improve function argument detection --- src/client/formatters/lineFormatter.ts | 57 ++++---- src/client/typeFormatters/onEnterFormatter.ts | 16 +-- .../format/extension.lineFormatter.test.ts | 136 ++++++++++-------- .../pythonFiles/formatting/pythonGrammar.py | 8 +- 4 files changed, 123 insertions(+), 94 deletions(-) diff --git a/src/client/formatters/lineFormatter.ts b/src/client/formatters/lineFormatter.ts index 2c7b37580f11..28a71f6ff08d 100644 --- a/src/client/formatters/lineFormatter.ts +++ b/src/client/formatters/lineFormatter.ts @@ -3,6 +3,7 @@ // tslint:disable-next-line:import-name import Char from 'typescript-char'; +import { TextDocument } from 'vscode'; import { BraceCounter } from '../language/braceCounter'; import { TextBuilder } from '../language/textBuilder'; import { TextRangeCollection } from '../language/textRangeCollection'; @@ -14,11 +15,15 @@ export class LineFormatter { private tokens: ITextRangeCollection = new TextRangeCollection([]); private braceCounter = new BraceCounter(); private text = ''; + private document?: TextDocument; + private lineNumber = 0; // tslint:disable-next-line:cyclomatic-complexity - public formatLine(text: string): string { - this.tokens = new Tokenizer().tokenize(text); - this.text = text; + public formatLine(document: TextDocument, lineNumber: number): string { + this.document = document; + this.lineNumber = lineNumber; + this.text = document.lineAt(lineNumber).text; + this.tokens = new Tokenizer().tokenize(this.text); this.builder = new TextBuilder(); this.braceCounter = new BraceCounter(); @@ -107,7 +112,7 @@ export class LineFormatter { this.builder.append(this.text[t.start]); return; case Char.Asterisk: - if (prev && prev.type === TokenType.Identifier && prev.length === 6 && this.text.substr(prev.start, prev.length) === 'lambda') { + if (prev && this.isKeyword(prev, 'lambda')) { this.builder.softAppendSpace(); this.builder.append('*'); return; @@ -122,7 +127,7 @@ export class LineFormatter { this.builder.append('**'); return; } - if (prev && prev.type === TokenType.Identifier && prev.length === 6 && this.text.substr(prev.start, prev.length) === 'lambda') { + if (prev && this.isKeyword(prev, 'lambda')) { this.builder.softAppendSpace(); this.builder.append('**'); return; @@ -194,6 +199,8 @@ export class LineFormatter { this.builder.softAppendSpace(); } } + + // tslint:disable-next-line:cyclomatic-complexity private isEqualsInsideArguments(index: number): boolean { // Since we don't have complete statement, this is mostly heuristics. // Therefore the code may not be handling all possible ways of the @@ -217,28 +224,31 @@ export class LineFormatter { return true; // Line ends in comma } - if (index >= 2) { - // (x=1 or ,x=1 - const prevPrev = this.tokens.getItemAt(index - 2); - return prevPrev.type === TokenType.Comma || prevPrev.type === TokenType.OpenBrace; + if (last.type === TokenType.Comment && this.tokens.count > 1 && this.tokens.getItemAt(this.tokens.count - 2).type === TokenType.Comma) { + return true; // Line ends in comma and then comment } - if (index >= this.tokens.count - 2) { - return false; + if (this.document) { + const prevLine = this.lineNumber > 0 ? this.document.lineAt(this.lineNumber - 1).text : ''; + const prevLineTokens = new Tokenizer().tokenize(prevLine); + if (prevLineTokens.count > 0) { + const lastOnPrevLine = prevLineTokens.getItemAt(prevLineTokens.count - 1); + if (lastOnPrevLine.type === TokenType.Comma) { + return true; // Previous line ends in comma + } + if (lastOnPrevLine.type === TokenType.Comment && prevLineTokens.count > 1 && prevLineTokens.getItemAt(prevLineTokens.count - 2).type === TokenType.Comma) { + return true; // Previous line ends in comma and then comment + } + } } - const next = this.tokens.getItemAt(index + 1); - const nextNext = this.tokens.getItemAt(index + 2); - // x=1, or x=1) - if (this.isValueType(next.type)) { - if (nextNext.type === TokenType.CloseBrace) { + for (let i = 0; i < index; i += 1) { + const t = this.tokens.getItemAt(i); + if (this.isKeyword(t, 'lambda')) { return true; } - if (nextNext.type === TokenType.Comma) { - return last.type === TokenType.CloseBrace; - } } - return false; + return this.braceCounter.isOpened(TokenType.OpenBrace); } private isOpenBraceType(type: TokenType): boolean { @@ -250,10 +260,6 @@ export class LineFormatter { private isBraceType(type: TokenType): boolean { return this.isOpenBraceType(type) || this.isCloseBraceType(type); } - private isValueType(type: TokenType): boolean { - return type === TokenType.Identifier || type === TokenType.Unknown || - type === TokenType.Number || type === TokenType.String; - } private isMultipleStatements(index: number): boolean { for (let i = index; i >= 0; i -= 1) { if (this.tokens.getItemAt(i).type === TokenType.Semicolon) { @@ -268,4 +274,7 @@ export class LineFormatter { s === 'import' || s === 'except' || s === 'for' || s === 'as' || s === 'is'; } + private isKeyword(t: IToken, keyword: string): boolean { + return t.type === TokenType.Identifier && t.length === keyword.length && this.text.substr(t.start, t.length) === keyword; + } } diff --git a/src/client/typeFormatters/onEnterFormatter.ts b/src/client/typeFormatters/onEnterFormatter.ts index 013b2d2a85f9..3e17e714d6ee 100644 --- a/src/client/typeFormatters/onEnterFormatter.ts +++ b/src/client/typeFormatters/onEnterFormatter.ts @@ -1,20 +1,20 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import * as vscode from 'vscode'; +import { CancellationToken, FormattingOptions, OnTypeFormattingEditProvider, Position, TextDocument, TextEdit } from 'vscode'; import { LineFormatter } from '../formatters/lineFormatter'; import { TokenizerMode, TokenType } from '../language/types'; import { getDocumentTokens } from '../providers/providerUtilities'; -export class OnEnterFormatter implements vscode.OnTypeFormattingEditProvider { +export class OnEnterFormatter implements OnTypeFormattingEditProvider { private readonly formatter = new LineFormatter(); public provideOnTypeFormattingEdits( - document: vscode.TextDocument, - position: vscode.Position, + document: TextDocument, + position: Position, ch: string, - options: vscode.FormattingOptions, - cancellationToken: vscode.CancellationToken): vscode.TextEdit[] { + options: FormattingOptions, + cancellationToken: CancellationToken): TextEdit[] { if (position.line === 0) { return []; } @@ -30,10 +30,10 @@ export class OnEnterFormatter implements vscode.OnTypeFormattingEditProvider { return []; } } - const formatted = this.formatter.formatLine(prevLine.text); + const formatted = this.formatter.formatLine(document, prevLine.lineNumber); if (formatted === prevLine.text) { return []; } - return [new vscode.TextEdit(prevLine.range, formatted)]; + return [new TextEdit(prevLine.range, formatted)]; } } diff --git a/src/test/format/extension.lineFormatter.test.ts b/src/test/format/extension.lineFormatter.test.ts index a9cb0fa04447..46ca5e46f816 100644 --- a/src/test/format/extension.lineFormatter.test.ts +++ b/src/test/format/extension.lineFormatter.test.ts @@ -5,6 +5,8 @@ import * as assert from 'assert'; import * as fs from 'fs'; import * as path from 'path'; +import * as TypeMoq from 'typemoq'; +import { TextDocument, TextLine } from 'vscode'; import '../../client/common/extensions'; import { LineFormatter } from '../../client/formatters/lineFormatter'; @@ -17,124 +19,142 @@ suite('Formatting - line formatter', () => { const formatter = new LineFormatter(); test('Operator spacing', () => { - const actual = formatter.formatLine('( x +1 )*y/ 3'); - assert.equal(actual, '(x + 1) * y / 3'); + testFormatLine('( x +1 )*y/ 3', '(x + 1) * y / 3'); }); test('Braces spacing', () => { - const actual = formatter.formatLine('foo =(0 ,)'); - assert.equal(actual, 'foo = (0,)'); + testFormatLine('foo =(0 ,)', 'foo = (0,)'); }); test('Function arguments', () => { - const actual = formatter.formatLine('foo (0 , x= 1, (3+7) , y , z )'); - assert.equal(actual, 'foo(0, x=1, (3 + 7), y, z)'); + testFormatLine('z=foo (0 , x= 1, (3+7) , y , z )', + 'z = foo(0, x=1, (3 + 7), y, z)'); }); test('Colon regular', () => { - const actual = formatter.formatLine('if x == 4 : print x,y; x,y= y, x'); - assert.equal(actual, 'if x == 4: print x, y; x, y = y, x'); + testFormatLine('if x == 4 : print x,y; x,y= y, x', + 'if x == 4: print x, y; x, y = y, x'); }); test('Colon slices', () => { - const actual = formatter.formatLine('x[1: 30]'); - assert.equal(actual, 'x[1:30]'); + testFormatLine('x[1: 30]', 'x[1:30]'); }); test('Colon slices in arguments', () => { - const actual = formatter.formatLine('spam ( ham[ 1 :3], {eggs : 2})'); - assert.equal(actual, 'spam(ham[1:3], {eggs: 2})'); + testFormatLine('spam ( ham[ 1 :3], {eggs : 2})', + 'spam(ham[1:3], {eggs: 2})'); }); test('Colon slices with double colon', () => { - const actual = formatter.formatLine('ham [1:9 ], ham[ 1: 9: 3], ham[: 9 :3], ham[1: :3], ham [ 1: 9:]'); - assert.equal(actual, 'ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:]'); + testFormatLine('ham [1:9 ], ham[ 1: 9: 3], ham[: 9 :3], ham[1: :3], ham [ 1: 9:]', + 'ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:]'); }); test('Colon slices with operators', () => { - const actual = formatter.formatLine('ham [lower+ offset :upper+offset]'); - assert.equal(actual, 'ham[lower + offset:upper + offset]'); + testFormatLine('ham [lower+ offset :upper+offset]', + 'ham[lower + offset:upper + offset]'); }); test('Colon slices with functions', () => { - const actual = formatter.formatLine('ham[ : upper_fn ( x) : step_fn(x )], ham[ :: step_fn(x)]'); - assert.equal(actual, 'ham[:upper_fn(x):step_fn(x)], ham[::step_fn(x)]'); + testFormatLine('ham[ : upper_fn ( x) : step_fn(x )], ham[ :: step_fn(x)]', + 'ham[:upper_fn(x):step_fn(x)], ham[::step_fn(x)]'); }); test('Colon in for loop', () => { - const actual = formatter.formatLine('for index in range( len(fruits) ): '); - assert.equal(actual, 'for index in range(len(fruits)):'); + testFormatLine('for index in range( len(fruits) ): ', + 'for index in range(len(fruits)):'); }); test('Nested braces', () => { - const actual = formatter.formatLine('[ 1 :[2: (x,),y]]{1}'); - assert.equal(actual, '[1:[2:(x,), y]]{1}'); + testFormatLine('[ 1 :[2: (x,),y]]{1}', '[1:[2:(x,), y]]{1}'); }); test('Trailing comment', () => { - const actual = formatter.formatLine('x=1 # comment'); - assert.equal(actual, 'x = 1 # comment'); + testFormatLine('x=1 # comment', 'x = 1 # comment'); }); test('Single comment', () => { - const actual = formatter.formatLine('# comment'); - assert.equal(actual, '# comment'); + testFormatLine('# comment', '# comment'); }); test('Comment with leading whitespace', () => { - const actual = formatter.formatLine(' # comment'); - assert.equal(actual, ' # comment'); + testFormatLine(' # comment', ' # comment'); }); test('Equals in first argument', () => { - const actual = formatter.formatLine('foo(x =0)'); - assert.equal(actual, 'foo(x=0)'); + testFormatLine('foo(x =0)', 'foo(x=0)'); }); test('Equals in second argument', () => { - const actual = formatter.formatLine('foo(x,y= \"a\",'); - assert.equal(actual, 'foo(x, y=\"a\",'); + testFormatLine('foo(x,y= \"a\",', 'foo(x, y=\"a\",'); }); test('Equals in multiline arguments', () => { - const actual = formatter.formatLine('x = 1,y =-2)'); - assert.equal(actual, 'x=1, y=-2)'); + testFormatLine2('foo(a,', 'x = 1,y =-2)', 'x=1, y=-2)'); }); test('Equals in multiline arguments starting comma', () => { - const actual = formatter.formatLine(',x = 1,y =m)'); - assert.equal(actual, ', x=1, y=m)'); + testFormatLine(',x = 1,y =m)', ', x=1, y=m)'); }); test('Equals in multiline arguments ending comma', () => { - const actual = formatter.formatLine('x = 1,y =m,'); - assert.equal(actual, 'x=1, y=m,'); + testFormatLine('x = 1,y =m,', 'x=1, y=m,'); }); test('Operators without following space', () => { - const actual = formatter.formatLine('foo( *a, ** b, ! c)'); - assert.equal(actual, 'foo(*a, **b, !c)'); + testFormatLine('foo( *a, ** b, ! c)', 'foo(*a, **b, !c)'); }); test('Brace after keyword', () => { - const actual = formatter.formatLine('for x in(1,2,3)'); - assert.equal(actual, 'for x in (1, 2, 3)'); + testFormatLine('for x in(1,2,3)', 'for x in (1, 2, 3)'); }); test('Dot operator', () => { - const actual = formatter.formatLine('x.y'); - assert.equal(actual, 'x.y'); + testFormatLine('x.y', 'x.y'); }); test('Unknown tokens no space', () => { - const actual = formatter.formatLine('abc\\n\\'); - assert.equal(actual, 'abc\\n\\'); + testFormatLine('abc\\n\\', 'abc\\n\\'); }); test('Unknown tokens with space', () => { - const actual = formatter.formatLine('abc \\n \\'); - assert.equal(actual, 'abc \\n \\'); + testFormatLine('abc \\n \\', 'abc \\n \\'); }); test('Double asterisk', () => { - const actual = formatter.formatLine('a**2, ** k'); - assert.equal(actual, 'a ** 2, **k'); + testFormatLine('a**2, ** k', 'a ** 2, **k'); }); test('Lambda', () => { - const actual = formatter.formatLine('lambda * args, :0'); - assert.equal(actual, 'lambda *args,: 0'); + testFormatLine('lambda * args, :0', 'lambda *args,: 0'); }); test('Comma expression', () => { - const actual = formatter.formatLine('x=1,2,3'); - assert.equal(actual, 'x = 1, 2, 3'); + testFormatLine('x=1,2,3', 'x = 1, 2, 3'); }); test('is exression', () => { - const actual = formatter.formatLine('a( (False is 2) is 3)'); - assert.equal(actual, 'a((False is 2) is 3)'); + testFormatLine('a( (False is 2) is 3)', 'a((False is 2) is 3)'); + }); + test('Function returning tuple', () => { + testFormatLine('x,y=f(a)', 'x, y = f(a)'); }); test('Grammar file', () => { const content = fs.readFileSync(grammarFile).toString('utf8'); const lines = content.splitLines({ trim: false, removeEmptyEntries: false }); + let prevLine = ''; for (let i = 0; i < lines.length; i += 1) { const line = lines[i]; - const actual = formatter.formatLine(line); - assert.equal(actual, line, `Line ${i + 1} changed: '${line}' to '${actual}'`); + const actual = formatLine2(prevLine, line); + assert.equal(actual, line, `Line ${i + 1} changed: '${line.trim()}' to '${actual.trim()}'`); + prevLine = line; } }); + + function testFormatLine(text: string, expected: string): void { + const actual = formatLine(text); + assert.equal(actual, expected); + } + + function formatLine(text: string): string { + const line = TypeMoq.Mock.ofType(); + line.setup(x => x.text).returns(() => text); + + const document = TypeMoq.Mock.ofType(); + document.setup(x => x.lineAt(TypeMoq.It.isAnyNumber())).returns(() => line.object); + + return formatter.formatLine(document.object, 0); + } + + function formatLine2(prevLineText: string, lineText: string): string { + const thisLine = TypeMoq.Mock.ofType(); + thisLine.setup(x => x.text).returns(() => lineText); + + const prevLine = TypeMoq.Mock.ofType(); + prevLine.setup(x => x.text).returns(() => prevLineText); + + const document = TypeMoq.Mock.ofType(); + document.setup(x => x.lineAt(0)).returns(() => prevLine.object); + document.setup(x => x.lineAt(1)).returns(() => thisLine.object); + + return formatter.formatLine(document.object, 1); + } + + function testFormatLine2(prevLineText: string, lineText: string, expected: string): void { + const actual = formatLine2(prevLineText, lineText); + assert.equal(actual, expected); + } }); diff --git a/src/test/pythonFiles/formatting/pythonGrammar.py b/src/test/pythonFiles/formatting/pythonGrammar.py index 32b82285c12f..1a17d94302b5 100644 --- a/src/test/pythonFiles/formatting/pythonGrammar.py +++ b/src/test/pythonFiles/formatting/pythonGrammar.py @@ -646,7 +646,7 @@ def test_lambdef(self): l2 = lambda: a[d] # XXX just testing the expression l3 = lambda: [2 < x for x in [-1, 3, 0]] self.assertEqual(l3(), [0, 1, 0]) - l4 = lambda x = lambda y = lambda z = 1: z: y(): x() + l4 = lambda x=lambda y=lambda z=1: z: y(): x() self.assertEqual(l4(), 1) l5 = lambda x, y, z=2: x + y + z self.assertEqual(l5(1, 2), 5) @@ -696,8 +696,8 @@ def test_expr_stmt(self): x = 1 x = 1, 2, 3 x = y = z = 1, 2, 3 - x, y, z=1, 2, 3 - abc=a, b, c=x, y, z=xyz = 1, 2, (3, 4) + x, y, z = 1, 2, 3 + abc = a, b, c = x, y, z = xyz = 1, 2, (3, 4) check_syntax_error(self, "x + 1 = 1") check_syntax_error(self, "a + 1 = b + 2") @@ -730,7 +730,7 @@ def test_former_statements_refer_to_builtins(self): def test_del_stmt(self): # 'del' exprlist abc = [1, 2, 3] - x, y, z=abc + x, y, z = abc xyz = x, y, z del abc From 7a95bce8a663ccc7aa083031ce8f6951cda5cc11 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 27 Apr 2018 14:18:18 -0700 Subject: [PATCH 195/433] Jinja template debugging for watson apps (#1512) Fixes #1480 --- news/1 Enhancements/1480.md | 1 + package.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 news/1 Enhancements/1480.md diff --git a/news/1 Enhancements/1480.md b/news/1 Enhancements/1480.md new file mode 100644 index 000000000000..57d280dc60ae --- /dev/null +++ b/news/1 Enhancements/1480.md @@ -0,0 +1 @@ +Enable Jinja template debugging as a default behaivour when using the Watson debug configuration for debugging of Watson applications. diff --git a/package.json b/package.json index 7abba1b72240..32a415cede29 100644 --- a/package.json +++ b/package.json @@ -842,7 +842,8 @@ "dev", "runserver", "--noreload=True" - ] + ], + "jinja": true } }, { From ed0bc9444a49185b993678a5ad6c27f7662ef8ac Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 27 Apr 2018 14:18:35 -0700 Subject: [PATCH 196/433] News entry for jinja template debugging in the experimental debugger Fixes #1206 --- news/1 Enhancements/1206.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 news/1 Enhancements/1206.md diff --git a/news/1 Enhancements/1206.md b/news/1 Enhancements/1206.md new file mode 100644 index 000000000000..d581de672d41 --- /dev/null +++ b/news/1 Enhancements/1206.md @@ -0,0 +1,7 @@ +Enable debugging of Jinja templates in the experimental debugger. +This is made possible with the addition of the `jinja` setting in the `launch.json` file as follows: +```json + "request": "launch or attach", + ... + "jinja": true +``` From 4b997d6f15a54065ea25e599a8dba8002e448809 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 27 Apr 2018 14:18:49 -0700 Subject: [PATCH 197/433] Remove news entry related to issue #259 as the issue was not fixed --- news/2 Fixes/259.md | 1 - 1 file changed, 1 deletion(-) delete mode 100644 news/2 Fixes/259.md diff --git a/news/2 Fixes/259.md b/news/2 Fixes/259.md deleted file mode 100644 index 8579bc07f4cc..000000000000 --- a/news/2 Fixes/259.md +++ /dev/null @@ -1 +0,0 @@ -Add blank lines to seprate blocks of indented code (function defs, classes, and the like) to ensure the code can be run within a Python interactive prompt. From 3c2b779633a1a0886836b1eaf45a1fcec694a0de Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 27 Apr 2018 14:19:19 -0700 Subject: [PATCH 198/433] Resolve warning message related to document filters (#1531) Fixes #1530 --- news/3 Code Health/1530.md | 1 + src/client/common/constants.ts | 7 ++++- .../debugger/configProviders/baseProvider.ts | 4 +-- src/client/extension.ts | 12 +++------ src/client/terminals/codeExecution/helper.ts | 4 +-- src/client/unittests/codeLenses/main.ts | 4 +-- .../configProvider/provider.attach.test.ts | 22 ++++++++-------- .../debugger/configProvider/provider.test.ts | 26 +++++++++---------- src/test/linters/lintengine.test.ts | 16 ++++++------ .../terminals/codeExecution/helper.test.ts | 14 +++++----- 10 files changed, 55 insertions(+), 55 deletions(-) create mode 100644 news/3 Code Health/1530.md diff --git a/news/3 Code Health/1530.md b/news/3 Code Health/1530.md new file mode 100644 index 000000000000..e5692016a587 --- /dev/null +++ b/news/3 Code Health/1530.md @@ -0,0 +1 @@ +Register language server functionality in the extension against specific resource types supporting the python language. diff --git a/src/client/common/constants.ts b/src/client/common/constants.ts index de0c7d260a9f..52533d6642f1 100644 --- a/src/client/common/constants.ts +++ b/src/client/common/constants.ts @@ -1,5 +1,10 @@ import * as path from 'path'; -export const PythonLanguage = { language: 'python' }; + +export const PYTHON_LANGUAGE = 'python'; +export const PYTHON = [ + { scheme: 'file', language: PYTHON_LANGUAGE }, + { scheme: 'untitled', language: PYTHON_LANGUAGE } +]; export namespace Commands { export const Set_Interpreter = 'python.setInterpreter'; diff --git a/src/client/debugger/configProviders/baseProvider.ts b/src/client/debugger/configProviders/baseProvider.ts index 744dca9c4ea6..87cb4639dfc6 100644 --- a/src/client/debugger/configProviders/baseProvider.ts +++ b/src/client/debugger/configProviders/baseProvider.ts @@ -9,7 +9,7 @@ import { injectable, unmanaged } from 'inversify'; import * as path from 'path'; import { CancellationToken, DebugConfiguration, DebugConfigurationProvider, Uri, WorkspaceFolder } from 'vscode'; import { IDocumentManager, IWorkspaceService } from '../../common/application/types'; -import { PythonLanguage } from '../../common/constants'; +import { PYTHON_LANGUAGE } from '../../common/constants'; import { IConfigurationService } from '../../common/types'; import { IServiceContainer } from '../../ioc/types'; import { BaseAttachRequestArguments, BaseLaunchRequestArguments, DebuggerType, DebugOptions } from '../Common/Contracts'; @@ -105,7 +105,7 @@ export abstract class BaseConfigurationProvider(IDocumentManager); const editor = documentManager.activeTextEditor; - if (editor && editor.document.languageId === PythonLanguage.language) { + if (editor && editor.document.languageId === PYTHON_LANGUAGE) { return editor.document.fileName; } } diff --git a/src/client/extension.ts b/src/client/extension.ts index 627cf7c39385..b14c115bb176 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -7,7 +7,7 @@ if ((Reflect as any).metadata === undefined) { } import { Container } from 'inversify'; import { - debug, Disposable, DocumentFilter, ExtensionContext, + debug, Disposable, ExtensionContext, extensions, IndentAction, languages, Memento, OutputChannel, window } from 'vscode'; @@ -15,7 +15,7 @@ import { AnalysisExtensionActivator } from './activation/analysis'; import { ClassicExtensionActivator } from './activation/classic'; import { IExtensionActivator } from './activation/types'; import { PythonSettings } from './common/configSettings'; -import { isPythonAnalysisEngineTest, STANDARD_OUTPUT_CHANNEL } from './common/constants'; +import { isPythonAnalysisEngineTest, PYTHON, PYTHON_LANGUAGE, STANDARD_OUTPUT_CHANNEL } from './common/constants'; import { FeatureDeprecationManager } from './common/featureDeprecationManager'; import { createDeferred } from './common/helpers'; import { PythonInstaller } from './common/installer/pythonInstallation'; @@ -59,12 +59,6 @@ import { WorkspaceSymbols } from './workspaceSymbols/main'; const activationDeferred = createDeferred(); export const activated = activationDeferred.promise; -const PYTHON_LANGUAGE = 'python'; -const PYTHON: DocumentFilter[] = [ - { scheme: 'file', language: PYTHON_LANGUAGE }, - { scheme: 'untitled', language: PYTHON_LANGUAGE } -]; - // tslint:disable-next-line:max-func-body-length export async function activate(context: ExtensionContext) { const cont = new Container(); @@ -110,7 +104,7 @@ export async function activate(context: ExtensionContext) { // Enable indentAction // tslint:disable-next-line:no-non-null-assertion - languages.setLanguageConfiguration(PYTHON_LANGUAGE!, { + languages.setLanguageConfiguration(PYTHON_LANGUAGE, { onEnterRules: [ { beforeText: /^\s*(?:def|class|for|if|elif|else|while|try|with|finally|except)\b.*:\s*\S+/, diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index f5c2e0baeb27..674d5cfba681 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -4,7 +4,7 @@ import { inject, injectable } from 'inversify'; import { Range, TextEditor, Uri } from 'vscode'; import { IApplicationShell, IDocumentManager } from '../../common/application/types'; -import { PythonLanguage } from '../../common/constants'; +import { PYTHON_LANGUAGE } from '../../common/constants'; import '../../common/extensions'; import { IServiceContainer } from '../../ioc/types'; import { ICodeExecutionHelper } from '../types'; @@ -42,7 +42,7 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { this.applicationShell.showErrorMessage('The active file needs to be saved before it can be run'); return; } - if (activeEditor.document.languageId !== PythonLanguage.language) { + if (activeEditor.document.languageId !== PYTHON_LANGUAGE) { this.applicationShell.showErrorMessage('The active file is not a Python source file'); return; } diff --git a/src/client/unittests/codeLenses/main.ts b/src/client/unittests/codeLenses/main.ts index 3efd30091227..654152ca4e58 100644 --- a/src/client/unittests/codeLenses/main.ts +++ b/src/client/unittests/codeLenses/main.ts @@ -1,5 +1,5 @@ import * as vscode from 'vscode'; -import * as constants from '../../common/constants'; +import { PYTHON } from '../../common/constants'; import { PythonSymbolProvider } from '../../providers/symbolProvider'; import { ITestCollectionStorageService } from '../common/types'; import { TestFileCodeLensProvider } from './testFiles'; @@ -9,7 +9,7 @@ export function activateCodeLenses(onDidChange: vscode.EventEmitter, const disposables: vscode.Disposable[] = []; const codeLensProvider = new TestFileCodeLensProvider(onDidChange, symboldProvider, testCollectionStorage); - disposables.push(vscode.languages.registerCodeLensProvider(constants.PythonLanguage, codeLensProvider)); + disposables.push(vscode.languages.registerCodeLensProvider(PYTHON, codeLensProvider)); return { dispose: () => { disposables.forEach(d => d.dispose()); } diff --git a/src/test/debugger/configProvider/provider.attach.test.ts b/src/test/debugger/configProvider/provider.attach.test.ts index f470616ee7a5..0c3d2b297ddb 100644 --- a/src/test/debugger/configProvider/provider.attach.test.ts +++ b/src/test/debugger/configProvider/provider.attach.test.ts @@ -10,7 +10,7 @@ import * as path from 'path'; import * as TypeMoq from 'typemoq'; import { DebugConfiguration, DebugConfigurationProvider, TextDocument, TextEditor, Uri, WorkspaceFolder } from 'vscode'; import { IDocumentManager, IWorkspaceService } from '../../../client/common/application/types'; -import { PythonLanguage } from '../../../client/common/constants'; +import { PYTHON_LANGUAGE } from '../../../client/common/constants'; import { EnumEx } from '../../../client/common/enumUtils'; import { IFileSystem, IPlatformService } from '../../../client/common/platform/types'; import { PythonDebugConfigurationProvider, PythonV2DebugConfigurationProvider } from '../../../client/debugger'; @@ -77,7 +77,7 @@ enum OS { const workspaceFolder = createMoqWorkspaceFolder(__dirname); const pythonFile = 'xyz.py'; - setupActiveEditor(pythonFile, PythonLanguage.language); + setupActiveEditor(pythonFile, PYTHON_LANGUAGE); const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { request: 'attach' } as DebugConfiguration); @@ -92,7 +92,7 @@ enum OS { test('Defaults should be returned when an empty object is passed without Workspace Folder, no workspaces and active file', async () => { const pythonFile = 'xyz.py'; - setupActiveEditor(pythonFile, PythonLanguage.language); + setupActiveEditor(pythonFile, PYTHON_LANGUAGE); setupWorkspaces([]); const debugConfig = await debugProvider.resolveDebugConfiguration!(undefined, { request: 'attach' } as DebugConfiguration); @@ -108,7 +108,7 @@ enum OS { } }); test('Defaults should be returned when an empty object is passed without Workspace Folder, no workspaces and no active file', async () => { - setupActiveEditor(undefined, PythonLanguage.language); + setupActiveEditor(undefined, PYTHON_LANGUAGE); setupWorkspaces([]); const debugConfig = await debugProvider.resolveDebugConfiguration!(undefined, { request: 'attach' } as DebugConfiguration); @@ -137,7 +137,7 @@ enum OS { }); test('Defaults should be returned when an empty object is passed without Workspace Folder, with a workspace and an active python file', async () => { const activeFile = 'xyz.py'; - setupActiveEditor(activeFile, PythonLanguage.language); + setupActiveEditor(activeFile, PYTHON_LANGUAGE); const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); @@ -156,7 +156,7 @@ enum OS { test('Ensure \'localRoot\' is left unaltered', async () => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); - setupActiveEditor(activeFile, PythonLanguage.language); + setupActiveEditor(activeFile, PYTHON_LANGUAGE); const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); @@ -174,7 +174,7 @@ enum OS { } const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); - setupActiveEditor(activeFile, PythonLanguage.language); + setupActiveEditor(activeFile, PYTHON_LANGUAGE); const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); @@ -191,7 +191,7 @@ enum OS { } const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); - setupActiveEditor(activeFile, PythonLanguage.language); + setupActiveEditor(activeFile, PYTHON_LANGUAGE); const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); @@ -205,7 +205,7 @@ enum OS { test('Ensure \'remoteRoot\' is left unaltered', async () => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); - setupActiveEditor(activeFile, PythonLanguage.language); + setupActiveEditor(activeFile, PYTHON_LANGUAGE); const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); @@ -217,7 +217,7 @@ enum OS { test('Ensure \'port\' is left unaltered', async () => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); - setupActiveEditor(activeFile, PythonLanguage.language); + setupActiveEditor(activeFile, PYTHON_LANGUAGE); const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); @@ -229,7 +229,7 @@ enum OS { test('Ensure \'debugOptions\' are left unaltered', async () => { const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); - setupActiveEditor(activeFile, PythonLanguage.language); + setupActiveEditor(activeFile, PYTHON_LANGUAGE); const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); diff --git a/src/test/debugger/configProvider/provider.test.ts b/src/test/debugger/configProvider/provider.test.ts index 0ea18a47ac56..b73c1b504099 100644 --- a/src/test/debugger/configProvider/provider.test.ts +++ b/src/test/debugger/configProvider/provider.test.ts @@ -10,7 +10,7 @@ import * as path from 'path'; import * as TypeMoq from 'typemoq'; import { DebugConfiguration, DebugConfigurationProvider, TextDocument, TextEditor, Uri, WorkspaceFolder } from 'vscode'; import { IApplicationShell, IDocumentManager, IWorkspaceService } from '../../../client/common/application/types'; -import { PythonLanguage } from '../../../client/common/constants'; +import { PYTHON_LANGUAGE } from '../../../client/common/constants'; import { IFileSystem, IPlatformService } from '../../../client/common/platform/types'; import { IPythonExecutionFactory, IPythonExecutionService } from '../../../client/common/process/types'; import { IConfigurationService, IPythonSettings } from '../../../client/common/types'; @@ -94,7 +94,7 @@ import { IServiceContainer } from '../../../client/ioc/types'; const pythonFile = 'xyz.py'; setupIoc(pythonPath); - setupActiveEditor(pythonFile, PythonLanguage.language); + setupActiveEditor(pythonFile, PYTHON_LANGUAGE); const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, {} as DebugConfiguration); @@ -116,7 +116,7 @@ import { IServiceContainer } from '../../../client/ioc/types'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); const pythonFile = 'xyz.py'; setupIoc(pythonPath); - setupActiveEditor(pythonFile, PythonLanguage.language); + setupActiveEditor(pythonFile, PYTHON_LANGUAGE); const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { noDebug: true } as any as DebugConfiguration); @@ -137,7 +137,7 @@ import { IServiceContainer } from '../../../client/ioc/types'; const pythonPath = `PythonPath_${new Date().toString()}`; const pythonFile = 'xyz.py'; setupIoc(pythonPath); - setupActiveEditor(pythonFile, PythonLanguage.language); + setupActiveEditor(pythonFile, PYTHON_LANGUAGE); setupWorkspaces([]); const debugConfig = await debugProvider.resolveDebugConfiguration!(undefined, {} as DebugConfiguration); @@ -159,7 +159,7 @@ import { IServiceContainer } from '../../../client/ioc/types'; test('Defaults should be returned when an empty object is passed without Workspace Folder, no workspaces and no active file', async () => { const pythonPath = `PythonPath_${new Date().toString()}`; setupIoc(pythonPath); - setupActiveEditor(undefined, PythonLanguage.language); + setupActiveEditor(undefined, PYTHON_LANGUAGE); setupWorkspaces([]); const debugConfig = await debugProvider.resolveDebugConfiguration!(undefined, {} as DebugConfiguration); @@ -199,7 +199,7 @@ import { IServiceContainer } from '../../../client/ioc/types'; const pythonPath = `PythonPath_${new Date().toString()}`; const activeFile = 'xyz.py'; setupIoc(pythonPath); - setupActiveEditor(activeFile, PythonLanguage.language); + setupActiveEditor(activeFile, PYTHON_LANGUAGE); const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); @@ -224,7 +224,7 @@ import { IServiceContainer } from '../../../client/ioc/types'; const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupIoc(pythonPath); - setupActiveEditor(activeFile, PythonLanguage.language); + setupActiveEditor(activeFile, PYTHON_LANGUAGE); const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); @@ -237,7 +237,7 @@ import { IServiceContainer } from '../../../client/ioc/types'; const activeFile = 'xyz.py'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); setupIoc(pythonPath); - setupActiveEditor(activeFile, PythonLanguage.language); + setupActiveEditor(activeFile, PYTHON_LANGUAGE); const defaultWorkspace = path.join('usr', 'desktop'); setupWorkspaces([defaultWorkspace]); @@ -254,7 +254,7 @@ import { IServiceContainer } from '../../../client/ioc/types'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); const pythonFile = 'xyz.py'; setupIoc(pythonPath); - setupActiveEditor(pythonFile, PythonLanguage.language); + setupActiveEditor(pythonFile, PYTHON_LANGUAGE); const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, {} as DebugConfiguration); @@ -271,7 +271,7 @@ import { IServiceContainer } from '../../../client/ioc/types'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); const pythonFile = 'xyz.py'; setupIoc(pythonPath); - setupActiveEditor(pythonFile, PythonLanguage.language); + setupActiveEditor(pythonFile, PYTHON_LANGUAGE); const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, {} as DebugConfiguration); @@ -284,7 +284,7 @@ import { IServiceContainer } from '../../../client/ioc/types'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); const pythonFile = 'xyz.py'; setupIoc(pythonPath, isWindows, isMac, isLinux); - setupActiveEditor(pythonFile, PythonLanguage.language); + setupActiveEditor(pythonFile, PYTHON_LANGUAGE); const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, {} as DebugConfiguration); if (isWindows) { @@ -321,7 +321,7 @@ import { IServiceContainer } from '../../../client/ioc/types'; const pythonFile = 'xyz.py'; setupIoc(pythonPath, isWindows, isMac, isLinux); - setupActiveEditor(pythonFile, PythonLanguage.language); + setupActiveEditor(pythonFile, PYTHON_LANGUAGE); const execOutput = pyramidExists ? Promise.resolve({ stdout: pyramidFilePath }) : Promise.reject(new Error('No Module')); pythonExecutionService.setup(e => e.exec(TypeMoq.It.isValue(args), TypeMoq.It.isAny())) @@ -379,7 +379,7 @@ import { IServiceContainer } from '../../../client/ioc/types'; const workspaceFolder = createMoqWorkspaceFolder(__dirname); const pythonFile = 'xyz.py'; setupIoc(pythonPath); - setupActiveEditor(pythonFile, PythonLanguage.language); + setupActiveEditor(pythonFile, PYTHON_LANGUAGE); const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { module: 'flask' } as any as DebugConfiguration); diff --git a/src/test/linters/lintengine.test.ts b/src/test/linters/lintengine.test.ts index 44520df46cdc..e11c2dcdc5c4 100644 --- a/src/test/linters/lintengine.test.ts +++ b/src/test/linters/lintengine.test.ts @@ -4,7 +4,7 @@ import * as TypeMoq from 'typemoq'; import { OutputChannel, TextDocument, Uri } from 'vscode'; import { IDocumentManager, IWorkspaceService } from '../../client/common/application/types'; -import { PythonLanguage, STANDARD_OUTPUT_CHANNEL } from '../../client/common/constants'; +import { PYTHON_LANGUAGE, STANDARD_OUTPUT_CHANNEL } from '../../client/common/constants'; import '../../client/common/extensions'; import { IFileSystem } from '../../client/common/platform/types'; import { IConfigurationService, ILintingSettings, IOutputChannel, IPythonSettings } from '../../client/common/types'; @@ -55,7 +55,7 @@ suite('Linting - LintingEngine', () => { }); test('Ensure document.uri is passed into isLintingEnabled', () => { - const doc = mockTextDocument('a.py', PythonLanguage.language, true); + const doc = mockTextDocument('a.py', PYTHON_LANGUAGE, true); try { lintingEngine.lintDocument(doc, 'auto').ignoreErrors(); } catch { @@ -63,7 +63,7 @@ suite('Linting - LintingEngine', () => { } }); test('Ensure document.uri is passed into createLinter', () => { - const doc = mockTextDocument('a.py', PythonLanguage.language, true); + const doc = mockTextDocument('a.py', PYTHON_LANGUAGE, true); try { lintingEngine.lintDocument(doc, 'auto').ignoreErrors(); } catch { @@ -72,7 +72,7 @@ suite('Linting - LintingEngine', () => { }); test('Verify files that match ignore pattern are not linted', async () => { - const doc = mockTextDocument('a1.py', PythonLanguage.language, true, ['a*.py']); + const doc = mockTextDocument('a1.py', PYTHON_LANGUAGE, true, ['a*.py']); await lintingEngine.lintDocument(doc, 'auto'); lintManager.verify(l => l.createLinter(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.never()); }); @@ -84,23 +84,23 @@ suite('Linting - LintingEngine', () => { }); test('Ensure files with git scheme are not linted', async () => { - const doc = mockTextDocument('a1.py', PythonLanguage.language, false, [], 'git'); + const doc = mockTextDocument('a1.py', PYTHON_LANGUAGE, false, [], 'git'); await lintingEngine.lintDocument(doc, 'auto'); lintManager.verify(l => l.createLinter(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.never()); }); test('Ensure files with showModifications scheme are not linted', async () => { - const doc = mockTextDocument('a1.py', PythonLanguage.language, false, [], 'showModifications'); + const doc = mockTextDocument('a1.py', PYTHON_LANGUAGE, false, [], 'showModifications'); await lintingEngine.lintDocument(doc, 'auto'); lintManager.verify(l => l.createLinter(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.never()); }); test('Ensure files with svn scheme are not linted', async () => { - const doc = mockTextDocument('a1.py', PythonLanguage.language, false, [], 'svn'); + const doc = mockTextDocument('a1.py', PYTHON_LANGUAGE, false, [], 'svn'); await lintingEngine.lintDocument(doc, 'auto'); lintManager.verify(l => l.createLinter(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.never()); }); test('Ensure non-existing files are not linted', async () => { - const doc = mockTextDocument('file.py', PythonLanguage.language, false, []); + const doc = mockTextDocument('file.py', PYTHON_LANGUAGE, false, []); await lintingEngine.lintDocument(doc, 'auto'); lintManager.verify(l => l.createLinter(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.never()); }); diff --git a/src/test/terminals/codeExecution/helper.test.ts b/src/test/terminals/codeExecution/helper.test.ts index a9344344d548..1d0347c8d48b 100644 --- a/src/test/terminals/codeExecution/helper.test.ts +++ b/src/test/terminals/codeExecution/helper.test.ts @@ -10,7 +10,7 @@ import * as path from 'path'; import * as TypeMoq from 'typemoq'; import { Range, Selection, TextDocument, TextEditor, TextLine, Uri } from 'vscode'; import { IApplicationShell, IDocumentManager } from '../../../client/common/application/types'; -import { EXTENSION_ROOT_DIR, PythonLanguage } from '../../../client/common/constants'; +import { EXTENSION_ROOT_DIR, PYTHON_LANGUAGE } from '../../../client/common/constants'; import { IServiceContainer } from '../../../client/ioc/types'; import { CodeExecutionHelper } from '../../../client/terminals/codeExecution/helper'; import { ICodeExecutionHelper } from '../../../client/terminals/types'; @@ -92,7 +92,7 @@ suite('Terminal - Code Execution Helper', () => { test('Returns file uri', async () => { document.setup(doc => doc.isUntitled).returns(() => false); - document.setup(doc => doc.languageId).returns(() => PythonLanguage.language); + document.setup(doc => doc.languageId).returns(() => PYTHON_LANGUAGE); const expectedUri = Uri.file('one.py'); document.setup(doc => doc.uri).returns(() => expectedUri); documentManager.setup(doc => doc.activeTextEditor).returns(() => editor.object); @@ -104,7 +104,7 @@ suite('Terminal - Code Execution Helper', () => { test('Returns file uri even if saving fails', async () => { document.setup(doc => doc.isUntitled).returns(() => false); document.setup(doc => doc.isDirty).returns(() => true); - document.setup(doc => doc.languageId).returns(() => PythonLanguage.language); + document.setup(doc => doc.languageId).returns(() => PYTHON_LANGUAGE); document.setup(doc => doc.save()).returns(() => Promise.resolve(false)); const expectedUri = Uri.file('one.py'); document.setup(doc => doc.uri).returns(() => expectedUri); @@ -117,7 +117,7 @@ suite('Terminal - Code Execution Helper', () => { test('Dirty files are saved', async () => { document.setup(doc => doc.isUntitled).returns(() => false); document.setup(doc => doc.isDirty).returns(() => true); - document.setup(doc => doc.languageId).returns(() => PythonLanguage.language); + document.setup(doc => doc.languageId).returns(() => PYTHON_LANGUAGE); const expectedUri = Uri.file('one.py'); document.setup(doc => doc.uri).returns(() => expectedUri); documentManager.setup(doc => doc.activeTextEditor).returns(() => editor.object); @@ -130,7 +130,7 @@ suite('Terminal - Code Execution Helper', () => { test('Non-Dirty files are not-saved', async () => { document.setup(doc => doc.isUntitled).returns(() => false); document.setup(doc => doc.isDirty).returns(() => false); - document.setup(doc => doc.languageId).returns(() => PythonLanguage.language); + document.setup(doc => doc.languageId).returns(() => PYTHON_LANGUAGE); const expectedUri = Uri.file('one.py'); document.setup(doc => doc.uri).returns(() => expectedUri); documentManager.setup(doc => doc.activeTextEditor).returns(() => editor.object); @@ -173,7 +173,7 @@ suite('Terminal - Code Execution Helper', () => { documentManager.setup(d => d.textDocuments).returns(() => [document.object]).verifiable(TypeMoq.Times.once()); document.setup(doc => doc.isUntitled).returns(() => false); document.setup(doc => doc.isDirty).returns(() => true); - document.setup(doc => doc.languageId).returns(() => PythonLanguage.language); + document.setup(doc => doc.languageId).returns(() => PYTHON_LANGUAGE); const expectedUri = Uri.file('one.py'); document.setup(doc => doc.uri).returns(() => expectedUri); @@ -186,7 +186,7 @@ suite('Terminal - Code Execution Helper', () => { documentManager.setup(d => d.textDocuments).returns(() => [document.object]).verifiable(TypeMoq.Times.once()); document.setup(doc => doc.isUntitled).returns(() => false); document.setup(doc => doc.isDirty).returns(() => false); - document.setup(doc => doc.languageId).returns(() => PythonLanguage.language); + document.setup(doc => doc.languageId).returns(() => PYTHON_LANGUAGE); const expectedUri = Uri.file('one.py'); document.setup(doc => doc.uri).returns(() => expectedUri); From ffb7c69f309093fefca8650f5258050ce5326c6a Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 27 Apr 2018 16:03:29 -0700 Subject: [PATCH 199/433] Ensure Flask application is launched with multi-threading disabled in CI tests (#1543) * Fix CI tests * update entry * Fixes #1535 --- news/2 Fixes/1535.md | 1 + src/test/debugger/web.framework.test.ts | 1 + 2 files changed, 2 insertions(+) create mode 100644 news/2 Fixes/1535.md diff --git a/news/2 Fixes/1535.md b/news/2 Fixes/1535.md new file mode 100644 index 000000000000..04b3bc69c3dd --- /dev/null +++ b/news/2 Fixes/1535.md @@ -0,0 +1 @@ +Ensure Flask application is launched with multi-threading disabled, when run in the CI tests. diff --git a/src/test/debugger/web.framework.test.ts b/src/test/debugger/web.framework.test.ts index 0036fe9041f3..9b23a691f83c 100644 --- a/src/test/debugger/web.framework.test.ts +++ b/src/test/debugger/web.framework.test.ts @@ -69,6 +69,7 @@ suite(`Django and Flask Debugging: ${debuggerType}`, () => { 'run', '--no-debugger', '--no-reload', + '--without-threads', '--port', `${port}` ]; From 9f0da31d0414e7f9c35b10f5cb9c0f11a1d7f7ad Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 27 Apr 2018 16:23:18 -0700 Subject: [PATCH 200/433] Increase delay for activation of powershell terminals (#1537) Fixes #1533 --- news/2 Fixes/1533.md | 1 + src/client/common/terminal/service.ts | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 news/2 Fixes/1533.md diff --git a/news/2 Fixes/1533.md b/news/2 Fixes/1533.md new file mode 100644 index 000000000000..30f0e0452b1a --- /dev/null +++ b/news/2 Fixes/1533.md @@ -0,0 +1 @@ +Increase the delay for the activation of environments in Powershell terminals. diff --git a/src/client/common/terminal/service.ts b/src/client/common/terminal/service.ts index 7e54a853b1dc..0fe4ddaa5f3d 100644 --- a/src/client/common/terminal/service.ts +++ b/src/client/common/terminal/service.ts @@ -5,6 +5,7 @@ import { inject, injectable } from 'inversify'; import { Disposable, Event, EventEmitter, Terminal, Uri } from 'vscode'; import { IServiceContainer } from '../../ioc/types'; import { ITerminalManager } from '../application/types'; +import { sleep } from '../core.utils'; import { IDisposableRegistry } from '../types'; import { ITerminalHelper, ITerminalService, TerminalShellType } from './types'; @@ -67,7 +68,8 @@ export class TerminalService implements ITerminalService, Disposable { // Give the command some time to complete. // Its been observed that sending commands too early will strip some text off. - await new Promise(resolve => setTimeout(resolve, 500)); + const delay = (this.terminalShellType === TerminalShellType.powershell || TerminalShellType.powershellCore) ? 1000 : 500; + await sleep(delay); } } From fe3192bc5156612a61cbdc548ed1acdf304636e4 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 27 Apr 2018 16:23:33 -0700 Subject: [PATCH 201/433] Enable jinja template debugging when debugging pyramid apps (#1511) Fixes #1492 --- news/1 Enhancements/1492.md | 1 + package.json | 3 ++- src/client/debugger/configProviders/pythonV2Provider.ts | 8 +++++++- src/test/debugger/configProvider/provider.test.ts | 9 +++++++-- 4 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 news/1 Enhancements/1492.md diff --git a/news/1 Enhancements/1492.md b/news/1 Enhancements/1492.md new file mode 100644 index 000000000000..fe8decf90818 --- /dev/null +++ b/news/1 Enhancements/1492.md @@ -0,0 +1 @@ +Enable Jinja template debugging as a default behavior when debugging Pyramid applications. diff --git a/package.json b/package.json index 32a415cede29..9ec66d4aec47 100644 --- a/package.json +++ b/package.json @@ -872,7 +872,8 @@ "args": [ "^\"\\${workspaceFolder}/development.ini\"" ], - "pyramid": true + "pyramid": true, + "jinja": true } }, { diff --git a/src/client/debugger/configProviders/pythonV2Provider.ts b/src/client/debugger/configProviders/pythonV2Provider.ts index 4b60be89a688..89ac776dddd4 100644 --- a/src/client/debugger/configProviders/pythonV2Provider.ts +++ b/src/client/debugger/configProviders/pythonV2Provider.ts @@ -37,7 +37,8 @@ export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvide if (this.serviceContainer.get(IPlatformService).isWindows) { this.debugOption(debugOptions, DebugOptions.FixFilePathCase); } - if (debugConfiguration.module && debugConfiguration.module.toUpperCase() === 'FLASK' + const isFlask = debugConfiguration.module && debugConfiguration.module.toUpperCase() === 'FLASK'; + if ((debugConfiguration.pyramid || isFlask) && debugOptions.indexOf(DebugOptions.Jinja) === -1 && debugConfiguration.jinja !== false) { this.debugOption(debugOptions, DebugOptions.Jinja); @@ -59,6 +60,11 @@ export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvide if (debugConfiguration.jinja) { this.debugOption(debugOptions, DebugOptions.Jinja); } + if (debugConfiguration.pyramid + && debugOptions.indexOf(DebugOptions.Jinja) === -1 + && debugConfiguration.jinja !== false) { + this.debugOption(debugOptions, DebugOptions.Jinja); + } if (debugConfiguration.redirectOutput || debugConfiguration.redirectOutput === undefined) { this.debugOption(debugOptions, DebugOptions.RedirectOutput); } diff --git a/src/test/debugger/configProvider/provider.test.ts b/src/test/debugger/configProvider/provider.test.ts index b73c1b504099..c84a84833d69 100644 --- a/src/test/debugger/configProvider/provider.test.ts +++ b/src/test/debugger/configProvider/provider.test.ts @@ -323,7 +323,7 @@ import { IServiceContainer } from '../../../client/ioc/types'; setupIoc(pythonPath, isWindows, isMac, isLinux); setupActiveEditor(pythonFile, PYTHON_LANGUAGE); - const execOutput = pyramidExists ? Promise.resolve({ stdout: pyramidFilePath }) : Promise.reject(new Error('No Module')); + const execOutput = pyramidExists ? Promise.resolve({ stdout: pyramidFilePath }) : Promise.reject('No Module'); pythonExecutionService.setup(e => e.exec(TypeMoq.It.isValue(args), TypeMoq.It.isAny())) .returns(() => execOutput) .verifiable(TypeMoq.Times.exactly(addPyramidDebugOption ? 1 : 0)); @@ -337,13 +337,18 @@ import { IServiceContainer } from '../../../client/ioc/types'; const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, options as any as DebugConfiguration); if (shouldWork) { expect(debugConfig).to.have.property('program', pserveFilePath); + + if (provider.debugType === 'pythonExperimental') { + expect(debugConfig).to.have.property('debugOptions'); + expect((debugConfig as any).debugOptions).contains(DebugOptions.Jinja); + } } else { expect(debugConfig!.program).to.be.not.equal(pserveFilePath); } pythonExecutionService.verifyAll(); fileSystem.verifyAll(); appShell.verifyAll(); - } + } test('Program is set for Pyramid (windows)', async () => { await testPyramidConfiguration(true, false, false); }); From 1360f5e427f41a9ad8a1f43176f2c416d2b38e1b Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 27 Apr 2018 16:24:25 -0700 Subject: [PATCH 202/433] Fix activation of conda environments in Powershell (#1536) Fixes #1520 Fixes #1534 --- news/2 Fixes/1520.md | 1 + news/2 Fixes/1534.md | 1 + .../commandPrompt.ts | 4 +-- .../condaActivationProvider.ts | 12 +++++-- .../activation.commandPrompt.test.ts | 8 ++--- .../common/terminals/activation.conda.test.ts | 34 +++++++++++++++---- 6 files changed, 44 insertions(+), 16 deletions(-) create mode 100644 news/2 Fixes/1520.md create mode 100644 news/2 Fixes/1534.md diff --git a/news/2 Fixes/1520.md b/news/2 Fixes/1520.md new file mode 100644 index 000000000000..5f5d52ec9e44 --- /dev/null +++ b/news/2 Fixes/1520.md @@ -0,0 +1 @@ +Fixes the issue where Conda environments created using the latest version of Anaconda are not activated in Powershell. diff --git a/news/2 Fixes/1534.md b/news/2 Fixes/1534.md new file mode 100644 index 000000000000..c0e5b965116c --- /dev/null +++ b/news/2 Fixes/1534.md @@ -0,0 +1 @@ +Fix activation of environments with spaces in the python path when using Powershell. diff --git a/src/client/common/terminal/environmentActivationProviders/commandPrompt.ts b/src/client/common/terminal/environmentActivationProviders/commandPrompt.ts index b7d8f24d86b9..f9c8ca77311b 100644 --- a/src/client/common/terminal/environmentActivationProviders/commandPrompt.ts +++ b/src/client/common/terminal/environmentActivationProviders/commandPrompt.ts @@ -12,7 +12,7 @@ import { BaseActivationCommandProvider } from './baseActivationProvider'; @injectable() export class CommandPromptAndPowerShell extends BaseActivationCommandProvider { - constructor( @inject(IServiceContainer) serviceContainer: IServiceContainer) { + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { super(serviceContainer); } public isShellSupported(targetShell: TerminalShellType): boolean { @@ -41,7 +41,7 @@ export class CommandPromptAndPowerShell extends BaseActivationCommandProvider { const powershellExe = targetShell === TerminalShellType.powershell ? 'powershell' : 'pwsh'; const activationCmd = scriptFile.fileToCommandArgument(); return [ - `& cmd /k "${activationCmd} & ${powershellExe}"` + `& cmd /k "${activationCmd.replace(/"/g, '""')} & ${powershellExe}"` ]; } else { // Powershell on non-windows os, we cannot execute the batch file. diff --git a/src/client/common/terminal/environmentActivationProviders/condaActivationProvider.ts b/src/client/common/terminal/environmentActivationProviders/condaActivationProvider.ts index 51cb12e88e7e..e3f73deecf90 100644 --- a/src/client/common/terminal/environmentActivationProviders/condaActivationProvider.ts +++ b/src/client/common/terminal/environmentActivationProviders/condaActivationProvider.ts @@ -8,8 +8,7 @@ import { IServiceContainer } from '../../../ioc/types'; import '../../extensions'; import { IPlatformService } from '../../platform/types'; import { IConfigurationService } from '../../types'; -import { TerminalShellType } from '../types'; -import { ITerminalActivationCommandProvider } from '../types'; +import { ITerminalActivationCommandProvider, TerminalShellType } from '../types'; @injectable() export class CondaActivationCommandProvider implements ITerminalActivationCommandProvider { @@ -29,8 +28,15 @@ export class CondaActivationCommandProvider implements ITerminalActivationComman const isWindows = this.serviceContainer.get(IPlatformService).isWindows; if (targetShell === TerminalShellType.powershell || targetShell === TerminalShellType.powershellCore) { + if (!isWindows) { + return; + } // https://github.com/conda/conda/issues/626 - return; + // On windows, the solution is to go into cmd, then run the batch (.bat) file and go back into powershell. + const powershellExe = targetShell === TerminalShellType.powershell ? 'powershell' : 'pwsh'; + return [ + `& cmd /k "activate ${envInfo.name.toCommandArgument().replace(/"/g, '""')} & ${powershellExe}"` + ]; } else if (targetShell === TerminalShellType.fish) { // https://github.com/conda/conda/blob/be8c08c083f4d5e05b06bd2689d2cd0d410c2ffe/shell/etc/fish/conf.d/conda.fish#L18-L28 return [`conda activate ${envInfo.name.toCommandArgument()}`]; diff --git a/src/test/common/terminals/activation.commandPrompt.test.ts b/src/test/common/terminals/activation.commandPrompt.test.ts index f6c7509ce00d..ce1942bc3b88 100644 --- a/src/test/common/terminals/activation.commandPrompt.test.ts +++ b/src/test/common/terminals/activation.commandPrompt.test.ts @@ -97,17 +97,17 @@ suite('Terminal Environment Activation (cmd/powershell)', () => { }); test('Ensure batch files are supported by powershell (on windows)', async () => { - const bash = new CommandPromptAndPowerShell(serviceContainer.object); + const batch = new CommandPromptAndPowerShell(serviceContainer.object); platform.setup(p => p.isWindows).returns(() => true); const pathToScriptFile = path.join(path.dirname(pythonPath), 'activate.bat'); fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(pathToScriptFile))).returns(() => Promise.resolve(true)); - const command = await bash.getActivationCommands(resource, TerminalShellType.powershell); + const command = await batch.getActivationCommands(resource, TerminalShellType.powershell); // Executing batch files from powershell requires going back to cmd, then into powershell const activationCommand = pathToScriptFile.fileToCommandArgument(); - const commands = [`& cmd /k "${activationCommand} & powershell"`]; + const commands = [`& cmd /k "${activationCommand.replace(/"/g, '""')} & powershell"`]; expect(command).to.be.deep.equal(commands, 'Invalid command'); }); @@ -122,7 +122,7 @@ suite('Terminal Environment Activation (cmd/powershell)', () => { // Executing batch files from powershell requires going back to cmd, then into powershell const activationCommand = pathToScriptFile.fileToCommandArgument(); - const commands = [`& cmd /k "${activationCommand} & pwsh"`]; + const commands = [`& cmd /k "${activationCommand.replace(/"/g, '""')} & pwsh"`]; expect(command).to.be.deep.equal(commands, 'Invalid command'); }); diff --git a/src/test/common/terminals/activation.conda.test.ts b/src/test/common/terminals/activation.conda.test.ts index c9570ef54d94..d1d868dcc44f 100644 --- a/src/test/common/terminals/activation.conda.test.ts +++ b/src/test/common/terminals/activation.conda.test.ts @@ -6,6 +6,7 @@ import * as path from 'path'; import * as TypeMoq from 'typemoq'; import { Disposable } from 'vscode'; import { EnumEx } from '../../../client/common/enumUtils'; +import '../../../client/common/extensions'; import { IFileSystem, IPlatformService } from '../../../client/common/platform/types'; import { IProcessService } from '../../../client/common/process/types'; import { CondaActivationCommandProvider } from '../../../client/common/terminal/environmentActivationProviders/condaActivationProvider'; @@ -67,29 +68,32 @@ suite('Terminal Environment Activation conda', () => { expect(activationCommands).to.equal(undefined, 'Activation commands should be undefined'); }); - async function expectNoCondaActivationCommandForPowershell(isWindows: boolean, isOsx: boolean, isLinux: boolean, pythonPath: string, shellType: TerminalShellType) { + async function expectNoCondaActivationCommandForPowershell(isWindows: boolean, isOsx: boolean, isLinux: boolean, pythonPath: string, shellType: TerminalShellType, hasSpaceInEnvironmentName = false) { terminalSettings.setup(t => t.activateEnvironment).returns(() => true); platformService.setup(p => p.isLinux).returns(() => isLinux); platformService.setup(p => p.isWindows).returns(() => isWindows); platformService.setup(p => p.isMac).returns(() => isOsx); condaService.setup(c => c.isCondaEnvironment(TypeMoq.It.isAny())).returns(() => Promise.resolve(true)); pythonSettings.setup(s => s.pythonPath).returns(() => pythonPath); - condaService.setup(c => c.getCondaEnvironment(TypeMoq.It.isAny())).returns(() => Promise.resolve({ name: 'EnvA', path: path.dirname(pythonPath) })); + const envName = hasSpaceInEnvironmentName ? 'EnvA' : 'Env A'; + condaService.setup(c => c.getCondaEnvironment(TypeMoq.It.isAny())).returns(() => Promise.resolve({ name: envName, path: path.dirname(pythonPath) })); const activationCommands = await new CondaActivationCommandProvider(serviceContainer.object).getActivationCommands(undefined, shellType); let expectedActivationCommamnd: string[] | undefined; switch (shellType) { case TerminalShellType.powershell: case TerminalShellType.powershellCore: { - expectedActivationCommamnd = undefined; + const powershellExe = shellType === TerminalShellType.powershell ? 'powershell' : 'pwsh'; + const envNameForCmd = envName.toCommandArgument().replace(/"/g, '""'); + expectedActivationCommamnd = isWindows ? [`& cmd /k \"activate ${envNameForCmd} & ${powershellExe}\"`] : undefined; break; } case TerminalShellType.fish: { - expectedActivationCommamnd = ['conda activate EnvA']; + expectedActivationCommamnd = [`conda activate ${envName.toCommandArgument()}`]; break; } default: { - expectedActivationCommamnd = isWindows ? ['activate EnvA'] : ['source activate EnvA']; + expectedActivationCommamnd = isWindows ? [`activate ${envName.toCommandArgument()}`] : [`source activate ${envName.toCommandArgument()}`]; break; } } @@ -101,16 +105,32 @@ suite('Terminal Environment Activation conda', () => { await expectNoCondaActivationCommandForPowershell(true, false, false, pythonPath, shellType.value); }); - test(`Conda activation command for shell ${shellType.name} on (windows)`, async () => { + test(`Conda activation command for shell ${shellType.name} on (linux)`, async () => { const pythonPath = path.join('users', 'xyz', '.conda', 'envs', 'enva', 'bin', 'python'); await expectNoCondaActivationCommandForPowershell(false, false, true, pythonPath, shellType.value); }); - test(`Conda activation command for shell ${shellType.name} on (linux)`, async () => { + test(`Conda activation command for shell ${shellType.name} on (mac)`, async () => { const pythonPath = path.join('users', 'xyz', '.conda', 'envs', 'enva', 'bin', 'python'); await expectNoCondaActivationCommandForPowershell(false, true, false, pythonPath, shellType.value); }); }); + EnumEx.getNamesAndValues(TerminalShellType).forEach(shellType => { + test(`Conda activation command for shell ${shellType.name} on (windows), containing spaces in environment name`, async () => { + const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'enva', 'python.exe'); + await expectNoCondaActivationCommandForPowershell(true, false, false, pythonPath, shellType.value, true); + }); + + test(`Conda activation command for shell ${shellType.name} on (linux), containing spaces in environment name`, async () => { + const pythonPath = path.join('users', 'xyz', '.conda', 'envs', 'enva', 'bin', 'python'); + await expectNoCondaActivationCommandForPowershell(false, false, true, pythonPath, shellType.value, true); + }); + + test(`Conda activation command for shell ${shellType.name} on (mac), containing spaces in environment name`, async () => { + const pythonPath = path.join('users', 'xyz', '.conda', 'envs', 'enva', 'bin', 'python'); + await expectNoCondaActivationCommandForPowershell(false, true, false, pythonPath, shellType.value, true); + }); + }); async function expectCondaActivationCommand(isWindows: boolean, isOsx: boolean, isLinux: boolean, pythonPath: string) { terminalSettings.setup(t => t.activateEnvironment).returns(() => true); platformService.setup(p => p.isLinux).returns(() => isLinux); From 127d843a70beb48fc5440fb34bfc0098b5028055 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 30 Apr 2018 12:06:48 -0700 Subject: [PATCH 203/433] Ensure users can override defaults in experimental debugger (#1540) * Ensure users can override defaults in experimental debugger * Fixes #1539 --- .../debugger/configProviders/baseProvider.ts | 6 +----- .../configProviders/pythonProvider.ts | 4 ++++ .../debugger/configProvider/provider.test.ts | 20 ++++++++++++++++++- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/client/debugger/configProviders/baseProvider.ts b/src/client/debugger/configProviders/baseProvider.ts index 87cb4639dfc6..81c86c7520a7 100644 --- a/src/client/debugger/configProviders/baseProvider.ts +++ b/src/client/debugger/configProviders/baseProvider.ts @@ -12,7 +12,7 @@ import { IDocumentManager, IWorkspaceService } from '../../common/application/ty import { PYTHON_LANGUAGE } from '../../common/constants'; import { IConfigurationService } from '../../common/types'; import { IServiceContainer } from '../../ioc/types'; -import { BaseAttachRequestArguments, BaseLaunchRequestArguments, DebuggerType, DebugOptions } from '../Common/Contracts'; +import { BaseAttachRequestArguments, BaseLaunchRequestArguments, DebuggerType } from '../Common/Contracts'; export type PythonLaunchDebugConfiguration = DebugConfiguration & T; export type PythonAttachDebugConfiguration = DebugConfiguration & T; @@ -78,10 +78,6 @@ export abstract class BaseConfigurationProvider): Promise { await super.provideLaunchDefaults(workspaceFolder, debugConfiguration); + // Always redirect output. + if (debugConfiguration.debugOptions!.indexOf(DebugOptions.RedirectOutput) === -1) { + debugConfiguration.debugOptions!.push(DebugOptions.RedirectOutput); + } if (debugConfiguration.debugOptions!.indexOf(DebugOptions.Pyramid) >= 0) { const utils = this.serviceContainer.get(IConfigurationProviderUtils); debugConfiguration.program = (await utils.getPyramidStartupScriptFilePath(workspaceFolder))!; diff --git a/src/test/debugger/configProvider/provider.test.ts b/src/test/debugger/configProvider/provider.test.ts index c84a84833d69..94ca215e76db 100644 --- a/src/test/debugger/configProvider/provider.test.ts +++ b/src/test/debugger/configProvider/provider.test.ts @@ -15,7 +15,8 @@ import { IFileSystem, IPlatformService } from '../../../client/common/platform/t import { IPythonExecutionFactory, IPythonExecutionService } from '../../../client/common/process/types'; import { IConfigurationService, IPythonSettings } from '../../../client/common/types'; import { PythonDebugConfigurationProvider, PythonV2DebugConfigurationProvider } from '../../../client/debugger'; -import { DebugOptions } from '../../../client/debugger/Common/Contracts'; +import { DebugOptions, LaunchRequestArguments } from '../../../client/debugger/Common/Contracts'; +import { PythonLaunchDebugConfiguration } from '../../../client/debugger/configProviders/baseProvider'; import { ConfigurationProviderUtils } from '../../../client/debugger/configProviders/configurationProviderUtils'; import { IConfigurationProviderUtils } from '../../../client/debugger/configProviders/types'; import { IServiceContainer } from '../../../client/ioc/types'; @@ -279,6 +280,23 @@ import { IServiceContainer } from '../../../client/ioc/types'; expect(debugConfig).to.have.property('debugOptions'); expect((debugConfig as any).debugOptions).to.be.deep.equal([DebugOptions.RedirectOutput]); }); + test('Test overriding defaults of experimental debugger', async () => { + if (provider.debugType !== 'pythonExperimental') { + return; + } + const pythonPath = `PythonPath_${new Date().toString()}`; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + const pythonFile = 'xyz.py'; + setupIoc(pythonPath); + setupActiveEditor(pythonFile, PYTHON_LANGUAGE); + + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { redirectOutput: false } as PythonLaunchDebugConfiguration); + + expect(debugConfig).to.have.property('console', 'integratedTerminal'); + expect(debugConfig).to.have.property('stopOnEntry', false); + expect(debugConfig).to.have.property('debugOptions'); + expect((debugConfig as any).debugOptions).to.be.deep.equal([]); + }); async function testFixFilePathCase(isWindows: boolean, isMac: boolean, isLinux: boolean) { const pythonPath = `PythonPath_${new Date().toString()}`; const workspaceFolder = createMoqWorkspaceFolder(__dirname); From 3c187d772f40dabbbbe9e7e91d779a78406f99fb Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 30 Apr 2018 12:07:07 -0700 Subject: [PATCH 204/433] Create and pass environment object to the jedi.Script API (#1544) * Create environment for Jedi * Build environment object once * Fixes #1532 --- pythonFiles/completion.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/pythonFiles/completion.py b/pythonFiles/completion.py index ed74a830e095..c130673c6b06 100644 --- a/pythonFiles/completion.py +++ b/pythonFiles/completion.py @@ -33,6 +33,7 @@ class JediCompletion(object): def __init__(self): self.default_sys_path = sys.path + self.environment = jedi.api.environment.Environment(sys.prefix, sys.executable) self._input = io.open(sys.stdin.fileno(), encoding='utf-8') if (os.path.sep == '/') and (platform.uname()[2].find('Microsoft') > -1): # WSL; does not support UNC paths @@ -150,7 +151,7 @@ def _get_call_signatures_with_args(self, script): except Exception: sig["docstring"] = '' sig["raw_docstring"] = '' - + sig["name"] = signature.name sig["paramindex"] = signature.index sig["bracketstart"].append(signature.index) @@ -199,7 +200,7 @@ def _serialize_completions(self, script, identifier=None, prefix=''): } _completion['description'] = '' _completion['raw_docstring'] = '' - + # we pass 'text' here only for fuzzy matcher if value: _completion['snippet'] = '%s=${1:%s}$0' % (name, value) @@ -223,7 +224,7 @@ def _serialize_completions(self, script, identifier=None, prefix=''): 'type': self._get_definition_type(completion), 'raw_type': completion.type, 'rightLabel': self._additional_info(completion) - } + } except Exception: continue @@ -231,7 +232,7 @@ def _serialize_completions(self, script, identifier=None, prefix=''): if c['text'] == _completion['text']: c['type'] = _completion['type'] c['raw_type'] = _completion['raw_type'] - + if any([c['text'].split('=')[0] == _completion['text'] for c in _completions]): # ignore function arguments we already have @@ -361,7 +362,7 @@ def _get_definitionsx(self, definitions, identifier=None, ignoreNoModulePath=Fal definition = self._top_definition(definition) definitionRange = { 'start_line': 0, - 'start_column': 0, + 'start_column': 0, 'end_line': 0, 'end_column': 0 } @@ -377,7 +378,7 @@ def _get_definitionsx(self, definitions, identifier=None, ignoreNoModulePath=Fal container = parent.name if parent.type != 'module' else '' except Exception: container = '' - + try: docstring = definition.docstring() rawdocstring = definition.docstring(raw=True) @@ -424,7 +425,7 @@ def _serialize_definitions(self, definitions, identifier=None): container = parent.name if parent.type != 'module' else '' except Exception: container = '' - + try: docstring = definition.docstring() rawdocstring = definition.docstring(raw=True) @@ -474,7 +475,7 @@ def _serialize_tooltip(self, definitions, identifier=None): 'type': self._get_definition_type(definition), 'text': definition.name, 'description': description, - 'docstring': description, + 'docstring': description, 'signature': signature } _definitions.append(_definition) @@ -566,8 +567,8 @@ def _process_request(self, request): script = jedi.Script( source=request.get('source', None), line=request['line'] + 1, column=request['column'], path=request.get('path', ''), - sys_path=sys.path) - + sys_path=sys.path, environment=self.environment) + if lookup == 'definitions': defs = [] try: @@ -625,7 +626,7 @@ def watch(self): try: rq = self._input.readline() if len(rq) == 0: - # Reached EOF - indication our parent process is gone. + # Reached EOF - indication our parent process is gone. sys.stderr.write('Received EOF from the standard input,exiting' + '\n') sys.stderr.flush() return From aac2f8a9e8041ac8a4d189fd921086a0059ebcfa Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 30 Apr 2018 15:31:07 -0700 Subject: [PATCH 205/433] Add blank lines to separate blocks of indented code (#1515) Fixes #259 --- news/2 Fixes/259.md | 1 + pythonFiles/normalizeForInterpreter.py | 116 ++++++++++++++++++ src/client/terminals/codeExecution/helper.ts | 21 +++- .../terminalExec/sample1_normalized.py | 5 +- .../terminalExec/sample3_normalized.py | 1 + .../pythonFiles/terminalExec/sample3_raw.py | 1 + .../terminalExec/sample6_normalized.py | 15 +++ .../pythonFiles/terminalExec/sample6_raw.py | 12 ++ .../terminalExec/sample7_normalized.py | 8 ++ .../pythonFiles/terminalExec/sample7_raw.py | 9 ++ .../terminalExec/sample_normalized.py | 5 + .../pythonFiles/terminalExec/sample_raw.py | 8 ++ .../terminals/codeExecution/helper.test.ts | 40 ++++-- .../codeExecution/terminalCodeExec.test.ts | 2 +- 14 files changed, 226 insertions(+), 18 deletions(-) create mode 100644 news/2 Fixes/259.md create mode 100644 pythonFiles/normalizeForInterpreter.py create mode 100644 src/test/pythonFiles/terminalExec/sample6_normalized.py create mode 100644 src/test/pythonFiles/terminalExec/sample6_raw.py create mode 100644 src/test/pythonFiles/terminalExec/sample7_normalized.py create mode 100644 src/test/pythonFiles/terminalExec/sample7_raw.py create mode 100644 src/test/pythonFiles/terminalExec/sample_normalized.py create mode 100644 src/test/pythonFiles/terminalExec/sample_raw.py diff --git a/news/2 Fixes/259.md b/news/2 Fixes/259.md new file mode 100644 index 000000000000..6460214099c3 --- /dev/null +++ b/news/2 Fixes/259.md @@ -0,0 +1 @@ +Add blank lines to separate blocks of indented code (function defs, classes, and the like) so as to ensure the code can be run within a Python interactive prompt. diff --git a/pythonFiles/normalizeForInterpreter.py b/pythonFiles/normalizeForInterpreter.py new file mode 100644 index 000000000000..1bb1823b9f04 --- /dev/null +++ b/pythonFiles/normalizeForInterpreter.py @@ -0,0 +1,116 @@ +import ast +import io +import operator +import os +import sys +import token +import tokenize + + +class Visitor(ast.NodeVisitor): + def __init__(self, lines): + self._lines = lines + self.line_numbers_with_nodes = set() + self.line_numbers_with_statements = [] + + def generic_visit(self, node): + if hasattr(node, 'col_offset') and hasattr(node, 'lineno') and node.col_offset == 0: + self.line_numbers_with_nodes.add(node.lineno) + if isinstance(node, ast.stmt): + self.line_numbers_with_statements.append(node.lineno) + + ast.NodeVisitor.generic_visit(self, node) + + +def _tokenize(source): + """Tokenize Python source code.""" + # Using an undocumented API as the documented one in Python 2.7 does not work as needed + # cross-version. + return tokenize.generate_tokens(io.StringIO(source).readline) + + +def _indent_size(line): + for index, char in enumerate(line): + if not char.isspace(): + return index + + +def _get_global_statement_blocks(source, lines): + """Return a list of all global statement blocks. + + The list comprises of 3-item tuples that contain the starting line number, + ending line number and whether the statement is a single line. + + """ + tree = ast.parse(source) + visitor = Visitor(lines) + visitor.visit(tree) + + statement_ranges = [] + for index, line_number in enumerate(visitor.line_numbers_with_statements): + remaining_line_numbers = visitor.line_numbers_with_statements[index+1:] + end_line_number = len(lines) if len(remaining_line_numbers) == 0 else min(remaining_line_numbers) - 1 + current_statement_is_oneline = line_number == end_line_number + + if len(statement_ranges) == 0: + statement_ranges.append((line_number, end_line_number, current_statement_is_oneline)) + continue + + previous_statement = statement_ranges[-1] + previous_statement_is_oneline = previous_statement[2] + if previous_statement_is_oneline and current_statement_is_oneline: + statement_ranges[-1] = previous_statement[0], end_line_number, True + else: + statement_ranges.append((line_number, end_line_number, current_statement_is_oneline)) + + return statement_ranges + + +def normalize_lines(source): + """Normalize blank lines for sending to the terminal. + + Blank lines within a statement block are removed to prevent the REPL + from thinking the block is finished. Newlines are added to separate + top-level statements so that the REPL does not think there is a syntax + error. + + """ + lines = source.splitlines(False) + # Find out if we have any trailing blank lines + has_blank_lines = len(lines[-1].strip()) == 0 or source.endswith(os.linesep) + + # Step 1: Remove empty lines. + tokens = _tokenize(source) + newlines_indexes_to_remove = (spos[0] for (toknum, tokval, spos, epos, line) in tokens + if len(line.strip()) == 0 and token.tok_name[toknum] == 'NL' and spos[0] == epos[0]) + + for line_number in reversed(list(newlines_indexes_to_remove)): + del lines[line_number-1] + + # Step 2: Add blank lines between each global statement block. + # A consequtive single lines blocks of code will be treated as a single statement, + # just to ensure we do not unnecessarily add too many blank lines. + source = os.linesep.join(lines) + tokens = _tokenize(source) + dedent_indexes = (spos[0] for (toknum, tokval, spos, epos, line) in tokens + if toknum == token.DEDENT and _indent_size(line) == 0) + + global_statement_ranges = _get_global_statement_blocks(source, lines) + + for line_number in filter(lambda x: x > 1, map(operator.itemgetter(0), reversed(global_statement_ranges))): + lines.insert(line_number-1, '') + + sys.stdout.write(os.linesep.join(lines) + (os.linesep if has_blank_lines else '')) + sys.stdout.flush() + + +if __name__ == '__main__': + contents = sys.argv[1] + try: + default_encoding = sys.getdefaultencoding() + contents = contents.encode(default_encoding, 'surrogateescape').decode(default_encoding, 'replace') + except (UnicodeError, LookupError): + pass + if isinstance(contents, bytes): + contents = contents.decode('utf8') + normalize_lines(contents) diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index 674d5cfba681..de31a8b94049 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -2,10 +2,14 @@ // Licensed under the MIT License. import { inject, injectable } from 'inversify'; +import * as path from 'path'; import { Range, TextEditor, Uri } from 'vscode'; import { IApplicationShell, IDocumentManager } from '../../common/application/types'; -import { PYTHON_LANGUAGE } from '../../common/constants'; +import { EXTENSION_ROOT_DIR, PYTHON_LANGUAGE } from '../../common/constants'; import '../../common/extensions'; +import { IProcessService } from '../../common/process/types'; +import { IConfigurationService } from '../../common/types'; +import { IEnvironmentVariablesProvider } from '../../common/variables/types'; import { IServiceContainer } from '../../ioc/types'; import { ICodeExecutionHelper } from '../types'; @@ -13,19 +17,26 @@ import { ICodeExecutionHelper } from '../types'; export class CodeExecutionHelper implements ICodeExecutionHelper { private readonly documentManager: IDocumentManager; private readonly applicationShell: IApplicationShell; + private readonly envVariablesProvider: IEnvironmentVariablesProvider; + private readonly processService: IProcessService; + private readonly configurationService: IConfigurationService; constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { this.documentManager = serviceContainer.get(IDocumentManager); this.applicationShell = serviceContainer.get(IApplicationShell); + this.envVariablesProvider = serviceContainer.get(IEnvironmentVariablesProvider); + this.processService = serviceContainer.get(IProcessService); + this.configurationService = serviceContainer.get(IConfigurationService); } public async normalizeLines(code: string, resource?: Uri): Promise { try { if (code.trim().length === 0) { return ''; } - const regex = /(\n)([ \t]*\r?\n)([ \t]+\S+)/gm; - return code.replace(regex, (_, a, b, c) => { - return `${a}${c}`; - }); + const env = await this.envVariablesProvider.getEnvironmentVariables(resource); + const pythonPath = this.configurationService.getSettings(resource).pythonPath; + const args = [path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'normalizeForInterpreter.py'), code]; + const proc = await this.processService.exec(pythonPath, args, { env, throwOnStdErr: true }); + return proc.stdout; } catch (ex) { console.error(ex, 'Python: Failed to normalize code for execution in terminal'); return code; diff --git a/src/test/pythonFiles/terminalExec/sample1_normalized.py b/src/test/pythonFiles/terminalExec/sample1_normalized.py index 0896de65d22f..8591baeb6489 100644 --- a/src/test/pythonFiles/terminalExec/sample1_normalized.py +++ b/src/test/pythonFiles/terminalExec/sample1_normalized.py @@ -1,18 +1,21 @@ # Sample block 1 + def square(x): return x**2 print('hello') # Sample block 2 + a = 2 + if a < 2: print('less than 2') else: print('more than 2') print('hello') - # Sample block 3 + for i in range(5): print(i) print(i) diff --git a/src/test/pythonFiles/terminalExec/sample3_normalized.py b/src/test/pythonFiles/terminalExec/sample3_normalized.py index e4f028b0b778..4fa62091c66d 100644 --- a/src/test/pythonFiles/terminalExec/sample3_normalized.py +++ b/src/test/pythonFiles/terminalExec/sample3_normalized.py @@ -1,4 +1,5 @@ if True: print(1) print(2) + print(3) diff --git a/src/test/pythonFiles/terminalExec/sample3_raw.py b/src/test/pythonFiles/terminalExec/sample3_raw.py index 5865e6d2cbde..fee6c839aa89 100644 --- a/src/test/pythonFiles/terminalExec/sample3_raw.py +++ b/src/test/pythonFiles/terminalExec/sample3_raw.py @@ -2,4 +2,5 @@ print(1) print(2) + print(3) diff --git a/src/test/pythonFiles/terminalExec/sample6_normalized.py b/src/test/pythonFiles/terminalExec/sample6_normalized.py new file mode 100644 index 000000000000..242bf35abea8 --- /dev/null +++ b/src/test/pythonFiles/terminalExec/sample6_normalized.py @@ -0,0 +1,15 @@ +if True: + print(1) +else: print(2) + +print('🔨') +print(3) +print(3) + +if True: + print(1) +else: print(2) + +if True: + print(1) +else: print(2) diff --git a/src/test/pythonFiles/terminalExec/sample6_raw.py b/src/test/pythonFiles/terminalExec/sample6_raw.py new file mode 100644 index 000000000000..b064ca962070 --- /dev/null +++ b/src/test/pythonFiles/terminalExec/sample6_raw.py @@ -0,0 +1,12 @@ +if True: + print(1) +else: print(2) +print('🔨') +print(3) +print(3) +if True: + print(1) +else: print(2) +if True: + print(1) +else: print(2) diff --git a/src/test/pythonFiles/terminalExec/sample7_normalized.py b/src/test/pythonFiles/terminalExec/sample7_normalized.py new file mode 100644 index 000000000000..2288800fc985 --- /dev/null +++ b/src/test/pythonFiles/terminalExec/sample7_normalized.py @@ -0,0 +1,8 @@ +if True: + print(1) + print(1) +else: + print(2) + print(2) + +print(3) diff --git a/src/test/pythonFiles/terminalExec/sample7_raw.py b/src/test/pythonFiles/terminalExec/sample7_raw.py new file mode 100644 index 000000000000..62d01b9659c6 --- /dev/null +++ b/src/test/pythonFiles/terminalExec/sample7_raw.py @@ -0,0 +1,9 @@ +if True: + print(1) + + print(1) +else: + print(2) + + print(2) +print(3) diff --git a/src/test/pythonFiles/terminalExec/sample_normalized.py b/src/test/pythonFiles/terminalExec/sample_normalized.py new file mode 100644 index 000000000000..8ee9b90cdd27 --- /dev/null +++ b/src/test/pythonFiles/terminalExec/sample_normalized.py @@ -0,0 +1,5 @@ +import sys +print(sys.executable) +print("1234") +print(1) +print(2) diff --git a/src/test/pythonFiles/terminalExec/sample_raw.py b/src/test/pythonFiles/terminalExec/sample_raw.py new file mode 100644 index 000000000000..d1b32aaf606c --- /dev/null +++ b/src/test/pythonFiles/terminalExec/sample_raw.py @@ -0,0 +1,8 @@ +import sys + +print(sys.executable) + +print("1234") + +print(1) +print(2) diff --git a/src/test/terminals/codeExecution/helper.test.ts b/src/test/terminals/codeExecution/helper.test.ts index 1d0347c8d48b..6c6ddeb9ca8f 100644 --- a/src/test/terminals/codeExecution/helper.test.ts +++ b/src/test/terminals/codeExecution/helper.test.ts @@ -11,9 +11,15 @@ import * as TypeMoq from 'typemoq'; import { Range, Selection, TextDocument, TextEditor, TextLine, Uri } from 'vscode'; import { IApplicationShell, IDocumentManager } from '../../../client/common/application/types'; import { EXTENSION_ROOT_DIR, PYTHON_LANGUAGE } from '../../../client/common/constants'; +import { BufferDecoder } from '../../../client/common/process/decoder'; +import { ProcessService } from '../../../client/common/process/proc'; +import { IProcessService } from '../../../client/common/process/types'; +import { IConfigurationService, IPythonSettings } from '../../../client/common/types'; +import { IEnvironmentVariablesProvider } from '../../../client/common/variables/types'; import { IServiceContainer } from '../../../client/ioc/types'; import { CodeExecutionHelper } from '../../../client/terminals/codeExecution/helper'; import { ICodeExecutionHelper } from '../../../client/terminals/types'; +import { PYTHON_PATH } from '../../common'; const TEST_FILES_PATH = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'terminalExec'); @@ -24,12 +30,24 @@ suite('Terminal - Code Execution Helper', () => { let helper: ICodeExecutionHelper; let document: TypeMoq.IMock; let editor: TypeMoq.IMock; + let processService: TypeMoq.IMock; + let configService: TypeMoq.IMock; setup(() => { const serviceContainer = TypeMoq.Mock.ofType(); documentManager = TypeMoq.Mock.ofType(); applicationShell = TypeMoq.Mock.ofType(); + const envVariablesProvider = TypeMoq.Mock.ofType(); + processService = TypeMoq.Mock.ofType(); + configService = TypeMoq.Mock.ofType(); + const pythonSettings = TypeMoq.Mock.ofType(); + pythonSettings.setup(p => p.pythonPath).returns(() => PYTHON_PATH); + configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); + envVariablesProvider.setup(e => e.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IDocumentManager), TypeMoq.It.isAny())).returns(() => documentManager.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IApplicationShell), TypeMoq.It.isAny())).returns(() => applicationShell.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IEnvironmentVariablesProvider), TypeMoq.It.isAny())).returns(() => envVariablesProvider.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IProcessService), TypeMoq.It.isAny())).returns(() => processService.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationService), TypeMoq.It.isAny())).returns(() => configService.object); helper = new CodeExecutionHelper(serviceContainer.object); document = TypeMoq.Mock.ofType(); @@ -38,18 +56,23 @@ suite('Terminal - Code Execution Helper', () => { }); async function ensureBlankLinesAreRemoved(source: string, expectedSource: string) { + const actualProcessService = new ProcessService(new BufferDecoder()); + processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns((file, args, options) => { + return actualProcessService.exec.apply(actualProcessService, [file, args, options]); + }); const normalizedZCode = await helper.normalizeLines(source); expect(normalizedZCode).to.be.equal(expectedSource); } test('Ensure blank lines are NOT removed when code is not indented (simple)', async () => { - const code = ['import sys', '', 'print(sys.executable)', '', 'print("1234")', '', 'print(1)', 'print(2)']; - const expectedCode = code.join(EOL); + const code = ['import sys', '', '', '', 'print(sys.executable)', '', 'print("1234")', '', '', 'print(1)', 'print(2)']; + const expectedCode = code.filter(line => line.trim().length > 0).join(EOL); await ensureBlankLinesAreRemoved(code.join(EOL), expectedCode); }); - ['sample1', 'sample2', 'sample3', 'sample4', 'sample5'].forEach(fileName => { - test(`Ensure blank lines are removed (${fileName})`, async () => { - const code = await fs.readFile(path.join(TEST_FILES_PATH, `${fileName}_raw.py`), 'utf8'); - const expectedCode = await fs.readFile(path.join(TEST_FILES_PATH, `${fileName}_normalized.py`), 'utf8'); + ['', '1', '2', '3', '4', '5', '6', '7'].forEach(fileNameSuffix => { + test(`Ensure blank lines are removed (Sample${fileNameSuffix})`, async () => { + const code = await fs.readFile(path.join(TEST_FILES_PATH, `sample${fileNameSuffix}_raw.py`), 'utf8'); + const expectedCode = await fs.readFile(path.join(TEST_FILES_PATH, `sample${fileNameSuffix}_normalized.py`), 'utf8'); await ensureBlankLinesAreRemoved(code, expectedCode); }); // test(`Ensure blank lines are removed, including leading empty lines (${fileName})`, async () => { @@ -58,11 +81,6 @@ suite('Terminal - Code Execution Helper', () => { // await ensureBlankLinesAreRemoved(['', '', ''].join(EOL) + EOL + code, expectedCode); // }); }); - test('Ensure blank lines are removed (sample2)', async () => { - const code = await fs.readFile(path.join(TEST_FILES_PATH, 'sample2_raw.py'), 'utf8'); - const expectedCode = await fs.readFile(path.join(TEST_FILES_PATH, 'sample2_normalized.py'), 'utf8'); - await ensureBlankLinesAreRemoved(code, expectedCode); - }); test('Display message if there\s no active file', async () => { documentManager.setup(doc => doc.activeTextEditor).returns(() => undefined); diff --git a/src/test/terminals/codeExecution/terminalCodeExec.test.ts b/src/test/terminals/codeExecution/terminalCodeExec.test.ts index 2ada071c2f3c..8fec1ace6b8f 100644 --- a/src/test/terminals/codeExecution/terminalCodeExec.test.ts +++ b/src/test/terminals/codeExecution/terminalCodeExec.test.ts @@ -18,7 +18,7 @@ import { ICodeExecutionService } from '../../../client/terminals/types'; import { PYTHON_PATH } from '../../common'; // tslint:disable-next-line:max-func-body-length -suite('Terminal Code Execution', () => { +suite('Terminal - Code Execution', () => { // tslint:disable-next-line:max-func-body-length ['Terminal Execution', 'Repl Execution', 'Django Execution'].forEach(testSuiteName => { let terminalSettings: TypeMoq.IMock; From 10980addb2a12c619abdc4ac2396b4a704000238 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 30 Apr 2018 15:55:16 -0700 Subject: [PATCH 206/433] Remove redundant settings from default launch.json config settings Fixes #1553 --- package.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/package.json b/package.json index 9ec66d4aec47..50f5f36b4d24 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ { "command": "python.execSelectionInTerminal", "key": "ctrl+enter", - "when": "editorFocus && editorHasSelection && editorLangId == python" + "when": "editorFocus && editorHasSelection && editorLangId == python" } ], "commands": [ @@ -1078,8 +1078,6 @@ "name": "Python Experimental: Attach", "type": "pythonExperimental", "request": "attach", - "localRoot": "${workspaceFolder}", - "remoteRoot": "${workspaceFolder}", "port": 3000, "host": "localhost" }, From 5c5a4e8f753eabbc8357d7fc706fab4e12558314 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 30 Apr 2018 17:13:12 -0700 Subject: [PATCH 207/433] Clarify some settings --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 50f5f36b4d24..5a9f884db36f 100644 --- a/package.json +++ b/package.json @@ -1153,7 +1153,7 @@ "python.disableInstallationCheck": { "type": "boolean", "default": false, - "description": "Whether to check if Python is installed.", + "description": "Whether to check if Python is installed (also warn when using the macOS-installed Python).", "scope": "resource" }, "python.disablePromptForFeatures": { @@ -1226,7 +1226,7 @@ "python.globalModuleInstallation": { "type": "boolean", "default": false, - "description": "Whether to install Python modules globally.", + "description": "Whether to install Python modules globally when not using an environment.", "scope": "resource" }, "python.jediMemoryLimit": { From e77626dcb7687e91f40c40ca34042fd49ab5b379 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 1 May 2018 09:56:40 -0700 Subject: [PATCH 208/433] Update to the RC (#1557) --- CHANGELOG.md | 115 +++++++++++++++++++++++++----------- news/1 Enhancements/1206.md | 6 +- news/announce.py | 17 ++++-- package.json | 2 +- 4 files changed, 96 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f49f3e669a29..6eb336ef03f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2018.4.0-beta (23 Mar 2018) +## 2018.4.0-rc (30 Apr 2018) Thanks to the following projects which we fully rely on to provide some of our features: @@ -16,18 +16,38 @@ his help on [our issue tracker](https://github.com/Microsoft/vscode-python)! ### Enhancements +1. Enable debugging of Jinja templates in the experimental debugger. + This is made possible with the addition of the `jinja` setting in the `launch.json` file as follows: + ```json + "request": "launch or attach", + ... + "jinja": true + ``` + ([#1206](https://github.com/Microsoft/vscode-python/issues/1206)) +1. Remove empty spaces from the selected text of the active editor when executing in a terminal. + ([#1207](https://github.com/Microsoft/vscode-python/issues/1207)) 1. Add prelimnary support for remote debugging using the experimental debugger. -Attach to a Python program started using the command `python -m ptvsd --server --port 9091 --file pythonFile.py` ([#1229](https://github.com/Microsoft/vscode-python/issues/1229)) + Attach to a Python program started using the command `python -m ptvsd --server --port 9091 --file pythonFile.py` + ([#1229](https://github.com/Microsoft/vscode-python/issues/1229)) 1. Add support for [logpoints](https://code.visualstudio.com/docs/editor/debugging#_logpoints) in the experimental debugger. - ([#1306](https://github.com/Microsoft/vscode-python/issues/1306)) + ([#1306](https://github.com/Microsoft/vscode-python/issues/1306)) 1. Set focus to the terminal upon creation of a terminal using the `Python: Create Terminal` command. - ([#1315](https://github.com/Microsoft/vscode-python/issues/1315)) + ([#1315](https://github.com/Microsoft/vscode-python/issues/1315)) +1. Save the python file before running it in the terminal using the command/menu `Run Python File in Terminal`. + ([#1316](https://github.com/Microsoft/vscode-python/issues/1316)) 1. Added support for source references (remote debugging without having the source code locally) in the experimental debugger. - ([#1333](https://github.com/Microsoft/vscode-python/issues/1333)) + ([#1333](https://github.com/Microsoft/vscode-python/issues/1333)) +1. Add `Ctrl+Enter` keyboard shortcut for `Run Selection/Line in Python Terminal`. + ([#1349](https://github.com/Microsoft/vscode-python/issues/1349)) 1. Settings configured within the `debugOptions` property of `launch.json` for the old debugger are now defined as individual (boolean) properties in the new experimental debugger (e.g. `"debugOptions": ["RedirectOutput"]` becomes `"redirectOutput": true`). - ([#1395](https://github.com/Microsoft/vscode-python/issues/1395)) -1. Intergrate Jedi 0.12. See https://github.com/davidhalter/jedi/issues/1063#issuecomment-381417297 for details. ([#1400](https://github.com/Microsoft/vscode-python/issues/1400)) -1. Add prelimnary support for remote debugging using the experimental debugger. ([#907](https://github.com/Microsoft/vscode-python/issues/907)) + ([#1395](https://github.com/Microsoft/vscode-python/issues/1395)) +1. Intergrate Jedi 0.12. See https://github.com/davidhalter/jedi/issues/1063#issuecomment-381417297 for details. + ([#1400](https://github.com/Microsoft/vscode-python/issues/1400)) +1. Enable Jinja template debugging as a default behaivour when using the Watson debug configuration for debugging of Watson applications. + ([#1480](https://github.com/Microsoft/vscode-python/issues/1480)) +1. Enable Jinja template debugging as a default behavior when debugging Pyramid applications. + ([#1492](https://github.com/Microsoft/vscode-python/issues/1492)) +1. Add prelimnary support for remote debugging using the experimental debugger. Attach to a Python program after having imported `ptvsd` and enabling the debugger to attach as follows: ```python import ptvsd @@ -37,56 +57,83 @@ Attach to a Python program started using the command `python -m ptvsd --server - * `ptvsd.break_into_debugger()` to break into the attached debugger. * `ptvsd.wait_for_attach(timeout)` to cause the program to wait untill a debugger attaches. * `ptvsd.is_attached()` to determine whether a debugger is attached to the program. + ([#907](https://github.com/Microsoft/vscode-python/issues/907)) ### Fixes -1. Use an existing method to identify the active interpreter. ([#1015](https://github.com/Microsoft/vscode-python/issues/1015)) -1. Fix go to definition functionality across files. ([#1033](https://github.com/Microsoft/vscode-python/issues/1033)) +1. Use an existing method to identify the active interpreter. + ([#1015](https://github.com/Microsoft/vscode-python/issues/1015)) +1. Fix `go to definition` functionality across files. + ([#1033](https://github.com/Microsoft/vscode-python/issues/1033)) 1. IntelliSense under Python 2 for inherited attributes works again (thanks to an upgraded Jedi). - ([#1072](https://github.com/Microsoft/vscode-python/issues/1072)) + ([#1072](https://github.com/Microsoft/vscode-python/issues/1072)) 1. Reverted change that ended up considering symlinked interpreters as duplicate interpreter. - ([#1192](https://github.com/Microsoft/vscode-python/issues/1192)) + ([#1192](https://github.com/Microsoft/vscode-python/issues/1192)) 1. Display errors returned by the PipEnv command when identifying the corresonding environment. - ([#1254](https://github.com/Microsoft/vscode-python/issues/1254)) + ([#1254](https://github.com/Microsoft/vscode-python/issues/1254)) 1. When `editor.formatOnType` is on, don't add a space for `*args` or `**kwargs` - ([#1257](https://github.com/Microsoft/vscode-python/issues/1257)) + ([#1257](https://github.com/Microsoft/vscode-python/issues/1257)) 1. When `editor.formatOnType` is on, don't add a space between a string type specifier and the string literal - ([#1257](https://github.com/Microsoft/vscode-python/issues/1257)) + ([#1257](https://github.com/Microsoft/vscode-python/issues/1257)) +1. Reduce the frequency within which the memory usage of the language server is checked, also ensure memory usage is not checked unless language server functionality is used. + ([#1277](https://github.com/Microsoft/vscode-python/issues/1277)) 1. Ensure interpreter file exists on the file system before including into list of interpreters. - ([#1305](https://github.com/Microsoft/vscode-python/issues/1305)) + ([#1305](https://github.com/Microsoft/vscode-python/issues/1305)) 1. Do not have the formatter consider single-quoted string multiline even if it is not terminated. - ([#1364](https://github.com/Microsoft/vscode-python/issues/1364)) + ([#1364](https://github.com/Microsoft/vscode-python/issues/1364)) 1. IntelliSense works in module-level `if` statements (thanks to Jedi 0.12.0 upgrade). - ([#142](https://github.com/Microsoft/vscode-python/issues/142)) + ([#142](https://github.com/Microsoft/vscode-python/issues/142)) +1. Clicking the codelens `Run Test` on a test class should run that specific test class instead of all tests in the file. + ([#1472](https://github.com/Microsoft/vscode-python/issues/1472)) +1. Clicking the codelens `Run Test` on a test class or method should run that specific test instead of all tests in the file. + ([#1473](https://github.com/Microsoft/vscode-python/issues/1473)) +1. Check whether the selected python interpreter is valid before starting the language server. Failing to do so could result in the extension failing to load. + ([#1487](https://github.com/Microsoft/vscode-python/issues/1487)) +1. Fixes the issue where Conda environments created using the latest version of Anaconda are not activated in Powershell. + ([#1520](https://github.com/Microsoft/vscode-python/issues/1520)) +1. Increase the delay for the activation of environments in Powershell terminals. + ([#1533](https://github.com/Microsoft/vscode-python/issues/1533)) +1. Fix activation of environments with spaces in the python path when using Powershell. + ([#1534](https://github.com/Microsoft/vscode-python/issues/1534)) +1. Ensure Flask application is launched with multi-threading disabled, when run in the CI tests. + ([#1535](https://github.com/Microsoft/vscode-python/issues/1535)) 1. IntelliSense works appropriately when a project contains multiple files with the same name (thanks to Jedi 0.12.0 update). - ([#178](https://github.com/Microsoft/vscode-python/issues/178)) + ([#178](https://github.com/Microsoft/vscode-python/issues/178)) +1. Add blank lines to separate blocks of indented code (function defs, classes, and the like) so as to ensure the code can be run within a Python interactive prompt. + ([#259](https://github.com/Microsoft/vscode-python/issues/259)) 1. Provide type details appropriate for the iterable in a `for` loop when the line has a `# type` comment. - ([#338](https://github.com/Microsoft/vscode-python/issues/338)) + ([#338](https://github.com/Microsoft/vscode-python/issues/338)) 1. Parameter hints following an f-string work again. - ([#344](https://github.com/Microsoft/vscode-python/issues/344)) + ([#344](https://github.com/Microsoft/vscode-python/issues/344)) 1. When `editor.formatOnType` is on, don't indent after a single-line statement block - ([#726](https://github.com/Microsoft/vscode-python/issues/726)) + ([#726](https://github.com/Microsoft/vscode-python/issues/726)) +1. Fix debugging of Pyramid applications on Windows. + ([#737](https://github.com/Microsoft/vscode-python/issues/737)) ### Code Health -1. Improved developer experience of the Python Extension on Windows. ([#1216](https://github.com/Microsoft/vscode-python/issues/1216)) +1. Improved developer experience of the Python Extension on Windows. + ([#1216](https://github.com/Microsoft/vscode-python/issues/1216)) 1. Parallelize jobs (unit tests) on CI server. - ([#1247](https://github.com/Microsoft/vscode-python/issues/1247)) -1. Run CI tests against the release version and master branch of PTVSD (experimental debugger), allowing tests to fail against the mastre branch of PTVSD. - ([#1253](https://github.com/Microsoft/vscode-python/issues/1253)) + ([#1247](https://github.com/Microsoft/vscode-python/issues/1247)) +1. Run CI tests against the release version and master branch of PTVSD (experimental debugger), allowing tests to fail against the master branch of PTVSD. + ([#1253](https://github.com/Microsoft/vscode-python/issues/1253)) 1. Only trigger the extension for `file` and `untitled` in preparation for -[Visual Studio Live Share](https://aka.ms/vsls) -(thanks to [Jonathan Carter](https://github.com/lostintangent)) - ([#1298](https://github.com/Microsoft/vscode-python/issues/1298)) + [Visual Studio Live Share](https://aka.ms/vsls) + (thanks to [Jonathan Carter](https://github.com/lostintangent)) + ([#1298](https://github.com/Microsoft/vscode-python/issues/1298)) 1. Ensure all unit tests run on Travis use the right Python interpreter. - ([#1318](https://github.com/Microsoft/vscode-python/issues/1318)) + ([#1318](https://github.com/Microsoft/vscode-python/issues/1318)) 1. Pin all production dependencies. - ([#1374](https://github.com/Microsoft/vscode-python/issues/1374)) + ([#1374](https://github.com/Microsoft/vscode-python/issues/1374)) 1. Add support for [hit count breakpoints](https://code.visualstudio.com/docs/editor/debugging#_advanced-breakpoint-topics) in the experimental debugger. - ([#1409](https://github.com/Microsoft/vscode-python/issues/1409)) + ([#1409](https://github.com/Microsoft/vscode-python/issues/1409)) 1. Ensure custom environment variables defined in `.env` file are passed onto the `pipenv` command. - ([#1428](https://github.com/Microsoft/vscode-python/issues/1428)) - + ([#1428](https://github.com/Microsoft/vscode-python/issues/1428)) +1. Remove unwanted python packages no longer used in unit tests. + ([#1494](https://github.com/Microsoft/vscode-python/issues/1494)) +1. Register language server functionality in the extension against specific resource types supporting the python language. + ([#1530](https://github.com/Microsoft/vscode-python/issues/1530)) ## 2018.3.1 (29 Mar 2018) diff --git a/news/1 Enhancements/1206.md b/news/1 Enhancements/1206.md index d581de672d41..53ce31079bf9 100644 --- a/news/1 Enhancements/1206.md +++ b/news/1 Enhancements/1206.md @@ -1,7 +1,7 @@ Enable debugging of Jinja templates in the experimental debugger. This is made possible with the addition of the `jinja` setting in the `launch.json` file as follows: ```json - "request": "launch or attach", - ... - "jinja": true +"request": "launch or attach", +... +"jinja": true ``` diff --git a/news/announce.py b/news/announce.py index ce929b9900b2..af715d4c7e4e 100644 --- a/news/announce.py +++ b/news/announce.py @@ -12,9 +12,6 @@ FILENAME_RE = re.compile(r"(?P\d+)(?P-\S+)?\.md") -ISSUE_URL = "https://github.com/Microsoft/vscode-python/issues/{issue}" -ENTRY_TEMPLATE = "1. {entry} ([#{issue}]({issue_url}))" -SECTION_DEPTH = "###" def NewsEntry(issue_number, description, path): @@ -68,8 +65,16 @@ def gather(directory): def entry_markdown(entry): """Generate the Markdown for the specified entry.""" - issue_url = ISSUE_URL.format(issue=entry.issue_number) - return ENTRY_TEMPLATE.format(entry=entry.description, + enumerated_item = "1. " + indent = ' ' * len(enumerated_item) + issue_url = f'https://github.com/Microsoft/vscode-python/issues/{entry.issue_number}' + issue_md = f'([#{entry.issue_number}]({issue_url}))' + entry_lines = entry.description.strip().splitlines() + formatted_lines = [f'{enumerated_item}{entry_lines[0]}'] + formatted_lines.extend(f'{indent}{line}' for line in entry_lines[1:]) + formatted_lines.append(f'{indent}{issue_md}') + return '\n'.join(formatted_lines) + return ENTRY_TEMPLATE.format(entry=entry.description.strip(), issue=entry.issue_number, issue_url=issue_url) @@ -78,7 +83,7 @@ def changelog_markdown(data): """Generate the Markdown for the release.""" changelog = [] for section, entries in data: - changelog.append(f"{SECTION_DEPTH} {section.title}") + changelog.append(f"### {section.title}") changelog.append("") changelog.extend(map(entry_markdown, entries)) changelog.append("") diff --git a/package.json b/package.json index 5a9f884db36f..a0dd00b5f6a7 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2018.4.0-beta", + "version": "2018.4.0-rc", "publisher": "ms-python", "author": { "name": "Microsoft Corporation" From 21be85cfaa37d584c4cefb228cfba02af5fdd64e Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 1 May 2018 18:07:43 -0700 Subject: [PATCH 209/433] Fixed unit tests on Windows (Appveyor) (#1560) * Fixed unit tests on Windows * Fixes #1559 * :bug: ensure we always use expected line endings * Add a trailing blank line * More fixes --- pythonFiles/normalizeForInterpreter.py | 20 ++++++++++++------- .../terminals/codeExecution/helper.test.ts | 3 +++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/pythonFiles/normalizeForInterpreter.py b/pythonFiles/normalizeForInterpreter.py index 1bb1823b9f04..678ce8caec4e 100644 --- a/pythonFiles/normalizeForInterpreter.py +++ b/pythonFiles/normalizeForInterpreter.py @@ -77,12 +77,17 @@ def normalize_lines(source): """ lines = source.splitlines(False) # Find out if we have any trailing blank lines - has_blank_lines = len(lines[-1].strip()) == 0 or source.endswith(os.linesep) + if len(lines[-1].strip()) == 0 or source.endswith('\n'): + trailing_newline = '\n' + else: + trailing_newline = '' # Step 1: Remove empty lines. tokens = _tokenize(source) newlines_indexes_to_remove = (spos[0] for (toknum, tokval, spos, epos, line) in tokens - if len(line.strip()) == 0 and token.tok_name[toknum] == 'NL' and spos[0] == epos[0]) + if len(line.strip()) == 0 + and token.tok_name[toknum] == 'NL' + and spos[0] == epos[0]) for line_number in reversed(list(newlines_indexes_to_remove)): del lines[line_number-1] @@ -90,17 +95,17 @@ def normalize_lines(source): # Step 2: Add blank lines between each global statement block. # A consequtive single lines blocks of code will be treated as a single statement, # just to ensure we do not unnecessarily add too many blank lines. - source = os.linesep.join(lines) + source = '\n'.join(lines) tokens = _tokenize(source) dedent_indexes = (spos[0] for (toknum, tokval, spos, epos, line) in tokens if toknum == token.DEDENT and _indent_size(line) == 0) global_statement_ranges = _get_global_statement_blocks(source, lines) - - for line_number in filter(lambda x: x > 1, map(operator.itemgetter(0), reversed(global_statement_ranges))): + start_positions = map(operator.itemgetter(0), reversed(global_statement_ranges)) + for line_number in filter(lambda x: x > 1, start_positions): lines.insert(line_number-1, '') - sys.stdout.write(os.linesep.join(lines) + (os.linesep if has_blank_lines else '')) + sys.stdout.write('\n'.join(lines) + trailing_newline) sys.stdout.flush() @@ -108,7 +113,8 @@ def normalize_lines(source): contents = sys.argv[1] try: default_encoding = sys.getdefaultencoding() - contents = contents.encode(default_encoding, 'surrogateescape').decode(default_encoding, 'replace') + encoded_contents = contents.encode(default_encoding, 'surrogateescape') + contents = encoded_contents.decode(default_encoding, 'replace') except (UnicodeError, LookupError): pass if isinstance(contents, bytes): diff --git a/src/test/terminals/codeExecution/helper.test.ts b/src/test/terminals/codeExecution/helper.test.ts index 6c6ddeb9ca8f..56db9e5657cd 100644 --- a/src/test/terminals/codeExecution/helper.test.ts +++ b/src/test/terminals/codeExecution/helper.test.ts @@ -11,6 +11,7 @@ import * as TypeMoq from 'typemoq'; import { Range, Selection, TextDocument, TextEditor, TextLine, Uri } from 'vscode'; import { IApplicationShell, IDocumentManager } from '../../../client/common/application/types'; import { EXTENSION_ROOT_DIR, PYTHON_LANGUAGE } from '../../../client/common/constants'; +import '../../../client/common/extensions'; import { BufferDecoder } from '../../../client/common/process/decoder'; import { ProcessService } from '../../../client/common/process/proc'; import { IProcessService } from '../../../client/common/process/types'; @@ -62,6 +63,8 @@ suite('Terminal - Code Execution Helper', () => { return actualProcessService.exec.apply(actualProcessService, [file, args, options]); }); const normalizedZCode = await helper.normalizeLines(source); + // In case file has been saved with different line endings. + expectedSource = expectedSource.splitLines({ removeEmptyEntries: false, trim: false }).join(EOL); expect(normalizedZCode).to.be.equal(expectedSource); } test('Ensure blank lines are NOT removed when code is not indented (simple)', async () => { From 2fdd9b9d6897f22cdf32e1057a18cb2b3ad7ca97 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 2 May 2018 07:36:56 -0700 Subject: [PATCH 210/433] Temporarily disable flask (experimental debugger) test on AppVeyor (#1566) * temprarily disable flask test on appveyor * Fix linter --- src/test/debugger/web.framework.test.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/test/debugger/web.framework.test.ts b/src/test/debugger/web.framework.test.ts index 9b23a691f83c..f95dbedaca77 100644 --- a/src/test/debugger/web.framework.test.ts +++ b/src/test/debugger/web.framework.test.ts @@ -13,7 +13,7 @@ import { EXTENSION_ROOT_DIR } from '../../client/common/constants'; import { noop } from '../../client/common/core.utils'; import { DebugOptions, LaunchRequestArguments } from '../../client/debugger/Common/Contracts'; import { PYTHON_PATH, sleep } from '../common'; -import { IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; +import { IS_APPVEYOR, IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; import { DEBUGGER_TIMEOUT } from './common/constants'; import { continueDebugging, createDebugAdapter, ExpectedVariable, hitHttpBreakpoint, makeHttpRequest, validateVariablesInFrame } from './utils'; @@ -129,7 +129,10 @@ suite(`Django and Flask Debugging: ${debuggerType}`, () => { expect(htmlResult).to.contain('Hello this_is_another_value_from_server'); } - test('Test Flask Route and Template debugging', async () => { + test('Test Flask Route and Template debugging', async function () { + if (IS_APPVEYOR) { + return this.skip(); + } const workspaceDirectory = path.join(EXTENSION_ROOT_DIR, 'src', 'testMultiRootWkspc', 'workspace5', 'flaskApp'); const { options, port } = await buildFlaskLaunchArgs(workspaceDirectory); From d91c5161eb73fb0ba3eae52d4e263ca7ad2899d3 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Wed, 2 May 2018 10:14:33 -0700 Subject: [PATCH 211/433] Bump to final for 2018.4.0 (#1562) --- CHANGELOG.md | 2 +- news/1 Enhancements/1206.md | 7 ------- news/1 Enhancements/1207.md | 1 - news/1 Enhancements/1229.md | 2 -- news/1 Enhancements/1306.md | 1 - news/1 Enhancements/1315.md | 1 - news/1 Enhancements/1316.md | 1 - news/1 Enhancements/1333.md | 1 - news/1 Enhancements/1349.md | 1 - news/1 Enhancements/1395.md | 1 - news/1 Enhancements/1400.md | 1 - news/1 Enhancements/1480.md | 1 - news/1 Enhancements/1492.md | 1 - news/1 Enhancements/907.md | 10 ---------- news/2 Fixes/1015.md | 1 - news/2 Fixes/1033.md | 1 - news/2 Fixes/1072.md | 1 - news/2 Fixes/1192.md | 1 - news/2 Fixes/1254.md | 1 - news/2 Fixes/1257-1.md | 1 - news/2 Fixes/1257-2.md | 1 - news/2 Fixes/1277.md | 1 - news/2 Fixes/1305.md | 1 - news/2 Fixes/1364.md | 1 - news/2 Fixes/142.md | 1 - news/2 Fixes/1472.md | 1 - news/2 Fixes/1473.md | 1 - news/2 Fixes/1487.md | 1 - news/2 Fixes/1520.md | 1 - news/2 Fixes/1533.md | 1 - news/2 Fixes/1534.md | 1 - news/2 Fixes/1535.md | 1 - news/2 Fixes/178.md | 1 - news/2 Fixes/259.md | 1 - news/2 Fixes/338.md | 1 - news/2 Fixes/344.md | 1 - news/2 Fixes/726.md | 1 - news/2 Fixes/737.md | 1 - news/3 Code Health/1216.md | 1 - news/3 Code Health/1247.md | 1 - news/3 Code Health/1253.md | 1 - news/3 Code Health/1298.md | 3 --- news/3 Code Health/1318.md | 1 - news/3 Code Health/1374.md | 1 - news/3 Code Health/1409.md | 1 - news/3 Code Health/1428.md | 1 - news/3 Code Health/1494.md | 1 - news/3 Code Health/1530.md | 1 - package.json | 4 ++-- 49 files changed, 3 insertions(+), 68 deletions(-) delete mode 100644 news/1 Enhancements/1206.md delete mode 100644 news/1 Enhancements/1207.md delete mode 100644 news/1 Enhancements/1229.md delete mode 100644 news/1 Enhancements/1306.md delete mode 100644 news/1 Enhancements/1315.md delete mode 100644 news/1 Enhancements/1316.md delete mode 100644 news/1 Enhancements/1333.md delete mode 100644 news/1 Enhancements/1349.md delete mode 100644 news/1 Enhancements/1395.md delete mode 100644 news/1 Enhancements/1400.md delete mode 100644 news/1 Enhancements/1480.md delete mode 100644 news/1 Enhancements/1492.md delete mode 100644 news/1 Enhancements/907.md delete mode 100644 news/2 Fixes/1015.md delete mode 100644 news/2 Fixes/1033.md delete mode 100644 news/2 Fixes/1072.md delete mode 100644 news/2 Fixes/1192.md delete mode 100644 news/2 Fixes/1254.md delete mode 100644 news/2 Fixes/1257-1.md delete mode 100644 news/2 Fixes/1257-2.md delete mode 100644 news/2 Fixes/1277.md delete mode 100644 news/2 Fixes/1305.md delete mode 100644 news/2 Fixes/1364.md delete mode 100644 news/2 Fixes/142.md delete mode 100644 news/2 Fixes/1472.md delete mode 100644 news/2 Fixes/1473.md delete mode 100644 news/2 Fixes/1487.md delete mode 100644 news/2 Fixes/1520.md delete mode 100644 news/2 Fixes/1533.md delete mode 100644 news/2 Fixes/1534.md delete mode 100644 news/2 Fixes/1535.md delete mode 100644 news/2 Fixes/178.md delete mode 100644 news/2 Fixes/259.md delete mode 100644 news/2 Fixes/338.md delete mode 100644 news/2 Fixes/344.md delete mode 100644 news/2 Fixes/726.md delete mode 100644 news/2 Fixes/737.md delete mode 100644 news/3 Code Health/1216.md delete mode 100644 news/3 Code Health/1247.md delete mode 100644 news/3 Code Health/1253.md delete mode 100644 news/3 Code Health/1298.md delete mode 100644 news/3 Code Health/1318.md delete mode 100644 news/3 Code Health/1374.md delete mode 100644 news/3 Code Health/1409.md delete mode 100644 news/3 Code Health/1428.md delete mode 100644 news/3 Code Health/1494.md delete mode 100644 news/3 Code Health/1530.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eb336ef03f7..5ddc0129fd3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2018.4.0-rc (30 Apr 2018) +## 2018.4.0 (2 May 2018) Thanks to the following projects which we fully rely on to provide some of our features: diff --git a/news/1 Enhancements/1206.md b/news/1 Enhancements/1206.md deleted file mode 100644 index 53ce31079bf9..000000000000 --- a/news/1 Enhancements/1206.md +++ /dev/null @@ -1,7 +0,0 @@ -Enable debugging of Jinja templates in the experimental debugger. -This is made possible with the addition of the `jinja` setting in the `launch.json` file as follows: -```json -"request": "launch or attach", -... -"jinja": true -``` diff --git a/news/1 Enhancements/1207.md b/news/1 Enhancements/1207.md deleted file mode 100644 index f668cdf00aae..000000000000 --- a/news/1 Enhancements/1207.md +++ /dev/null @@ -1 +0,0 @@ -Remove empty spaces from the selected text of the active editor when executing in a terminal. diff --git a/news/1 Enhancements/1229.md b/news/1 Enhancements/1229.md deleted file mode 100644 index 1367edb3bbcc..000000000000 --- a/news/1 Enhancements/1229.md +++ /dev/null @@ -1,2 +0,0 @@ -Add prelimnary support for remote debugging using the experimental debugger. -Attach to a Python program started using the command `python -m ptvsd --server --port 9091 --file pythonFile.py` \ No newline at end of file diff --git a/news/1 Enhancements/1306.md b/news/1 Enhancements/1306.md deleted file mode 100644 index 8b97fceaf12a..000000000000 --- a/news/1 Enhancements/1306.md +++ /dev/null @@ -1 +0,0 @@ -Add support for [logpoints](https://code.visualstudio.com/docs/editor/debugging#_logpoints) in the experimental debugger. diff --git a/news/1 Enhancements/1315.md b/news/1 Enhancements/1315.md deleted file mode 100644 index ff67ed87e629..000000000000 --- a/news/1 Enhancements/1315.md +++ /dev/null @@ -1 +0,0 @@ -Set focus to the terminal upon creation of a terminal using the `Python: Create Terminal` command. diff --git a/news/1 Enhancements/1316.md b/news/1 Enhancements/1316.md deleted file mode 100644 index ca6c6a80eb77..000000000000 --- a/news/1 Enhancements/1316.md +++ /dev/null @@ -1 +0,0 @@ -Save the python file before running it in the terminal using the command/menu `Run Python File in Terminal`. diff --git a/news/1 Enhancements/1333.md b/news/1 Enhancements/1333.md deleted file mode 100644 index dbf4296ba5bb..000000000000 --- a/news/1 Enhancements/1333.md +++ /dev/null @@ -1 +0,0 @@ -Added support for source references (remote debugging without having the source code locally) in the experimental debugger. diff --git a/news/1 Enhancements/1349.md b/news/1 Enhancements/1349.md deleted file mode 100644 index 7bcbcd8f1a88..000000000000 --- a/news/1 Enhancements/1349.md +++ /dev/null @@ -1 +0,0 @@ -Add `Ctrl+Enter` keyboard shortcut for `Run Selection/Line in Python Terminal`. diff --git a/news/1 Enhancements/1395.md b/news/1 Enhancements/1395.md deleted file mode 100644 index b572085061eb..000000000000 --- a/news/1 Enhancements/1395.md +++ /dev/null @@ -1 +0,0 @@ -Settings configured within the `debugOptions` property of `launch.json` for the old debugger are now defined as individual (boolean) properties in the new experimental debugger (e.g. `"debugOptions": ["RedirectOutput"]` becomes `"redirectOutput": true`). diff --git a/news/1 Enhancements/1400.md b/news/1 Enhancements/1400.md deleted file mode 100644 index 47b3cf611c88..000000000000 --- a/news/1 Enhancements/1400.md +++ /dev/null @@ -1 +0,0 @@ -Intergrate Jedi 0.12. See https://github.com/davidhalter/jedi/issues/1063#issuecomment-381417297 for details. \ No newline at end of file diff --git a/news/1 Enhancements/1480.md b/news/1 Enhancements/1480.md deleted file mode 100644 index 57d280dc60ae..000000000000 --- a/news/1 Enhancements/1480.md +++ /dev/null @@ -1 +0,0 @@ -Enable Jinja template debugging as a default behaivour when using the Watson debug configuration for debugging of Watson applications. diff --git a/news/1 Enhancements/1492.md b/news/1 Enhancements/1492.md deleted file mode 100644 index fe8decf90818..000000000000 --- a/news/1 Enhancements/1492.md +++ /dev/null @@ -1 +0,0 @@ -Enable Jinja template debugging as a default behavior when debugging Pyramid applications. diff --git a/news/1 Enhancements/907.md b/news/1 Enhancements/907.md deleted file mode 100644 index 378afbeb1c0c..000000000000 --- a/news/1 Enhancements/907.md +++ /dev/null @@ -1,10 +0,0 @@ -Add prelimnary support for remote debugging using the experimental debugger. -Attach to a Python program after having imported `ptvsd` and enabling the debugger to attach as follows: -```python -import ptvsd -ptvsd.enable_attach(('0.0.0.0', 5678)) -``` -Additional capabilities: -* `ptvsd.break_into_debugger()` to break into the attached debugger. -* `ptvsd.wait_for_attach(timeout)` to cause the program to wait untill a debugger attaches. -* `ptvsd.is_attached()` to determine whether a debugger is attached to the program. diff --git a/news/2 Fixes/1015.md b/news/2 Fixes/1015.md deleted file mode 100644 index c88fd33a17eb..000000000000 --- a/news/2 Fixes/1015.md +++ /dev/null @@ -1 +0,0 @@ -Use an existing method to identify the active interpreter. \ No newline at end of file diff --git a/news/2 Fixes/1033.md b/news/2 Fixes/1033.md deleted file mode 100644 index 6c66d9582d82..000000000000 --- a/news/2 Fixes/1033.md +++ /dev/null @@ -1 +0,0 @@ -Fix `go to definition` functionality across files. diff --git a/news/2 Fixes/1072.md b/news/2 Fixes/1072.md deleted file mode 100644 index 75e3c52bdc0c..000000000000 --- a/news/2 Fixes/1072.md +++ /dev/null @@ -1 +0,0 @@ -IntelliSense under Python 2 for inherited attributes works again (thanks to an upgraded Jedi). diff --git a/news/2 Fixes/1192.md b/news/2 Fixes/1192.md deleted file mode 100644 index b97883d49d8a..000000000000 --- a/news/2 Fixes/1192.md +++ /dev/null @@ -1 +0,0 @@ -Reverted change that ended up considering symlinked interpreters as duplicate interpreter. diff --git a/news/2 Fixes/1254.md b/news/2 Fixes/1254.md deleted file mode 100644 index fce864eb6f9f..000000000000 --- a/news/2 Fixes/1254.md +++ /dev/null @@ -1 +0,0 @@ -Display errors returned by the PipEnv command when identifying the corresonding environment. diff --git a/news/2 Fixes/1257-1.md b/news/2 Fixes/1257-1.md deleted file mode 100644 index 0f53d565ae55..000000000000 --- a/news/2 Fixes/1257-1.md +++ /dev/null @@ -1 +0,0 @@ -When `editor.formatOnType` is on, don't add a space for `*args` or `**kwargs` diff --git a/news/2 Fixes/1257-2.md b/news/2 Fixes/1257-2.md deleted file mode 100644 index c14a958df006..000000000000 --- a/news/2 Fixes/1257-2.md +++ /dev/null @@ -1 +0,0 @@ -When `editor.formatOnType` is on, don't add a space between a string type specifier and the string literal diff --git a/news/2 Fixes/1277.md b/news/2 Fixes/1277.md deleted file mode 100644 index 3f12d5f3ec26..000000000000 --- a/news/2 Fixes/1277.md +++ /dev/null @@ -1 +0,0 @@ -Reduce the frequency within which the memory usage of the language server is checked, also ensure memory usage is not checked unless language server functionality is used. diff --git a/news/2 Fixes/1305.md b/news/2 Fixes/1305.md deleted file mode 100644 index 321d9d0d8300..000000000000 --- a/news/2 Fixes/1305.md +++ /dev/null @@ -1 +0,0 @@ -Ensure interpreter file exists on the file system before including into list of interpreters. diff --git a/news/2 Fixes/1364.md b/news/2 Fixes/1364.md deleted file mode 100644 index df93759e9dd4..000000000000 --- a/news/2 Fixes/1364.md +++ /dev/null @@ -1 +0,0 @@ -Do not have the formatter consider single-quoted string multiline even if it is not terminated. diff --git a/news/2 Fixes/142.md b/news/2 Fixes/142.md deleted file mode 100644 index ab8918fcdfff..000000000000 --- a/news/2 Fixes/142.md +++ /dev/null @@ -1 +0,0 @@ -IntelliSense works in module-level `if` statements (thanks to Jedi 0.12.0 upgrade). diff --git a/news/2 Fixes/1472.md b/news/2 Fixes/1472.md deleted file mode 100644 index 873641719abf..000000000000 --- a/news/2 Fixes/1472.md +++ /dev/null @@ -1 +0,0 @@ -Clicking the codelens `Run Test` on a test class should run that specific test class instead of all tests in the file. diff --git a/news/2 Fixes/1473.md b/news/2 Fixes/1473.md deleted file mode 100644 index 80df87dc31df..000000000000 --- a/news/2 Fixes/1473.md +++ /dev/null @@ -1 +0,0 @@ -Clicking the codelens `Run Test` on a test class or method should run that specific test instead of all tests in the file. diff --git a/news/2 Fixes/1487.md b/news/2 Fixes/1487.md deleted file mode 100644 index 0d1bd5cfeb37..000000000000 --- a/news/2 Fixes/1487.md +++ /dev/null @@ -1 +0,0 @@ -Check whether the selected python interpreter is valid before starting the language server. Failing to do so could result in the extension failing to load. diff --git a/news/2 Fixes/1520.md b/news/2 Fixes/1520.md deleted file mode 100644 index 5f5d52ec9e44..000000000000 --- a/news/2 Fixes/1520.md +++ /dev/null @@ -1 +0,0 @@ -Fixes the issue where Conda environments created using the latest version of Anaconda are not activated in Powershell. diff --git a/news/2 Fixes/1533.md b/news/2 Fixes/1533.md deleted file mode 100644 index 30f0e0452b1a..000000000000 --- a/news/2 Fixes/1533.md +++ /dev/null @@ -1 +0,0 @@ -Increase the delay for the activation of environments in Powershell terminals. diff --git a/news/2 Fixes/1534.md b/news/2 Fixes/1534.md deleted file mode 100644 index c0e5b965116c..000000000000 --- a/news/2 Fixes/1534.md +++ /dev/null @@ -1 +0,0 @@ -Fix activation of environments with spaces in the python path when using Powershell. diff --git a/news/2 Fixes/1535.md b/news/2 Fixes/1535.md deleted file mode 100644 index 04b3bc69c3dd..000000000000 --- a/news/2 Fixes/1535.md +++ /dev/null @@ -1 +0,0 @@ -Ensure Flask application is launched with multi-threading disabled, when run in the CI tests. diff --git a/news/2 Fixes/178.md b/news/2 Fixes/178.md deleted file mode 100644 index 0d7105e2dc8c..000000000000 --- a/news/2 Fixes/178.md +++ /dev/null @@ -1 +0,0 @@ -IntelliSense works appropriately when a project contains multiple files with the same name (thanks to Jedi 0.12.0 update). diff --git a/news/2 Fixes/259.md b/news/2 Fixes/259.md deleted file mode 100644 index 6460214099c3..000000000000 --- a/news/2 Fixes/259.md +++ /dev/null @@ -1 +0,0 @@ -Add blank lines to separate blocks of indented code (function defs, classes, and the like) so as to ensure the code can be run within a Python interactive prompt. diff --git a/news/2 Fixes/338.md b/news/2 Fixes/338.md deleted file mode 100644 index 8c4553127ac2..000000000000 --- a/news/2 Fixes/338.md +++ /dev/null @@ -1 +0,0 @@ -Provide type details appropriate for the iterable in a `for` loop when the line has a `# type` comment. diff --git a/news/2 Fixes/344.md b/news/2 Fixes/344.md deleted file mode 100644 index 0a1d459ac2d6..000000000000 --- a/news/2 Fixes/344.md +++ /dev/null @@ -1 +0,0 @@ -Parameter hints following an f-string work again. diff --git a/news/2 Fixes/726.md b/news/2 Fixes/726.md deleted file mode 100644 index 3600f6648324..000000000000 --- a/news/2 Fixes/726.md +++ /dev/null @@ -1 +0,0 @@ -When `editor.formatOnType` is on, don't indent after a single-line statement block diff --git a/news/2 Fixes/737.md b/news/2 Fixes/737.md deleted file mode 100644 index e72295893f94..000000000000 --- a/news/2 Fixes/737.md +++ /dev/null @@ -1 +0,0 @@ -Fix debugging of Pyramid applications on Windows. diff --git a/news/3 Code Health/1216.md b/news/3 Code Health/1216.md deleted file mode 100644 index 884f1ba6286a..000000000000 --- a/news/3 Code Health/1216.md +++ /dev/null @@ -1 +0,0 @@ -Improved developer experience of the Python Extension on Windows. \ No newline at end of file diff --git a/news/3 Code Health/1247.md b/news/3 Code Health/1247.md deleted file mode 100644 index 458a70c5d1dc..000000000000 --- a/news/3 Code Health/1247.md +++ /dev/null @@ -1 +0,0 @@ -Parallelize jobs (unit tests) on CI server. diff --git a/news/3 Code Health/1253.md b/news/3 Code Health/1253.md deleted file mode 100644 index aabafccfc97a..000000000000 --- a/news/3 Code Health/1253.md +++ /dev/null @@ -1 +0,0 @@ -Run CI tests against the release version and master branch of PTVSD (experimental debugger), allowing tests to fail against the master branch of PTVSD. diff --git a/news/3 Code Health/1298.md b/news/3 Code Health/1298.md deleted file mode 100644 index 12214ac71549..000000000000 --- a/news/3 Code Health/1298.md +++ /dev/null @@ -1,3 +0,0 @@ -Only trigger the extension for `file` and `untitled` in preparation for -[Visual Studio Live Share](https://aka.ms/vsls) -(thanks to [Jonathan Carter](https://github.com/lostintangent)) diff --git a/news/3 Code Health/1318.md b/news/3 Code Health/1318.md deleted file mode 100644 index d0f965f5a448..000000000000 --- a/news/3 Code Health/1318.md +++ /dev/null @@ -1 +0,0 @@ -Ensure all unit tests run on Travis use the right Python interpreter. diff --git a/news/3 Code Health/1374.md b/news/3 Code Health/1374.md deleted file mode 100644 index 779b3920a1a8..000000000000 --- a/news/3 Code Health/1374.md +++ /dev/null @@ -1 +0,0 @@ -Pin all production dependencies. diff --git a/news/3 Code Health/1409.md b/news/3 Code Health/1409.md deleted file mode 100644 index 24e5dd3195ec..000000000000 --- a/news/3 Code Health/1409.md +++ /dev/null @@ -1 +0,0 @@ -Add support for [hit count breakpoints](https://code.visualstudio.com/docs/editor/debugging#_advanced-breakpoint-topics) in the experimental debugger. diff --git a/news/3 Code Health/1428.md b/news/3 Code Health/1428.md deleted file mode 100644 index 0a8db4aa2ee7..000000000000 --- a/news/3 Code Health/1428.md +++ /dev/null @@ -1 +0,0 @@ -Ensure custom environment variables defined in `.env` file are passed onto the `pipenv` command. diff --git a/news/3 Code Health/1494.md b/news/3 Code Health/1494.md deleted file mode 100644 index a23e50e189ea..000000000000 --- a/news/3 Code Health/1494.md +++ /dev/null @@ -1 +0,0 @@ -Remove unwanted python packages no longer used in unit tests. diff --git a/news/3 Code Health/1530.md b/news/3 Code Health/1530.md deleted file mode 100644 index e5692016a587..000000000000 --- a/news/3 Code Health/1530.md +++ /dev/null @@ -1 +0,0 @@ -Register language server functionality in the extension against specific resource types supporting the python language. diff --git a/package.json b/package.json index a0dd00b5f6a7..21c8c6834da6 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2018.4.0-rc", + "version": "2018.4.0", "publisher": "ms-python", "author": { "name": "Microsoft Corporation" @@ -52,7 +52,7 @@ "multi-root ready" ], "categories": [ - "Languages", + "Programming Languages", "Debuggers", "Linters", "Snippets", From 50e07dbd5d811306441c9f37cd93f21958a07fc7 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Wed, 2 May 2018 11:40:05 -0700 Subject: [PATCH 212/433] Go to 2018.5.0-alpha --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 21c8c6834da6..e2a93ec80a2c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2018.4.0", + "version": "2018.5.0-alpha", "publisher": "ms-python", "author": { "name": "Microsoft Corporation" From 7f6a4c4f462c47c7505cf8253b4b79ea73d46a90 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Wed, 2 May 2018 11:48:52 -0700 Subject: [PATCH 213/433] Release plan template --- .github/release_plan.md | 95 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 .github/release_plan.md diff --git a/.github/release_plan.md b/.github/release_plan.md new file mode 100644 index 000000000000..573eb4d3cec6 --- /dev/null +++ b/.github/release_plan.md @@ -0,0 +1,95 @@ +Helpful links & info: +* [Milestone](https://github.com/Microsoft/vscode-python/milestone/6) + +# Schedule + +## Monday, Apr 02 + +- [ ] Review the state of the current [milestone](https://github.com/Microsoft/vscode-python/milestones) +- [ ] Go through telemetry for GDPR +- [ ] Go through all [merged pull requests](https://github.com/Microsoft/vscode-python/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged) and add the [`validate fix` label](https://github.com/Microsoft/vscode-python/labels/validate%20fix) as appropriate as well as checking for news entries + +### Planning +- [ ] Evaluate if TypeScript usage needs updating to sync with VS Code's usage +- [ ] Evaluate [projects](https://github.com/Microsoft/vscode-python/projects) & [`meta` issues](https://github.com/Microsoft/vscode-python/labels/meta) +- [ ] Go through [`needs PR` issues](https://github.com/Microsoft/vscode-python/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22needs+PR%22+-label%3A%22help+wanted%22+-label%3A%22good+first+issue%22+no%3Amilestone) to see if there's anything we want to add to this milestone +- [ ] Finalize the initial set of issues for the [milestone](https://github.com/Microsoft/vscode-python/milestones) +- [ ] Make sure all issues for this [milestone](https://github.com/Microsoft/vscode-python/milestones) are assigned +- [ ] Close issues that have [needed more info](https://github.com/Microsoft/vscode-python/issues?q=is%3Aopen+label%3A%22needs+more+info%22+sort%3Aupdated-asc) for over a month + +## Monday, Apr 09 + +- [ ] Review the state of the current [milestone](https://github.com/Microsoft/vscode-python/milestones) +- [ ] Go through telemetry for GDPR +- [ ] Go through all [merged pull requests](https://github.com/Microsoft/vscode-python/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged) and add the [`validate fix` label](https://github.com/Microsoft/vscode-python/labels/validate%20fix) as appropriate as well as checking for news entries + +### Planning +- [ ] Check if there have been no performance regressions +- [ ] Read through [VS Code's iteration plan](https://github.com/Microsoft/vscode/labels/iteration-plan) (it may still be a [draft](https://github.com/Microsoft/vscode/labels/iteration-plan-draft)) + +## Monday, Apr 16 + +- [ ] Review the state of the current [milestone](https://github.com/Microsoft/vscode-python/milestones) +- [ ] Go through telemetry for GDPR +- [ ] Go through all [merged pull requests](https://github.com/Microsoft/vscode-python/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged) and add the [`validate fix` label](https://github.com/Microsoft/vscode-python/labels/validate%20fix) as appropriate as well as checking for news entries + +## Monday, Apr 23 + +- [ ] Review the state of the current [milestone](https://github.com/Microsoft/vscode-python/milestones) +- [ ] Go through telemetry for GDPR +- [ ] Go through all [merged pull requests](https://github.com/Microsoft/vscode-python/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged) and add the [`validate fix` label](https://github.com/Microsoft/vscode-python/labels/validate%20fix) as appropriate as well as checking for news entries + +### Legal +- [ ] Announce the lock-down of dependencies for this release +- [ ] Notify CELA of all changes to the [repository](https://github.com/Microsoft/vscode-python/tree/master/pythonFiles) and [distribution dependencies](https://github.com/Microsoft/vscode-python/blob/master/package.json) + +### Release a beta version for testing +- [ ] Update the [version](https://github.com/Microsoft/vscode-python/blob/master/package.json) to be a `beta` & update the [changelog](https://github.com/Microsoft/vscode-python/blob/master/CHANGELOG.md) +- [ ] Announce the beta [development build](https://github.com/Microsoft/vscode-python/blob/master/CONTRIBUTING.md#development-build) (along with how to help [validate fixes](https://github.com/Microsoft/vscode-python/issues?q=label%3A%22validate+fix%22+is%3Aclosed)) +- [ ] Open appropriate [documentation issues](https://github.com/microsoft/vscode-docs) + +## Monday, Apr 30 + +- [ ] Review the state of the current [milestone](https://github.com/Microsoft/vscode-python/milestones) +- [ ] Go through telemetry for GDPR +- [ ] Merge any last-minute [pull requests](https://github.com/Microsoft/vscode-python/pulls) +- [ ] Go through all [merged pull requests](https://github.com/Microsoft/vscode-python/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged) and add the [`validate fix` label](https://github.com/Microsoft/vscode-python/labels/validate%20fix) as appropriate as well as checking for news entries + +### Prep for the release candidate +- [ ] Announce feature freeze +- [ ] Make sure the [repo](https://github.com/Microsoft/vscode-python/blob/master/ThirdPartyNotices-Repository.txt) and [distribution TPNs](https://github.com/Microsoft/vscode-python/blob/master/ThirdPartyNotices-Distribution.txt) have been updated appropriately + +### Test the release candidate code +- [ ] Update the [version](https://github.com/Microsoft/vscode-python/blob/master/package.json) to be an `rc` & update the [changelog](https://github.com/Microsoft/vscode-python/blob/master/CHANGELOG.md) +- [ ] Announce the release candidate [development build](https://github.com/Microsoft/vscode-python/blob/master/CONTRIBUTING.md#development-build) +- [ ] Open appropriate [documentation issues](https://github.com/microsoft/vscode-docs) +- [ ] Begin drafting a [blog](http://aka.ms/pythonblog) post + +### Prep the release +- [ ] Ensure all new feature usages are tracked via telemetry +- [ ] Make sure no extraneous files are being included in the `.vsix` file (make sure to check for hidden files) +- [ ] Make sure the [appropriate pull requests](https://github.com/microsoft/vscode-docs/pulls) for the [documentation](https://code.visualstudio.com/docs/python/python-tutorial) -- including the [WOW](https://code.visualstudio.com/docs/languages/python) page -- are ready + +## Wednesday, May 02 (hopefully 😉) + +### Release +- [ ] Update the [changelog](https://github.com/Microsoft/vscode-python/blob/master/CHANGELOG.md) (including the names of external contributors & projects) +- [ ] Update the [version](https://github.com/Microsoft/vscode-python/blob/master/package.json) number to be final +- [ ] Make sure [CI](https://github.com/Microsoft/vscode-python/blob/master/CONTRIBUTING.md) is passing +- [ ] Create the `release-` [branch](https://github.com/Microsoft/vscode-python/branches) +- [ ] Generate final `.vsix` file from the `release-` branch +- [ ] Upload the final `.vsix` file to the [marketplace](https://marketplace.visualstudio.com/items?itemName=ms-python.python) +- [ ] Publish [documentation](https://code.visualstudio.com/docs/python/python-tutorial) [changes](https://github.com/microsoft/vscode-docs/pulls) +- [ ] Publish the [blog](http://aka.ms/pythonblog) post +- [ ] Create a [release](https://github.com/Microsoft/vscode-python/releases) on GitHub (which creates an appropriate git tag) + +### Prep for the next release +- [ ] Bump the [version](https://github.com/Microsoft/vscode-python/blob/master/package.json) number to the next `alpha` +- [ ] Make sure the next **two** [milestones](https://github.com/Microsoft/vscode-python/milestones) exist +- [ ] Lift the feature freeze +- [ ] Create a new [release plan](https://github.com/Microsoft/vscode-python/labels/release%20plan) + +### Clean up after this release +- [ ] Clean up any straggling [fixed issues needing validation](https://github.com/Microsoft/vscode-python/issues?q=label%3A%22validate+fix%22+is%3Aclosed) +- [ ] Close the (now) old [milestone](https://github.com/Microsoft/vscode-python/labels/release%20plan) +- [ ] Delete the previous releases' [branch](https://github.com/Microsoft/vscode-python/branches) From 71c8b22b79a4797488af61152f7ca8c0cd321235 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Wed, 2 May 2018 11:52:42 -0700 Subject: [PATCH 214/433] "XXX" mark variance per release Also lower the section levels. --- .github/release_plan.md | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/.github/release_plan.md b/.github/release_plan.md index 573eb4d3cec6..1c840915906b 100644 --- a/.github/release_plan.md +++ b/.github/release_plan.md @@ -1,15 +1,10 @@ -Helpful links & info: -* [Milestone](https://github.com/Microsoft/vscode-python/milestone/6) - -# Schedule - -## Monday, Apr 02 +# Monday, XXX XX - [ ] Review the state of the current [milestone](https://github.com/Microsoft/vscode-python/milestones) - [ ] Go through telemetry for GDPR - [ ] Go through all [merged pull requests](https://github.com/Microsoft/vscode-python/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged) and add the [`validate fix` label](https://github.com/Microsoft/vscode-python/labels/validate%20fix) as appropriate as well as checking for news entries -### Planning +## Planning - [ ] Evaluate if TypeScript usage needs updating to sync with VS Code's usage - [ ] Evaluate [projects](https://github.com/Microsoft/vscode-python/projects) & [`meta` issues](https://github.com/Microsoft/vscode-python/labels/meta) - [ ] Go through [`needs PR` issues](https://github.com/Microsoft/vscode-python/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22needs+PR%22+-label%3A%22help+wanted%22+-label%3A%22good+first+issue%22+no%3Amilestone) to see if there's anything we want to add to this milestone @@ -17,62 +12,62 @@ Helpful links & info: - [ ] Make sure all issues for this [milestone](https://github.com/Microsoft/vscode-python/milestones) are assigned - [ ] Close issues that have [needed more info](https://github.com/Microsoft/vscode-python/issues?q=is%3Aopen+label%3A%22needs+more+info%22+sort%3Aupdated-asc) for over a month -## Monday, Apr 09 +# Monday, XXX XXX - [ ] Review the state of the current [milestone](https://github.com/Microsoft/vscode-python/milestones) - [ ] Go through telemetry for GDPR - [ ] Go through all [merged pull requests](https://github.com/Microsoft/vscode-python/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged) and add the [`validate fix` label](https://github.com/Microsoft/vscode-python/labels/validate%20fix) as appropriate as well as checking for news entries -### Planning +## Planning - [ ] Check if there have been no performance regressions - [ ] Read through [VS Code's iteration plan](https://github.com/Microsoft/vscode/labels/iteration-plan) (it may still be a [draft](https://github.com/Microsoft/vscode/labels/iteration-plan-draft)) -## Monday, Apr 16 +# Monday, XXX XXX - [ ] Review the state of the current [milestone](https://github.com/Microsoft/vscode-python/milestones) - [ ] Go through telemetry for GDPR - [ ] Go through all [merged pull requests](https://github.com/Microsoft/vscode-python/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged) and add the [`validate fix` label](https://github.com/Microsoft/vscode-python/labels/validate%20fix) as appropriate as well as checking for news entries -## Monday, Apr 23 +# Monday, XXX XXX - [ ] Review the state of the current [milestone](https://github.com/Microsoft/vscode-python/milestones) - [ ] Go through telemetry for GDPR - [ ] Go through all [merged pull requests](https://github.com/Microsoft/vscode-python/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged) and add the [`validate fix` label](https://github.com/Microsoft/vscode-python/labels/validate%20fix) as appropriate as well as checking for news entries -### Legal +## Legal - [ ] Announce the lock-down of dependencies for this release - [ ] Notify CELA of all changes to the [repository](https://github.com/Microsoft/vscode-python/tree/master/pythonFiles) and [distribution dependencies](https://github.com/Microsoft/vscode-python/blob/master/package.json) -### Release a beta version for testing +## Release a beta version for testing - [ ] Update the [version](https://github.com/Microsoft/vscode-python/blob/master/package.json) to be a `beta` & update the [changelog](https://github.com/Microsoft/vscode-python/blob/master/CHANGELOG.md) - [ ] Announce the beta [development build](https://github.com/Microsoft/vscode-python/blob/master/CONTRIBUTING.md#development-build) (along with how to help [validate fixes](https://github.com/Microsoft/vscode-python/issues?q=label%3A%22validate+fix%22+is%3Aclosed)) - [ ] Open appropriate [documentation issues](https://github.com/microsoft/vscode-docs) -## Monday, Apr 30 +# Monday, XXX XXX - [ ] Review the state of the current [milestone](https://github.com/Microsoft/vscode-python/milestones) - [ ] Go through telemetry for GDPR - [ ] Merge any last-minute [pull requests](https://github.com/Microsoft/vscode-python/pulls) - [ ] Go through all [merged pull requests](https://github.com/Microsoft/vscode-python/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged) and add the [`validate fix` label](https://github.com/Microsoft/vscode-python/labels/validate%20fix) as appropriate as well as checking for news entries -### Prep for the release candidate +## Prep for the release candidate - [ ] Announce feature freeze - [ ] Make sure the [repo](https://github.com/Microsoft/vscode-python/blob/master/ThirdPartyNotices-Repository.txt) and [distribution TPNs](https://github.com/Microsoft/vscode-python/blob/master/ThirdPartyNotices-Distribution.txt) have been updated appropriately -### Test the release candidate code +## Test the release candidate code - [ ] Update the [version](https://github.com/Microsoft/vscode-python/blob/master/package.json) to be an `rc` & update the [changelog](https://github.com/Microsoft/vscode-python/blob/master/CHANGELOG.md) - [ ] Announce the release candidate [development build](https://github.com/Microsoft/vscode-python/blob/master/CONTRIBUTING.md#development-build) - [ ] Open appropriate [documentation issues](https://github.com/microsoft/vscode-docs) - [ ] Begin drafting a [blog](http://aka.ms/pythonblog) post -### Prep the release +## Prep the release - [ ] Ensure all new feature usages are tracked via telemetry - [ ] Make sure no extraneous files are being included in the `.vsix` file (make sure to check for hidden files) - [ ] Make sure the [appropriate pull requests](https://github.com/microsoft/vscode-docs/pulls) for the [documentation](https://code.visualstudio.com/docs/python/python-tutorial) -- including the [WOW](https://code.visualstudio.com/docs/languages/python) page -- are ready -## Wednesday, May 02 (hopefully 😉) +# Wednesday, XXX XXX (hopefully 😉) -### Release +## Release - [ ] Update the [changelog](https://github.com/Microsoft/vscode-python/blob/master/CHANGELOG.md) (including the names of external contributors & projects) - [ ] Update the [version](https://github.com/Microsoft/vscode-python/blob/master/package.json) number to be final - [ ] Make sure [CI](https://github.com/Microsoft/vscode-python/blob/master/CONTRIBUTING.md) is passing @@ -83,13 +78,13 @@ Helpful links & info: - [ ] Publish the [blog](http://aka.ms/pythonblog) post - [ ] Create a [release](https://github.com/Microsoft/vscode-python/releases) on GitHub (which creates an appropriate git tag) -### Prep for the next release +## Prep for the next release - [ ] Bump the [version](https://github.com/Microsoft/vscode-python/blob/master/package.json) number to the next `alpha` - [ ] Make sure the next **two** [milestones](https://github.com/Microsoft/vscode-python/milestones) exist - [ ] Lift the feature freeze - [ ] Create a new [release plan](https://github.com/Microsoft/vscode-python/labels/release%20plan) -### Clean up after this release +## Clean up after this release - [ ] Clean up any straggling [fixed issues needing validation](https://github.com/Microsoft/vscode-python/issues?q=label%3A%22validate+fix%22+is%3Aclosed) - [ ] Close the (now) old [milestone](https://github.com/Microsoft/vscode-python/labels/release%20plan) - [ ] Delete the previous releases' [branch](https://github.com/Microsoft/vscode-python/branches) From 3b59f2bf106b80ae13fe3d181343c14c5c4ea1d0 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Wed, 2 May 2018 11:53:17 -0700 Subject: [PATCH 215/433] Tweak "XXX" markers --- .github/release_plan.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/release_plan.md b/.github/release_plan.md index 1c840915906b..09a225f27168 100644 --- a/.github/release_plan.md +++ b/.github/release_plan.md @@ -1,4 +1,4 @@ -# Monday, XXX XX +# Monday, XXX - [ ] Review the state of the current [milestone](https://github.com/Microsoft/vscode-python/milestones) - [ ] Go through telemetry for GDPR @@ -12,7 +12,7 @@ - [ ] Make sure all issues for this [milestone](https://github.com/Microsoft/vscode-python/milestones) are assigned - [ ] Close issues that have [needed more info](https://github.com/Microsoft/vscode-python/issues?q=is%3Aopen+label%3A%22needs+more+info%22+sort%3Aupdated-asc) for over a month -# Monday, XXX XXX +# Monday, XXX - [ ] Review the state of the current [milestone](https://github.com/Microsoft/vscode-python/milestones) - [ ] Go through telemetry for GDPR @@ -22,13 +22,13 @@ - [ ] Check if there have been no performance regressions - [ ] Read through [VS Code's iteration plan](https://github.com/Microsoft/vscode/labels/iteration-plan) (it may still be a [draft](https://github.com/Microsoft/vscode/labels/iteration-plan-draft)) -# Monday, XXX XXX +# Monday, XXX - [ ] Review the state of the current [milestone](https://github.com/Microsoft/vscode-python/milestones) - [ ] Go through telemetry for GDPR - [ ] Go through all [merged pull requests](https://github.com/Microsoft/vscode-python/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged) and add the [`validate fix` label](https://github.com/Microsoft/vscode-python/labels/validate%20fix) as appropriate as well as checking for news entries -# Monday, XXX XXX +# Monday, XXX - [ ] Review the state of the current [milestone](https://github.com/Microsoft/vscode-python/milestones) - [ ] Go through telemetry for GDPR @@ -43,7 +43,7 @@ - [ ] Announce the beta [development build](https://github.com/Microsoft/vscode-python/blob/master/CONTRIBUTING.md#development-build) (along with how to help [validate fixes](https://github.com/Microsoft/vscode-python/issues?q=label%3A%22validate+fix%22+is%3Aclosed)) - [ ] Open appropriate [documentation issues](https://github.com/microsoft/vscode-docs) -# Monday, XXX XXX +# Monday, XXX - [ ] Review the state of the current [milestone](https://github.com/Microsoft/vscode-python/milestones) - [ ] Go through telemetry for GDPR @@ -65,7 +65,7 @@ - [ ] Make sure no extraneous files are being included in the `.vsix` file (make sure to check for hidden files) - [ ] Make sure the [appropriate pull requests](https://github.com/microsoft/vscode-docs/pulls) for the [documentation](https://code.visualstudio.com/docs/python/python-tutorial) -- including the [WOW](https://code.visualstudio.com/docs/languages/python) page -- are ready -# Wednesday, XXX XXX (hopefully 😉) +# Wednesday, XXX (hopefully 😉) ## Release - [ ] Update the [changelog](https://github.com/Microsoft/vscode-python/blob/master/CHANGELOG.md) (including the names of external contributors & projects) From c6ac96eada90052d8d0abfd1af9d278b30b26594 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Wed, 2 May 2018 11:58:03 -0700 Subject: [PATCH 216/433] Shift to a 4 Monday month --- .github/release_plan.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/release_plan.md b/.github/release_plan.md index 09a225f27168..6a5f2cc466b7 100644 --- a/.github/release_plan.md +++ b/.github/release_plan.md @@ -28,12 +28,6 @@ - [ ] Go through telemetry for GDPR - [ ] Go through all [merged pull requests](https://github.com/Microsoft/vscode-python/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged) and add the [`validate fix` label](https://github.com/Microsoft/vscode-python/labels/validate%20fix) as appropriate as well as checking for news entries -# Monday, XXX - -- [ ] Review the state of the current [milestone](https://github.com/Microsoft/vscode-python/milestones) -- [ ] Go through telemetry for GDPR -- [ ] Go through all [merged pull requests](https://github.com/Microsoft/vscode-python/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged) and add the [`validate fix` label](https://github.com/Microsoft/vscode-python/labels/validate%20fix) as appropriate as well as checking for news entries - ## Legal - [ ] Announce the lock-down of dependencies for this release - [ ] Notify CELA of all changes to the [repository](https://github.com/Microsoft/vscode-python/tree/master/pythonFiles) and [distribution dependencies](https://github.com/Microsoft/vscode-python/blob/master/package.json) From 5252320e5254fc98123b6b97d06be1beb28cb0ff Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 3 May 2018 15:21:27 -0700 Subject: [PATCH 217/433] Minor format tweak --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e2a93ec80a2c..3568488dee4f 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "path": "./snippets/python.json" } ], - "keybindings":[ + "keybindings": [ { "command": "python.execSelectionInTerminal", "key": "ctrl+enter", From 28abf11ee0595c82fb7e786e4764eec32e291a6a Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 4 May 2018 21:09:10 -0700 Subject: [PATCH 218/433] Support the Black formatter (#1611) Co-authored-by: Josh Smeaton --- news/1 Enhancements/1153.md | 2 + package.json | 18 ++++- requirements.txt | 1 + src/client/common/configSettings.ts | 7 +- .../common/installer/productInstaller.ts | 72 +++++++++++-------- src/client/common/installer/productNames.ts | 1 + src/client/common/types.ts | 11 +-- src/client/formatters/baseFormatter.ts | 19 +++-- src/client/formatters/blackFormatter.ts | 41 +++++++++++ src/client/formatters/helper.ts | 6 +- src/client/formatters/types.ts | 2 +- src/client/providers/formatProvider.ts | 5 +- src/client/telemetry/types.ts | 2 +- src/test/format/extension.format.test.ts | 47 ++++++++---- src/test/format/format.helper.test.ts | 12 ++-- src/test/pythonFiles/formatting/black.output | 54 ++++++++++++++ 16 files changed, 228 insertions(+), 72 deletions(-) create mode 100644 news/1 Enhancements/1153.md create mode 100644 src/client/formatters/blackFormatter.ts create mode 100644 src/test/pythonFiles/formatting/black.output diff --git a/news/1 Enhancements/1153.md b/news/1 Enhancements/1153.md new file mode 100644 index 000000000000..9f19892e6d51 --- /dev/null +++ b/news/1 Enhancements/1153.md @@ -0,0 +1,2 @@ +Add support for the [Black formatter](https://pypi.org/project/black/) +(thanks to [Josh Smeaton](https://github.com/jarshwah) for the initial patch) diff --git a/package.json b/package.json index 3568488dee4f..96dc0a2eb896 100644 --- a/package.json +++ b/package.json @@ -1200,14 +1200,30 @@ "python.formatting.provider": { "type": "string", "default": "autopep8", - "description": "Provider for formatting. Possible options include 'autopep8' and 'yapf'.", + "description": "Provider for formatting. Possible options include 'autopep8', 'black', and 'yapf'.", "enum": [ "autopep8", + "black", "yapf", "none" ], "scope": "resource" }, + "python.formatting.blackArgs": { + "type": "array", + "description": "Arguments passed in. Each argument is a separate item in the array.", + "default": [], + "items": { + "type": "string" + }, + "scope": "resource" + }, + "python.formatting.blackPath": { + "type": "string", + "default": "black", + "description": "Path to Black, you can use a custom version of Black by modifying this setting to include the full path.", + "scope": "resource" + }, "python.formatting.yapfArgs": { "type": "array", "description": "Arguments passed in. Each argument is a separate item in the array.", diff --git a/requirements.txt b/requirements.txt index 71762bf0eae3..01c6059a10cb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ # but flake8 has a tighter pinning. flake8 autopep8 +black ; python_version>='3.6' yapf pylint pep8 diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index 99c111d64f70..86d8ad57a218 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -34,12 +34,12 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { public venvFolders: string[] = []; public devOptions: string[] = []; public linting?: ILintingSettings; - public formatting?: IFormattingSettings; + public formatting!: IFormattingSettings; public autoComplete?: IAutoCompleteSettings; - public unitTest?: IUnitTestSettings; + public unitTest!: IUnitTestSettings; public terminal!: ITerminalSettings; public sortImports?: ISortImportSettings; - public workspaceSymbols?: IWorkspaceSymbolSettings; + public workspaceSymbols!: IWorkspaceSymbolSettings; public disableInstallationChecks = false; public globalModuleInstallation = false; @@ -213,6 +213,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { this.formatting = this.formatting ? this.formatting : { autopep8Args: [], autopep8Path: 'autopep8', provider: 'autopep8', + blackArgs: [], blackPath: 'black', yapfArgs: [], yapfPath: 'yapf' }; this.formatting.autopep8Path = getAbsolutePath(systemVariables.resolveAny(this.formatting.autopep8Path), workspaceRoot); diff --git a/src/client/common/installer/productInstaller.ts b/src/client/common/installer/productInstaller.ts index 15610472ad80..a0929e86de66 100644 --- a/src/client/common/installer/productInstaller.ts +++ b/src/client/common/installer/productInstaller.ts @@ -1,7 +1,6 @@ import { inject, injectable, named } from 'inversify'; import * as os from 'os'; import * as path from 'path'; -import { OutputChannel, Uri } from 'vscode'; import * as vscode from 'vscode'; import { IFormatterHelper } from '../../formatters/types'; import { IServiceContainer } from '../../ioc/types'; @@ -33,14 +32,14 @@ abstract class BaseInstaller { protected appShell: IApplicationShell; protected configService: IConfigurationService; - constructor(protected serviceContainer: IServiceContainer, protected outputChannel: OutputChannel) { + constructor(protected serviceContainer: IServiceContainer, protected outputChannel: vscode.OutputChannel) { this.appShell = serviceContainer.get(IApplicationShell); this.configService = serviceContainer.get(IConfigurationService); } - public abstract promptToInstall(product: Product, resource?: Uri): Promise; + public abstract promptToInstall(product: Product, resource?: vscode.Uri): Promise; - public async install(product: Product, resource?: Uri): Promise { + public async install(product: Product, resource?: vscode.Uri): Promise { if (product === Product.unittest) { return InstallerResponse.Installed; } @@ -60,7 +59,7 @@ abstract class BaseInstaller { .then(isInstalled => isInstalled ? InstallerResponse.Installed : InstallerResponse.Ignore); } - public async isInstalled(product: Product, resource?: Uri): Promise { + public async isInstalled(product: Product, resource?: vscode.Uri): Promise { if (product === Product.unittest) { return true; } @@ -85,22 +84,22 @@ abstract class BaseInstaller { } } - protected getExecutableNameFromSettings(product: Product, resource?: Uri): string { + protected getExecutableNameFromSettings(product: Product, resource?: vscode.Uri): string { throw new Error('getExecutableNameFromSettings is not supported on this object'); } } class CTagsInstaller extends BaseInstaller { - constructor(serviceContainer: IServiceContainer, outputChannel: OutputChannel) { + constructor(serviceContainer: IServiceContainer, outputChannel: vscode.OutputChannel) { super(serviceContainer, outputChannel); } - public async promptToInstall(product: Product, resource?: Uri): Promise { + public async promptToInstall(product: Product, resource?: vscode.Uri): Promise { const item = await this.appShell.showErrorMessage('Install CTags to enable Python workspace symbols?', 'Yes', 'No'); return item === 'Yes' ? this.install(product, resource) : InstallerResponse.Ignore; } - public async install(product: Product, resource?: Uri): Promise { + public async install(product: Product, resource?: vscode.Uri): Promise { if (this.serviceContainer.get(IPlatformService).isWindows) { this.outputChannel.appendLine('Install Universal Ctags Win32 to enable support for Workspace Symbols'); this.outputChannel.appendLine('Download the CTags binary from the Universal CTags site.'); @@ -117,32 +116,41 @@ class CTagsInstaller extends BaseInstaller { return InstallerResponse.Ignore; } - protected getExecutableNameFromSettings(product: Product, resource?: Uri): string { + protected getExecutableNameFromSettings(product: Product, resource?: vscode.Uri): string { const settings = this.configService.getSettings(resource); return settings.workspaceSymbols.ctagsPath; } } class FormatterInstaller extends BaseInstaller { - public async promptToInstall(product: Product, resource?: Uri): Promise { + public async promptToInstall(product: Product, resource?: vscode.Uri): Promise { + // Hard-coded on purpose because the UI won't necessarily work having + // another formatter. + const formatters = [Product.autopep8, Product.black, Product.yapf]; + const formatterNames = formatters.map((formatter) => ProductNames.get(formatter)!); const productName = ProductNames.get(product)!; + formatterNames.splice(formatterNames.indexOf(productName), 1); + const useOptions = formatterNames.map((name) => `Use ${name}`); + const yesChoice = 'Yes'; - const installThis = `Install ${productName}`; - const alternateFormatter = product === Product.autopep8 ? 'yapf' : 'autopep8'; - const useOtherFormatter = `Use '${alternateFormatter}' formatter`; - const item = await this.appShell.showErrorMessage(`Formatter ${productName} is not installed.`, installThis, useOtherFormatter); - - if (item === installThis) { + const item = await this.appShell.showErrorMessage(`Formatter ${productName} is not installed. Install?`, yesChoice, ...useOptions); + if (item === yesChoice) { return this.install(product, resource); + } else if (typeof item === 'string') { + for (const formatter of formatters) { + const formatterName = ProductNames.get(formatter)!; + + if (item.endsWith(formatterName)) { + await this.configService.updateSettingAsync('formatting.provider', formatterName, resource); + return this.install(formatter, resource); + } + } } - if (item === useOtherFormatter) { - await this.configService.updateSettingAsync('formatting.provider', alternateFormatter, resource); - return InstallerResponse.Installed; - } + return InstallerResponse.Ignore; } - protected getExecutableNameFromSettings(product: Product, resource?: Uri): string { + protected getExecutableNameFromSettings(product: Product, resource?: vscode.Uri): string { const settings = this.configService.getSettings(resource); const formatHelper = this.serviceContainer.get(IFormatterHelper); const settingsPropNames = formatHelper.getSettingsPropertyNames(product); @@ -152,7 +160,7 @@ class FormatterInstaller extends BaseInstaller { // tslint:disable-next-line:max-classes-per-file class LinterInstaller extends BaseInstaller { - public async promptToInstall(product: Product, resource?: Uri): Promise { + public async promptToInstall(product: Product, resource?: vscode.Uri): Promise { const productName = ProductNames.get(product)!; const install = 'Install'; const disableAllLinting = 'Disable linting'; @@ -173,7 +181,7 @@ class LinterInstaller extends BaseInstaller { } return InstallerResponse.Ignore; } - protected getExecutableNameFromSettings(product: Product, resource?: Uri): string { + protected getExecutableNameFromSettings(product: Product, resource?: vscode.Uri): string { const linterManager = this.serviceContainer.get(ILinterManager); return linterManager.getLinterInfo(product).pathName(resource); } @@ -181,13 +189,13 @@ class LinterInstaller extends BaseInstaller { // tslint:disable-next-line:max-classes-per-file class TestFrameworkInstaller extends BaseInstaller { - public async promptToInstall(product: Product, resource?: Uri): Promise { + public async promptToInstall(product: Product, resource?: vscode.Uri): Promise { const productName = ProductNames.get(product)!; const item = await this.appShell.showErrorMessage(`Test framework ${productName} is not installed. Install?`, 'Yes', 'No'); return item === 'Yes' ? this.install(product, resource) : InstallerResponse.Ignore; } - protected getExecutableNameFromSettings(product: Product, resource?: Uri): string { + protected getExecutableNameFromSettings(product: Product, resource?: vscode.Uri): string { const testHelper = this.serviceContainer.get(ITestsHelper); const settingsPropNames = testHelper.getSettingsPropertyNames(product); if (!settingsPropNames.pathName) { @@ -201,12 +209,12 @@ class TestFrameworkInstaller extends BaseInstaller { // tslint:disable-next-line:max-classes-per-file class RefactoringLibraryInstaller extends BaseInstaller { - public async promptToInstall(product: Product, resource?: Uri): Promise { + public async promptToInstall(product: Product, resource?: vscode.Uri): Promise { const productName = ProductNames.get(product)!; const item = await this.appShell.showErrorMessage(`Refactoring library ${productName} is not installed. Install?`, 'Yes', 'No'); return item === 'Yes' ? this.install(product, resource) : InstallerResponse.Ignore; } - protected getExecutableNameFromSettings(product: Product, resource?: Uri): string { + protected getExecutableNameFromSettings(product: Product, resource?: vscode.Uri): string { return translateProductToModule(product, ModuleNamePurpose.run); } } @@ -230,19 +238,20 @@ export class ProductInstaller implements IInstaller { this.ProductTypes.set(Product.pytest, ProductType.TestFramework); this.ProductTypes.set(Product.unittest, ProductType.TestFramework); this.ProductTypes.set(Product.autopep8, ProductType.Formatter); + this.ProductTypes.set(Product.black, ProductType.Formatter); this.ProductTypes.set(Product.yapf, ProductType.Formatter); this.ProductTypes.set(Product.rope, ProductType.RefactoringLibrary); } // tslint:disable-next-line:no-empty public dispose() { } - public async promptToInstall(product: Product, resource?: Uri): Promise { + public async promptToInstall(product: Product, resource?: vscode.Uri): Promise { return this.createInstaller(product).promptToInstall(product, resource); } - public async install(product: Product, resource?: Uri): Promise { + public async install(product: Product, resource?: vscode.Uri): Promise { return this.createInstaller(product).install(product, resource); } - public async isInstalled(product: Product, resource?: Uri): Promise { + public async isInstalled(product: Product, resource?: vscode.Uri): Promise { return this.createInstaller(product).isInstalled(product, resource); } public translateProductToModuleName(product: Product, purpose: ModuleNamePurpose): string { @@ -280,6 +289,7 @@ function translateProductToModule(product: Product, purpose: ModuleNamePurpose): case Product.pylint: return 'pylint'; case Product.pytest: return 'pytest'; case Product.autopep8: return 'autopep8'; + case Product.black: return 'black'; case Product.pep8: return 'pep8'; case Product.pydocstyle: return 'pydocstyle'; case Product.yapf: return 'yapf'; diff --git a/src/client/common/installer/productNames.ts b/src/client/common/installer/productNames.ts index 9371f540c778..dc58605cc57d 100644 --- a/src/client/common/installer/productNames.ts +++ b/src/client/common/installer/productNames.ts @@ -6,6 +6,7 @@ import { Product } from '../types'; // tslint:disable-next-line:variable-name export const ProductNames = new Map(); ProductNames.set(Product.autopep8, 'autopep8'); +ProductNames.set(Product.black, 'black'); ProductNames.set(Product.flake8, 'flake8'); ProductNames.set(Product.mypy, 'mypy'); ProductNames.set(Product.nosetest, 'nosetest'); diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 240b6f5809e3..35d77cae9846 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -62,7 +62,8 @@ export enum Product { unittest = 12, ctags = 13, rope = 14, - isort = 15 + isort = 15, + black = 16 } export enum ModuleNamePurpose { @@ -103,12 +104,12 @@ export interface IPythonSettings { readonly jediMemoryLimit: number; readonly devOptions: string[]; readonly linting?: ILintingSettings; - readonly formatting?: IFormattingSettings; - readonly unitTest?: IUnitTestSettings; + readonly formatting: IFormattingSettings; + readonly unitTest: IUnitTestSettings; readonly autoComplete?: IAutoCompleteSettings; readonly terminal: ITerminalSettings; readonly sortImports?: ISortImportSettings; - readonly workspaceSymbols?: IWorkspaceSymbolSettings; + readonly workspaceSymbols: IWorkspaceSymbolSettings; readonly envFile: string; readonly disablePromptForFeatures: string[]; readonly disableInstallationChecks: boolean; @@ -191,6 +192,8 @@ export interface IFormattingSettings { readonly provider: string; autopep8Path: string; readonly autopep8Args: string[]; + blackPath: string; + readonly blackArgs: string[]; yapfPath: string; readonly yapfArgs: string[]; } diff --git a/src/client/formatters/baseFormatter.ts b/src/client/formatters/baseFormatter.ts index 9e91b00a5a19..d72edac84532 100644 --- a/src/client/formatters/baseFormatter.ts +++ b/src/client/formatters/baseFormatter.ts @@ -1,7 +1,6 @@ import * as fs from 'fs-extra'; import * as path from 'path'; import * as vscode from 'vscode'; -import { OutputChannel, TextEdit, Uri } from 'vscode'; import { IWorkspaceService } from '../common/application/types'; import { STANDARD_OUTPUT_CHANNEL } from '../common/constants'; import '../common/extensions'; @@ -13,12 +12,12 @@ import { getTempFileWithDocumentContents, getTextEditsFromPatch } from './../com import { IFormatterHelper } from './types'; export abstract class BaseFormatter { - protected readonly outputChannel: OutputChannel; + protected readonly outputChannel: vscode.OutputChannel; protected readonly workspace: IWorkspaceService; private readonly helper: IFormatterHelper; constructor(public Id: string, private product: Product, protected serviceContainer: IServiceContainer) { - this.outputChannel = serviceContainer.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); + this.outputChannel = serviceContainer.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); this.helper = serviceContainer.get(IFormatterHelper); this.workspace = serviceContainer.get(IWorkspaceService); } @@ -49,8 +48,8 @@ export abstract class BaseFormatter { // autopep8 and yapf have the ability to read from the process input stream and return the formatted code out of the output stream. // However they don't support returning the diff of the formatted text when reading data from the input stream. - // Yes getting text formatted that way avoids having to create a temporary file, however the diffing will have - // to be done here in node (extension), i.e. extension cpu, i.e. les responsive solution. + // Yet getting text formatted that way avoids having to create a temporary file, however the diffing will have + // to be done here in node (extension), i.e. extension CPU, i.e. less responsive solution. const tempFile = await this.createTempFile(document); if (this.checkCancellation(document.fileName, tempFile, token)) { return []; @@ -63,17 +62,17 @@ export abstract class BaseFormatter { .then(output => output.stdout) .then(data => { if (this.checkCancellation(document.fileName, tempFile, token)) { - return [] as TextEdit[]; + return [] as vscode.TextEdit[]; } return getTextEditsFromPatch(document.getText(), data); }) .catch(error => { if (this.checkCancellation(document.fileName, tempFile, token)) { - return [] as TextEdit[]; + return [] as vscode.TextEdit[]; } // tslint:disable-next-line:no-empty this.handleError(this.Id, error, document.uri).catch(() => { }); - return [] as TextEdit[]; + return [] as vscode.TextEdit[]; }) .then(edits => { this.deleteTempFile(document.fileName, tempFile).ignoreErrors(); @@ -83,7 +82,7 @@ export abstract class BaseFormatter { return promise; } - protected async handleError(expectedFileName: string, error: Error, resource?: Uri) { + protected async handleError(expectedFileName: string, error: Error, resource?: vscode.Uri) { let customError = `Formatting with ${this.Id} failed.`; if (isNotInstalledError(error)) { @@ -100,7 +99,7 @@ export abstract class BaseFormatter { private async createTempFile(document: vscode.TextDocument): Promise { return document.isDirty - ? await getTempFileWithDocumentContents(document) + ? getTempFileWithDocumentContents(document) : document.fileName; } diff --git a/src/client/formatters/blackFormatter.ts b/src/client/formatters/blackFormatter.ts new file mode 100644 index 000000000000..d1bb356dc584 --- /dev/null +++ b/src/client/formatters/blackFormatter.ts @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as vscode from 'vscode'; +import { Product } from '../common/installer/productInstaller'; +import { StopWatch } from '../common/stopWatch'; +import { IConfigurationService } from '../common/types'; +import { IServiceContainer } from '../ioc/types'; +import { sendTelemetryWhenDone } from '../telemetry'; +import { FORMAT } from '../telemetry/constants'; +import { BaseFormatter } from './baseFormatter'; + +export class BlackFormatter extends BaseFormatter { + constructor(serviceContainer: IServiceContainer) { + super('black', Product.black, serviceContainer); + } + + public formatDocument(document: vscode.TextDocument, options: vscode.FormattingOptions, token: vscode.CancellationToken, range?: vscode.Range): Thenable { + const stopWatch = new StopWatch(); + const settings = this.serviceContainer.get(IConfigurationService).getSettings(document.uri); + const hasCustomArgs = Array.isArray(settings.formatting.blackArgs) && settings.formatting.blackArgs.length > 0; + const formatSelection = range ? !range.isEmpty : false; + + if (formatSelection) { + const errorMessage = async () => { + // Black does not support partial formatting on purpose. + await vscode.window.showErrorMessage('Black does not support the "Format Selection" command'); + return [] as vscode.TextEdit[]; + }; + + return errorMessage(); + } + + const blackArgs = ['--diff', '--quiet']; + const promise = super.provideDocumentFormattingEdits(document, options, token, blackArgs); + sendTelemetryWhenDone(FORMAT, promise, stopWatch, { tool: 'black', hasCustomArgs, formatSelection }); + return promise; + } +} diff --git a/src/client/formatters/helper.ts b/src/client/formatters/helper.ts index b491c40baaa2..95383e69b538 100644 --- a/src/client/formatters/helper.ts +++ b/src/client/formatters/helper.ts @@ -4,17 +4,17 @@ import { inject, injectable } from 'inversify'; import * as path from 'path'; import { Uri } from 'vscode'; -import { IConfigurationService, IFormattingSettings } from '../common/types'; -import { ExecutionInfo, Product } from '../common/types'; +import { ExecutionInfo, IConfigurationService, IFormattingSettings, Product } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { FormatterId, FormatterSettingsPropertyNames, IFormatterHelper } from './types'; @injectable() export class FormatterHelper implements IFormatterHelper { - constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer) { } + constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { } public translateToId(formatter: Product): FormatterId { switch (formatter) { case Product.autopep8: return 'autopep8'; + case Product.black: return 'black'; case Product.yapf: return 'yapf'; default: { throw new Error(`Unrecognized Formatter '${formatter}'`); diff --git a/src/client/formatters/types.ts b/src/client/formatters/types.ts index d21ace78bb4f..7f4bcf5b7524 100644 --- a/src/client/formatters/types.ts +++ b/src/client/formatters/types.ts @@ -6,7 +6,7 @@ import { ExecutionInfo, IFormattingSettings, Product } from '../common/types'; export const IFormatterHelper = Symbol('IFormatterHelper'); -export type FormatterId = 'autopep8' | 'yapf'; +export type FormatterId = 'autopep8' | 'black' | 'yapf'; export type FormatterSettingsPropertyNames = { argsName: keyof IFormattingSettings; diff --git a/src/client/providers/formatProvider.ts b/src/client/providers/formatProvider.ts index cabab87018e8..9398fd7b0e58 100644 --- a/src/client/providers/formatProvider.ts +++ b/src/client/providers/formatProvider.ts @@ -8,6 +8,7 @@ import { IConfigurationService } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { AutoPep8Formatter } from './../formatters/autoPep8Formatter'; import { BaseFormatter } from './../formatters/baseFormatter'; +import { BlackFormatter } from './../formatters/blackFormatter'; import { DummyFormatter } from './../formatters/dummyFormatter'; import { YapfFormatter } from './../formatters/yapfFormatter'; @@ -27,8 +28,10 @@ export class PythonFormattingEditProvider implements vscode.DocumentFormattingEd public constructor(context: vscode.ExtensionContext, serviceContainer: IServiceContainer) { const yapfFormatter = new YapfFormatter(serviceContainer); const autoPep8 = new AutoPep8Formatter(serviceContainer); + const black = new BlackFormatter(serviceContainer); const dummy = new DummyFormatter(serviceContainer); this.formatters.set(yapfFormatter.Id, yapfFormatter); + this.formatters.set(black.Id, black); this.formatters.set(autoPep8.Id, autoPep8); this.formatters.set(dummy.Id, dummy); @@ -36,7 +39,7 @@ export class PythonFormattingEditProvider implements vscode.DocumentFormattingEd this.workspace = serviceContainer.get(IWorkspaceService); this.documentManager = serviceContainer.get(IDocumentManager); this.config = serviceContainer.get(IConfigurationService); - this.disposables.push(this.documentManager.onDidSaveTextDocument(async document => await this.onSaveDocument(document))); + this.disposables.push(this.documentManager.onDidSaveTextDocument(async document => this.onSaveDocument(document))); } public dispose() { diff --git a/src/client/telemetry/types.ts b/src/client/telemetry/types.ts index dcb355155f45..818f892b00c4 100644 --- a/src/client/telemetry/types.ts +++ b/src/client/telemetry/types.ts @@ -7,7 +7,7 @@ export type EditorLoadTelemetry = { condaVersion: string; }; export type FormatTelemetry = { - tool: 'autopep8' | 'yapf'; + tool: 'autopep8' | 'black' | 'yapf'; hasCustomArgs: boolean; formatSelection: boolean; }; diff --git a/src/test/format/extension.format.test.ts b/src/test/format/extension.format.test.ts index fb74ecb0e522..f50e678b3886 100644 --- a/src/test/format/extension.format.test.ts +++ b/src/test/format/extension.format.test.ts @@ -1,9 +1,9 @@ import * as fs from 'fs-extra'; import * as path from 'path'; import * as vscode from 'vscode'; -import { CancellationTokenSource, Uri } from 'vscode'; import { IProcessService, IPythonExecutionFactory } from '../../client/common/process/types'; import { AutoPep8Formatter } from '../../client/formatters/autoPep8Formatter'; +import { BlackFormatter } from '../../client/formatters/blackFormatter'; import { YapfFormatter } from '../../client/formatters/yapfFormatter'; import { closeActiveWindows, initialize, initializeTest } from '../initialize'; import { MockProcessService } from '../mocks/proc'; @@ -16,11 +16,12 @@ const workspaceRootPath = path.join(__dirname, '..', '..', '..', 'src', 'test'); const originalUnformattedFile = path.join(formatFilesPath, 'fileToFormat.py'); const autoPep8FileToFormat = path.join(formatFilesPath, 'autoPep8FileToFormat.py'); -const autoPep8FileToAutoFormat = path.join(formatFilesPath, 'autoPep8FileToAutoFormat.py'); +const blackFileToFormat = path.join(formatFilesPath, 'blackFileToFormat.py'); +const blackReferenceFile = path.join(formatFilesPath, 'blackFileReference.py'); const yapfFileToFormat = path.join(formatFilesPath, 'yapfFileToFormat.py'); -const yapfFileToAutoFormat = path.join(formatFilesPath, 'yapfFileToAutoFormat.py'); let formattedYapf = ''; +let formattedBlack = ''; let formattedAutoPep8 = ''; // tslint:disable-next-line:max-func-body-length @@ -30,16 +31,30 @@ suite('Formatting', () => { suiteSetup(async () => { await initialize(); initializeDI(); - [autoPep8FileToFormat, autoPep8FileToAutoFormat, yapfFileToFormat, yapfFileToAutoFormat].forEach(file => { + [autoPep8FileToFormat, blackFileToFormat, blackReferenceFile, yapfFileToFormat].forEach(file => { fs.copySync(originalUnformattedFile, file, { overwrite: true }); }); fs.ensureDirSync(path.dirname(autoPep8FileToFormat)); - const pythonProcess = await ioc.serviceContainer.get(IPythonExecutionFactory).create(Uri.file(workspaceRootPath)); + const pythonProcess = await ioc.serviceContainer.get(IPythonExecutionFactory).create(vscode.Uri.file(workspaceRootPath)); + const py2 = await ioc.getPythonMajorVersion(vscode.Uri.parse(originalUnformattedFile)) === 2; const yapf = pythonProcess.execModule('yapf', [originalUnformattedFile], { cwd: workspaceRootPath }); const autoPep8 = pythonProcess.execModule('autopep8', [originalUnformattedFile], { cwd: workspaceRootPath }); - await Promise.all([yapf, autoPep8]).then(formattedResults => { + const formatters = [yapf, autoPep8]; + // When testing against 3.5 and older, this will break. + if (!py2) { + // Black doesn't support emitting only to stdout; it either works + // through a pipe, emits a diff, or rewrites the file in-place. + // Thus it's easier to let it do its in-place rewrite and then + // read the reference file from there. + const black = pythonProcess.execModule('black', [blackReferenceFile], { cwd: workspaceRootPath }); + formatters.push(black); + } + await Promise.all(formatters).then(formattedResults => { formattedYapf = formattedResults[0].stdout; formattedAutoPep8 = formattedResults[1].stdout; + if (!py2) { + formattedBlack = fs.readFileSync(blackReferenceFile).toString(); + } }); }); setup(async () => { @@ -47,7 +62,7 @@ suite('Formatting', () => { initializeDI(); }); suiteTeardown(async () => { - [autoPep8FileToFormat, autoPep8FileToAutoFormat, yapfFileToFormat, yapfFileToAutoFormat].forEach(file => { + [autoPep8FileToFormat, blackFileToFormat, blackReferenceFile, yapfFileToFormat].forEach(file => { if (fs.existsSync(file)) { fs.unlinkSync(file); } @@ -82,22 +97,30 @@ suite('Formatting', () => { }); } - async function testFormatting(formatter: AutoPep8Formatter | YapfFormatter, formattedContents: string, fileToFormat: string, outputFileName: string) { + async function testFormatting(formatter: AutoPep8Formatter | BlackFormatter | YapfFormatter, formattedContents: string, fileToFormat: string, outputFileName: string) { const textDocument = await vscode.workspace.openTextDocument(fileToFormat); const textEditor = await vscode.window.showTextDocument(textDocument); const options = { insertSpaces: textEditor.options.insertSpaces! as boolean, tabSize: textEditor.options.tabSize! as number }; injectFormatOutput(outputFileName); - const edits = await formatter.formatDocument(textDocument, options, new CancellationTokenSource().token); + const edits = await formatter.formatDocument(textDocument, options, new vscode.CancellationTokenSource().token); await textEditor.edit(editBuilder => { edits.forEach(edit => editBuilder.replace(edit.range, edit.newText)); }); compareFiles(formattedContents, textEditor.document.getText()); } - test('AutoPep8', async () => await testFormatting(new AutoPep8Formatter(ioc.serviceContainer), formattedAutoPep8, autoPep8FileToFormat, 'autopep8.output')); - test('Yapf', async () => await testFormatting(new YapfFormatter(ioc.serviceContainer), formattedYapf, yapfFileToFormat, 'yapf.output')); + test('AutoPep8', async () => testFormatting(new AutoPep8Formatter(ioc.serviceContainer), formattedAutoPep8, autoPep8FileToFormat, 'autopep8.output')); + test('Black', async function() { + if (await ioc.getPythonMajorVersion(vscode.Uri.parse(blackFileToFormat)) === 2) { + // tslint:disable-next-line:no-invalid-this + return this.skip(); + } + + await testFormatting(new BlackFormatter(ioc.serviceContainer), formattedBlack, blackFileToFormat, 'black.output'); + }); + test('Yapf', async () => testFormatting(new YapfFormatter(ioc.serviceContainer), formattedYapf, yapfFileToFormat, 'yapf.output')); test('Yapf on dirty file', async () => { const sourceDir = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'formatting'); @@ -130,7 +153,7 @@ suite('Formatting', () => { const options = { insertSpaces: textEditor.options.insertSpaces! as boolean, tabSize: 1 }; const formatter = new YapfFormatter(ioc.serviceContainer); - const edits = await formatter.formatDocument(textDocument, options, new CancellationTokenSource().token); + const edits = await formatter.formatDocument(textDocument, options, new vscode.CancellationTokenSource().token); await textEditor.edit(editBuilder => { edits.forEach(edit => editBuilder.replace(edit.range, edit.newText)); }); diff --git a/src/test/format/format.helper.test.ts b/src/test/format/format.helper.test.ts index 51afda1a64db..d6c732e3e131 100644 --- a/src/test/format/format.helper.test.ts +++ b/src/test/format/format.helper.test.ts @@ -25,7 +25,7 @@ suite('Formatting - Helper', () => { }); test('Ensure product is set in Execution Info', async () => { - [Product.autopep8, Product.yapf].forEach(formatter => { + [Product.autopep8, Product.black, Product.yapf].forEach(formatter => { const info = formatHelper.getExecutionInfo(formatter, []); assert.equal(info.product, formatter, `Incorrect products for ${formatHelper.translateToId(formatter)}`); }); @@ -34,7 +34,7 @@ suite('Formatting - Helper', () => { test('Ensure executable is set in Execution Info', async () => { const settings = PythonSettings.getInstance(); - [Product.autopep8, Product.yapf].forEach(formatter => { + [Product.autopep8, Product.black, Product.yapf].forEach(formatter => { const info = formatHelper.getExecutionInfo(formatter, []); const names = formatHelper.getSettingsPropertyNames(formatter); const execPath = settings.formatting[names.pathName] as string; @@ -47,7 +47,7 @@ suite('Formatting - Helper', () => { const settings = PythonSettings.getInstance(); const customArgs = ['1', '2', '3']; - [Product.autopep8, Product.yapf].forEach(formatter => { + [Product.autopep8, Product.black, Product.yapf].forEach(formatter => { const names = formatHelper.getSettingsPropertyNames(formatter); const args: string[] = Array.isArray(settings.formatting[names.argsName]) ? settings.formatting[names.argsName] as string[] : []; const expectedArgs = args.concat(customArgs).join(','); @@ -58,7 +58,7 @@ suite('Formatting - Helper', () => { }); test('Ensure correct setting names are returned', async () => { - [Product.autopep8, Product.yapf].forEach(formatter => { + [Product.autopep8, Product.black, Product.yapf].forEach(formatter => { const translatedId = formatHelper.translateToId(formatter)!; const settings = { argsName: `${translatedId}Args` as keyof IFormattingSettings, @@ -72,9 +72,10 @@ suite('Formatting - Helper', () => { test('Ensure translation of ids works', async () => { const formatterMapping = new Map(); formatterMapping.set(Product.autopep8, 'autopep8'); + formatterMapping.set(Product.black, 'black'); formatterMapping.set(Product.yapf, 'yapf'); - [Product.autopep8, Product.yapf].forEach(formatter => { + [Product.autopep8, Product.black, Product.yapf].forEach(formatter => { const translatedId = formatHelper.translateToId(formatter); assert.equal(translatedId, formatterMapping.get(formatter)!, `Incorrect translation for product ${formatHelper.translateToId(formatter)}`); }); @@ -83,6 +84,7 @@ suite('Formatting - Helper', () => { EnumEx.getValues(Product).forEach(product => { const formatterMapping = new Map(); formatterMapping.set(Product.autopep8, 'autopep8'); + formatterMapping.set(Product.black, 'black'); formatterMapping.set(Product.yapf, 'yapf'); if (formatterMapping.has(product)) { return; diff --git a/src/test/pythonFiles/formatting/black.output b/src/test/pythonFiles/formatting/black.output new file mode 100644 index 000000000000..be709f2d720a --- /dev/null +++ b/src/test/pythonFiles/formatting/black.output @@ -0,0 +1,54 @@ +--- src/test/pythonFiles/formatting/fileToFormat.py (original) ++++ src/test/pythonFiles/formatting/fileToFormat.py (formatted) +@@ -1,22 +1,38 @@ +-import math, sys; ++import math, sys ++ + + def example1(): + ####This is a long comment. This should be wrapped to fit within 72 characters. +- some_tuple=( 1,2, 3,'a' ); +- some_variable={'long':'Long code lines should be wrapped within 79 characters.', +- 'other':[math.pi, 100,200,300,9876543210,'This is a long string that goes on'], +- 'more':{'inner':'This whole logical line should be wrapped.',some_tuple:[1, +- 20,300,40000,500000000,60000000000000000]}} ++ some_tuple = (1, 2, 3, "a") ++ some_variable = { ++ "long": "Long code lines should be wrapped within 79 characters.", ++ "other": [ ++ math.pi, 100, 200, 300, 9876543210, "This is a long string that goes on" ++ ], ++ "more": { ++ "inner": "This whole logical line should be wrapped.", ++ some_tuple: [1, 20, 300, 40000, 500000000, 60000000000000000], ++ }, ++ } + return (some_tuple, some_variable) +-def example2(): return {'has_key() is deprecated':True}.has_key({'f':2}.has_key('')); +-class Example3( object ): +- def __init__ ( self, bar ): +- #Comments should have a space after the hash. +- if bar : bar+=1; bar=bar* bar ; return bar +- else: +- some_string = """ ++ ++ ++def example2(): ++ return {"has_key() is deprecated": True}.has_key({"f": 2}.has_key("")) ++ ++ ++class Example3(object): ++ ++ def __init__(self, bar): ++ # Comments should have a space after the hash. ++ if bar: ++ bar += 1 ++ bar = bar * bar ++ return bar ++ else: ++ some_string = """ + Indentation in multiline strings should not be touched. + Only actual code should be reindented. + """ +- return (sys.path, some_string) ++ return (sys.path, some_string) From 487e6d146d4e9f1ed2ab7aa4b8f64c6aa691ed71 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Sat, 5 May 2018 21:11:16 -0700 Subject: [PATCH 219/433] Fix typescript compilation error (#1624) * Fix compiler error * Fixes #1623 --- news/3 Code Health/1623.md | 1 + .../common/application/applicationShell.ts | 53 +++++++++---------- 2 files changed, 27 insertions(+), 27 deletions(-) create mode 100644 news/3 Code Health/1623.md diff --git a/news/3 Code Health/1623.md b/news/3 Code Health/1623.md new file mode 100644 index 000000000000..5e3a73bb56ef --- /dev/null +++ b/news/3 Code Health/1623.md @@ -0,0 +1 @@ +Fix typescript compilation error. diff --git a/src/client/common/application/applicationShell.ts b/src/client/common/application/applicationShell.ts index ac3330651b55..e956becd2204 100644 --- a/src/client/common/application/applicationShell.ts +++ b/src/client/common/application/applicationShell.ts @@ -6,50 +6,49 @@ const opn = require('opn'); import { injectable } from 'inversify'; -import * as vscode from 'vscode'; -import { Disposable, StatusBarAlignment, StatusBarItem, WorkspaceFolder, WorkspaceFolderPickOptions } from 'vscode'; +import { CancellationToken, Disposable, InputBoxOptions, MessageItem, MessageOptions, OpenDialogOptions, QuickPickItem, QuickPickOptions, SaveDialogOptions, StatusBarAlignment, StatusBarItem, Uri, window, WorkspaceFolder, WorkspaceFolderPickOptions } from 'vscode'; import { IApplicationShell } from './types'; @injectable() export class ApplicationShell implements IApplicationShell { public showInformationMessage(message: string, ...items: string[]): Thenable; - public showInformationMessage(message: string, options: vscode.MessageOptions, ...items: string[]): Thenable; - public showInformationMessage(message: string, ...items: T[]): Thenable; - public showInformationMessage(message: string, options: vscode.MessageOptions, ...items: T[]): Thenable; + public showInformationMessage(message: string, options: MessageOptions, ...items: string[]): Thenable; + public showInformationMessage(message: string, ...items: T[]): Thenable; + public showInformationMessage(message: string, options: MessageOptions, ...items: T[]): Thenable; public showInformationMessage(message: string, options?: any, ...items: any[]): Thenable { - return vscode.window.showInformationMessage(message, options, ...items); + return window.showInformationMessage(message, options, ...items); } public showWarningMessage(message: string, ...items: string[]): Thenable; - public showWarningMessage(message: string, options: vscode.MessageOptions, ...items: string[]): Thenable; - public showWarningMessage(message: string, ...items: T[]): Thenable; - public showWarningMessage(message: string, options: vscode.MessageOptions, ...items: T[]): Thenable; + public showWarningMessage(message: string, options: MessageOptions, ...items: string[]): Thenable; + public showWarningMessage(message: string, ...items: T[]): Thenable; + public showWarningMessage(message: string, options: MessageOptions, ...items: T[]): Thenable; public showWarningMessage(message: any, options?: any, ...items: any[]) { - return vscode.window.showWarningMessage(message, options, ...items); + return window.showWarningMessage(message, options, ...items); } public showErrorMessage(message: string, ...items: string[]): Thenable; - public showErrorMessage(message: string, options: vscode.MessageOptions, ...items: string[]): Thenable; - public showErrorMessage(message: string, ...items: T[]): Thenable; - public showErrorMessage(message: string, options: vscode.MessageOptions, ...items: T[]): Thenable; + public showErrorMessage(message: string, options: MessageOptions, ...items: string[]): Thenable; + public showErrorMessage(message: string, ...items: T[]): Thenable; + public showErrorMessage(message: string, options: MessageOptions, ...items: T[]): Thenable; public showErrorMessage(message: any, options?: any, ...items: any[]) { - return vscode.window.showErrorMessage(message, options, ...items); + return window.showErrorMessage(message, options, ...items); } - public showQuickPick(items: string[] | Thenable, options?: vscode.QuickPickOptions, token?: vscode.CancellationToken): Thenable; - public showQuickPick(items: T[] | Thenable, options?: vscode.QuickPickOptions, token?: vscode.CancellationToken): Thenable; - public showQuickPick(items: any, options?: any, token?: any) { - return vscode.window.showQuickPick(items, options, token); + public showQuickPick(items: string[] | Thenable, options?: QuickPickOptions, token?: CancellationToken): Thenable; + public showQuickPick(items: T[] | Thenable, options?: QuickPickOptions, token?: CancellationToken): Thenable; + public showQuickPick(items: any, options?: any, token?: any): Thenable { + return window.showQuickPick(items, options, token); } - public showOpenDialog(options: vscode.OpenDialogOptions): Thenable { - return vscode.window.showOpenDialog(options); + public showOpenDialog(options: OpenDialogOptions): Thenable { + return window.showOpenDialog(options); } - public showSaveDialog(options: vscode.SaveDialogOptions): Thenable { - return vscode.window.showSaveDialog(options); + public showSaveDialog(options: SaveDialogOptions): Thenable { + return window.showSaveDialog(options); } - public showInputBox(options?: vscode.InputBoxOptions, token?: vscode.CancellationToken): Thenable { - return vscode.window.showInputBox(options, token); + public showInputBox(options?: InputBoxOptions, token?: CancellationToken): Thenable { + return window.showInputBox(options, token); } public openUrl(url: string): void { opn(url); @@ -59,14 +58,14 @@ export class ApplicationShell implements IApplicationShell { public setStatusBarMessage(text: string, hideWhenDone: Thenable): Disposable; public setStatusBarMessage(text: string): Disposable; public setStatusBarMessage(text: string, arg?: any): Disposable { - return vscode.window.setStatusBarMessage(text, arg); + return window.setStatusBarMessage(text, arg); } public createStatusBarItem(alignment?: StatusBarAlignment, priority?: number): StatusBarItem { - return vscode.window.createStatusBarItem(alignment, priority); + return window.createStatusBarItem(alignment, priority); } public showWorkspaceFolderPick(options?: WorkspaceFolderPickOptions): Thenable { - return vscode.window.showWorkspaceFolderPick(options); + return window.showWorkspaceFolderPick(options); } } From 0313f09d5a8c0bb2dc515fec653ff5c507ff14fc Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 7 May 2018 11:06:30 -0700 Subject: [PATCH 220/433] News entry for #180 --- news/2 Fixes/180.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/2 Fixes/180.md diff --git a/news/2 Fixes/180.md b/news/2 Fixes/180.md new file mode 100644 index 000000000000..60af4c971641 --- /dev/null +++ b/news/2 Fixes/180.md @@ -0,0 +1 @@ +`Go to Definition` now works for functions which have numbers that use `_` as a separator (as part of our Jedi 0.12.0 upgrade). From 6bc40da25a9f8a75a9446d1cec7be5a461efd474 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 7 May 2018 11:40:39 -0700 Subject: [PATCH 221/433] Add missing news entry for #677 (#1601) --- news/2 Fixes/677.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/2 Fixes/677.md diff --git a/news/2 Fixes/677.md b/news/2 Fixes/677.md new file mode 100644 index 000000000000..8c55a40a7539 --- /dev/null +++ b/news/2 Fixes/677.md @@ -0,0 +1 @@ +Ensure empty paths do not get added into `sys.path` by the Jedi language server. (this was fixed in the previous release in [#1471](https://github.com/Microsoft/vscode-python/pull/1471)) From ded47030fe0c378fea61dc20c2fe1c8a72b60cde Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 7 May 2018 11:50:52 -0700 Subject: [PATCH 222/433] Remove unused Unit Test setting `debugHost` (#1573) Fixes #1552 --- news/3 Code Health/1552.md | 1 + package.json | 6 ------ src/client/common/types.ts | 1 - 3 files changed, 1 insertion(+), 7 deletions(-) create mode 100644 news/3 Code Health/1552.md diff --git a/news/3 Code Health/1552.md b/news/3 Code Health/1552.md new file mode 100644 index 000000000000..afad15c40dd3 --- /dev/null +++ b/news/3 Code Health/1552.md @@ -0,0 +1 @@ +Remove unused Unit Test setting `debugHost`. diff --git a/package.json b/package.json index 96dc0a2eb896..ef8d06556dd0 100644 --- a/package.json +++ b/package.json @@ -1632,12 +1632,6 @@ "description": "Optional working directory for unit tests.", "scope": "resource" }, - "python.unitTest.debugHost": { - "type": "number", - "default": "localhost", - "description": "IP Address of the of the local unit test server (default is localhost or use 127.0.0.1).", - "scope": "resource" - }, "python.unitTest.debugPort": { "type": "number", "default": 3000, diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 35d77cae9846..ec85e8d0f4e0 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -123,7 +123,6 @@ export interface ISortImportSettings { export interface IUnitTestSettings { readonly promptToConfigure: boolean; readonly debugPort: number; - readonly debugHost?: string; readonly nosetestsEnabled: boolean; nosetestPath: string; nosetestArgs: string[]; From 8a576d1fc02be5027c71683d701a5992f582ea09 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 7 May 2018 11:51:02 -0700 Subject: [PATCH 223/433] Remove unused setting `disablePromptForFeatures` (#1574) Fixes #1551 --- news/3 Code Health/1551.md | 1 + package.json | 20 -------------------- src/client/common/configSettings.ts | 4 ---- src/client/common/types.ts | 1 - 4 files changed, 1 insertion(+), 25 deletions(-) create mode 100644 news/3 Code Health/1551.md diff --git a/news/3 Code Health/1551.md b/news/3 Code Health/1551.md new file mode 100644 index 000000000000..22d188323ffb --- /dev/null +++ b/news/3 Code Health/1551.md @@ -0,0 +1 @@ +Remove unused setting `disablePromptForFeatures`. diff --git a/package.json b/package.json index ef8d06556dd0..3ef35bf1e9cd 100644 --- a/package.json +++ b/package.json @@ -1156,26 +1156,6 @@ "description": "Whether to check if Python is installed (also warn when using the macOS-installed Python).", "scope": "resource" }, - "python.disablePromptForFeatures": { - "type": "array", - "default": [], - "description": "Do not display a prompt to install these features", - "items": { - "type": "string", - "default": "pylint", - "description": "Feature", - "enum": [ - "flake8", - "mypy", - "pep8", - "pylama", - "prospector", - "pydocstyle", - "pylint" - ] - }, - "scope": "resource" - }, "python.envFile": { "type": "string", "description": "Absolute path to a file containing environment variable definitions.", diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index 86d8ad57a218..e040313e90ff 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -29,7 +29,6 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { public jediPath = ''; public jediMemoryLimit = 1024; public envFile = ''; - public disablePromptForFeatures: string[] = []; public venvPath = ''; public venvFolders: string[] = []; public devOptions: string[] = []; @@ -136,9 +135,6 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion const lintingSettings = systemVariables.resolveAny(pythonSettings.get('linting'))!; - // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion - this.disablePromptForFeatures = pythonSettings.get('disablePromptForFeatures')!; - this.disablePromptForFeatures = Array.isArray(this.disablePromptForFeatures) ? this.disablePromptForFeatures : []; if (this.linting) { Object.assign(this.linting, lintingSettings); } else { diff --git a/src/client/common/types.ts b/src/client/common/types.ts index ec85e8d0f4e0..fabac04aa35d 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -111,7 +111,6 @@ export interface IPythonSettings { readonly sortImports?: ISortImportSettings; readonly workspaceSymbols: IWorkspaceSymbolSettings; readonly envFile: string; - readonly disablePromptForFeatures: string[]; readonly disableInstallationChecks: boolean; readonly globalModuleInstallation: boolean; } From ba728f8b046963fedf2442bc18240b1c5733edf4 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 7 May 2018 11:51:12 -0700 Subject: [PATCH 224/433] Auto detect jinja and j2 files as jinja templates (#1575) Fixes #1484 --- news/1 Enhancements/1484.md | 1 + package.json | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 news/1 Enhancements/1484.md diff --git a/news/1 Enhancements/1484.md b/news/1 Enhancements/1484.md new file mode 100644 index 000000000000..2ed518b6abf1 --- /dev/null +++ b/news/1 Enhancements/1484.md @@ -0,0 +1 @@ +Auto detect `*.jinja2` and `*.j2` extensions as Jinja templates, to enable debugging of Jinja templates. diff --git a/package.json b/package.json index 3ef35bf1e9cd..3c883e11613e 100644 --- a/package.json +++ b/package.json @@ -747,7 +747,8 @@ "enableBreakpointsFor": { "languageIds": [ "python", - "html" + "html", + "jinja" ] }, "aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217", @@ -1747,7 +1748,6 @@ "description": "Whether to re-build the tags file on start (defaults to true).", "scope": "resource" }, - "python.workspaceSymbols.tagFilePath": { "type": "string", "default": "${workspaceFolder}/.vscode/tags", @@ -1780,6 +1780,16 @@ "filenames": [ ".condarc" ] + }, + { + "id": "jinja", + "extensions": [ + ".jinja2", + ".j2" + ], + "aliases": [ + "Jinja" + ] } ], "grammars": [ From 3a5c0a8891bc0f9c178b253d34800a882732d5d7 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 7 May 2018 11:51:25 -0700 Subject: [PATCH 225/433] Remove explicit initialization of PYTHONPATH with workspace for dbg test (#1576) * Remove explicit initialization of PYTHONPATH with workspace for dbg test * code review fixes * fix code review comments * Fixes #1465 --- news/3 Code Health/1465.md | 1 + src/test/debugger/module.test.ts | 10 +++------- 2 files changed, 4 insertions(+), 7 deletions(-) create mode 100644 news/3 Code Health/1465.md diff --git a/news/3 Code Health/1465.md b/news/3 Code Health/1465.md new file mode 100644 index 000000000000..461311cddd3a --- /dev/null +++ b/news/3 Code Health/1465.md @@ -0,0 +1 @@ +Remove explicit initialization of PYTHONPATH with the current workspace path in unit testing of modules with the experimental debugger. diff --git a/src/test/debugger/module.test.ts b/src/test/debugger/module.test.ts index 1e51b5e2b030..2e6839bdc9ef 100644 --- a/src/test/debugger/module.test.ts +++ b/src/test/debugger/module.test.ts @@ -35,11 +35,7 @@ suite(`Module Debugging - Misc tests: ${debuggerType}`, () => { } catch (ex) { } await sleep(1000); }); - function buildLauncArgs(): LaunchRequestArguments { - const env = {}; - // tslint:disable-next-line:no-string-literal - env['PYTHONPATH'] = `.${path.delimiter}${PTVSD_PATH}`; - + function buildLaunchArgs(): LaunchRequestArguments { // tslint:disable-next-line:no-unnecessary-local-variable const options: LaunchRequestArguments = { module: 'mymod', @@ -48,7 +44,7 @@ suite(`Module Debugging - Misc tests: ${debuggerType}`, () => { debugOptions: [DebugOptions.RedirectOutput], pythonPath: PYTHON_PATH, args: [], - env, + env: { PYTHONPATH: `${PTVSD_PATH}` }, envFile: '', logToFile: false, type: debuggerType @@ -60,7 +56,7 @@ suite(`Module Debugging - Misc tests: ${debuggerType}`, () => { test('Test stdout output', async () => { await Promise.all([ debugClient.configurationSequence(), - debugClient.launch(buildLauncArgs()), + debugClient.launch(buildLaunchArgs()), debugClient.waitForEvent('initialized'), debugClient.assertOutput('stdout', 'Hello world!'), debugClient.waitForEvent('exited'), From 02c19129da053627c4de06a7ee5e5a25de53de68 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 7 May 2018 11:51:35 -0700 Subject: [PATCH 226/433] Flag program in launch.json as an optional property (#1578) Fixes #1503 Also fix a few other linter warnings/errors. --- news/3 Code Health/1503.md | 1 + src/client/debugger/Common/Contracts.ts | 2 +- src/client/debugger/Main.ts | 4 ++-- src/client/workspaceSymbols/generator.ts | 4 ++-- 4 files changed, 6 insertions(+), 5 deletions(-) create mode 100644 news/3 Code Health/1503.md diff --git a/news/3 Code Health/1503.md b/news/3 Code Health/1503.md new file mode 100644 index 000000000000..6d81d673e007 --- /dev/null +++ b/news/3 Code Health/1503.md @@ -0,0 +1 @@ +Flag `program` in `launch.json` configuration items as an optional attribute. diff --git a/src/client/debugger/Common/Contracts.ts b/src/client/debugger/Common/Contracts.ts index d56e190355de..6806986feca6 100644 --- a/src/client/debugger/Common/Contracts.ts +++ b/src/client/debugger/Common/Contracts.ts @@ -78,7 +78,7 @@ export interface BaseLaunchRequestArguments extends DebugProtocol.LaunchRequestA type?: DebuggerType; /** An absolute path to the program to debug. */ module?: string; - program: string; + program?: string; pythonPath: string; /** Automatically stop target after launch. If not specified, target does not stop. */ stopOnEntry?: boolean; diff --git a/src/client/debugger/Main.ts b/src/client/debugger/Main.ts index 115f7c948785..c964ce47c2df 100644 --- a/src/client/debugger/Main.ts +++ b/src/client/debugger/Main.ts @@ -92,7 +92,7 @@ export class PythonDebugger extends LoggingDebugSession { private startDebugServer(): Promise { let programDirectory = ''; if ((this.launchArgs && this.launchArgs.program) || (this.attachArgs && this.attachArgs.localRoot)) { - programDirectory = this.launchArgs ? path.dirname(this.launchArgs.program) : this.attachArgs.localRoot; + programDirectory = (this.launchArgs && this.launchArgs.program) ? path.dirname(this.launchArgs.program) : this.attachArgs.localRoot; } if (this.launchArgs && typeof this.launchArgs.cwd === 'string' && this.launchArgs.cwd.length > 0 && this.launchArgs.cwd !== 'null') { programDirectory = this.launchArgs.cwd; @@ -227,7 +227,7 @@ export class PythonDebugger extends LoggingDebugSession { } // Confirm the file exists if (typeof args.module !== 'string' || args.module.length === 0) { - if (!fs.existsSync(args.program)) { + if (!args.program || !fs.existsSync(args.program)) { return this.sendErrorResponse(response, 2001, `File does not exist. "${args.program}"`); } } diff --git a/src/client/workspaceSymbols/generator.ts b/src/client/workspaceSymbols/generator.ts index 441605860357..9d4a6458374b 100644 --- a/src/client/workspaceSymbols/generator.ts +++ b/src/client/workspaceSymbols/generator.ts @@ -31,7 +31,7 @@ export class Generator implements vscode.Disposable { if (!this.pythonSettings.workspaceSymbols.enabled) { return; } - return await this.generateTags({ directory: this.workspaceFolder.fsPath }); + return this.generateTags({ directory: this.workspaceFolder.fsPath }); } private buildCmdArgs(): string[] { const exclusions = this.pythonSettings.workspaceSymbols.exclusionPatterns; @@ -40,7 +40,7 @@ export class Generator implements vscode.Disposable { return [`--options=${this.optionsFile}`, '--languages=Python'].concat(excludes); } @captureTelemetry(WORKSPACE_SYMBOLS_BUILD) - private generateTags(source: { directory?: string, file?: string }): Promise { + private generateTags(source: { directory?: string; file?: string }): Promise { const tagFile = path.normalize(this.pythonSettings.workspaceSymbols.tagFilePath); const cmd = this.pythonSettings.workspaceSymbols.ctagsPath; const args = this.buildCmdArgs(); From 2295b584035083f2b93413aee1409beb1448ad6b Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 7 May 2018 11:51:45 -0700 Subject: [PATCH 227/433] Unit tests for evaluating expressions in the experimental debugger (#1579) Fixes #1109 --- news/3 Code Health/1109.md | 1 + src/test/debugger/misc.test.ts | 29 +++++++++++++++++++ .../debugging/sample2WithoutSleep.py | 13 +++++++++ 3 files changed, 43 insertions(+) create mode 100644 news/3 Code Health/1109.md create mode 100644 src/test/pythonFiles/debugging/sample2WithoutSleep.py diff --git a/news/3 Code Health/1109.md b/news/3 Code Health/1109.md new file mode 100644 index 000000000000..4dd7d9934d18 --- /dev/null +++ b/news/3 Code Health/1109.md @@ -0,0 +1 @@ +Add unit tests for evaluating expressions in the experimental debugger. diff --git a/src/test/debugger/misc.test.ts b/src/test/debugger/misc.test.ts index 995be0db817a..58fe2676ebc4 100644 --- a/src/test/debugger/misc.test.ts +++ b/src/test/debugger/misc.test.ts @@ -19,6 +19,7 @@ import { PYTHON_PATH, sleep } from '../common'; import { IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; import { DEBUGGER_TIMEOUT } from './common/constants'; import { DebugClientEx } from './debugClient'; +import { continueDebugging } from './utils'; const isProcessRunning = require('is-running') as (number) => boolean; @@ -539,5 +540,33 @@ let testCounter = 0; expect(stackframes.body.stackFrames[2].line).to.be.equal(10); expect(fileSystem.arePathsSame(stackframes.body.stackFrames[2].source!.path!, pythonFile)).to.be.equal(true, 'paths do not match'); }); + test('Test Evaluation of Expressions', async function () { + if (debuggerType !== 'pythonExperimental') { + return this.skip(); + } + + const breakpointLocation = { path: path.join(debugFilesPath, 'sample2WithoutSleep.py'), column: 1, line: 5 }; + const breakpointArgs = { + lines: [breakpointLocation.line], + breakpoints: [{ line: breakpointLocation.line, column: breakpointLocation.column }], + source: { path: breakpointLocation.path } + }; + await Promise.all([ + debugClient.launch(buildLauncArgs('sample2WithoutSleep.py', false)), + debugClient.waitForEvent('initialized') + .then(() => debugClient.setBreakpointsRequest(breakpointArgs)) + .then(() => debugClient.configurationDoneRequest()) + .then(() => debugClient.threadsRequest()), + debugClient.waitForEvent('thread'), + debugClient.assertStoppedLocation('breakpoint', breakpointLocation) + ]); + + //Do not remove this, this is required to ensure PTVSD is ready to accept other requests. + await debugClient.threadsRequest(); + const evaluateResponse = await debugClient.evaluateRequest({ context: 'repl', expression: 'a+b+2', frameId: 1 }); + expect(evaluateResponse.body.type).to.equal('int'); + expect(evaluateResponse.body.result).to.equal('5'); + await continueDebugging(debugClient); + }); }); }); diff --git a/src/test/pythonFiles/debugging/sample2WithoutSleep.py b/src/test/pythonFiles/debugging/sample2WithoutSleep.py new file mode 100644 index 000000000000..88b11216bc70 --- /dev/null +++ b/src/test/pythonFiles/debugging/sample2WithoutSleep.py @@ -0,0 +1,13 @@ +import time +# time.sleep(3) +a = 1 +b = 2 +print(a + b) + +def do_something(name): + print("inside") + print(name) + +do_something("Do that") + +print("hello world") From 78dc4ca93fe237fb3bda2cdf8ce3a6b1a75ed3eb Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 7 May 2018 11:52:22 -0700 Subject: [PATCH 228/433] Allow test failures on CI against release version of PTVSD (#1594) * Allow test failures * Allow tests to fail with release version of PTVSD --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 2c4a073a7db8..d4f9b409925e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,10 +30,10 @@ matrix: allow_failures: - os: linux python: "2.7" - env: DEBUGGER_TEST=true + env: DEBUGGER_TEST_RELEASE=true - os: linux python: "3.6-dev" - env: DEBUGGER_TEST=true + env: DEBUGGER_TEST_RELEASE=true before_install: | if [ $TRAVIS_OS_NAME == "linux" ]; then export CXX="g++-4.9" CC="gcc-4.9" DISPLAY=:99.0; From db0ef2e5237e686c2b0220f33ff08aa53fe36f55 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 7 May 2018 11:52:36 -0700 Subject: [PATCH 229/433] Close communication channel before exiting the test runner (#1597) * Close communication channel before exiting the test runner * revert change * Fixes #1529 --- news/2 Fixes/1529.md | 1 + .../PythonTools/visualstudio_py_testlauncher.py | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 news/2 Fixes/1529.md diff --git a/news/2 Fixes/1529.md b/news/2 Fixes/1529.md new file mode 100644 index 000000000000..fdb8051aac0a --- /dev/null +++ b/news/2 Fixes/1529.md @@ -0,0 +1 @@ +Close communication channel before exiting the test runner. diff --git a/pythonFiles/PythonTools/visualstudio_py_testlauncher.py b/pythonFiles/PythonTools/visualstudio_py_testlauncher.py index 9464afddc6f1..7ed86ecaa320 100644 --- a/pythonFiles/PythonTools/visualstudio_py_testlauncher.py +++ b/pythonFiles/PythonTools/visualstudio_py_testlauncher.py @@ -102,12 +102,20 @@ def __init__(self, socket, callback): self.seq = 0 self.callback = callback self.lock = thread.allocate_lock() + self._closed = False # start the testing reader thread loop self.test_thread_id = thread.start_new_thread(self.readSocket, ()) + def close(self): + self._closed = True + def readSocket(self): - data = self.socket.recv(1024) - self.callback() + try: + data = self.socket.recv(1024) + self.callback() + except OSError: + if not self._closed: + raise def receive(self): pass @@ -312,6 +320,8 @@ def main(): else: runner = unittest.TextTestRunner(verbosity=opts.uvInt, resultclass=VsTestResult) result = runner.run(tests) + if _channel is not None: + _channel.close() sys.exit(not result.wasSuccessful()) finally: if cov is not None: From f788b43e41eafc7ed880e0b5efd46ae47b445fc9 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 7 May 2018 11:52:45 -0700 Subject: [PATCH 230/433] Add the command 'Discover Unit Tests' (#1598) Fixes #1474 --- news/1 Enhancements/1474.md | 1 + package.json | 8 +++++++- package.nls.json | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 news/1 Enhancements/1474.md diff --git a/news/1 Enhancements/1474.md b/news/1 Enhancements/1474.md new file mode 100644 index 000000000000..a439ceb3d20a --- /dev/null +++ b/news/1 Enhancements/1474.md @@ -0,0 +1 @@ +Add the command 'Discover Unit Tests'. diff --git a/package.json b/package.json index 3c883e11613e..6fceac114e85 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,8 @@ "onCommand:python.goToPythonObject", "onCommand:python.setLinter", "onCommand:python.enableLinting", - "onCommand:python.createTerminal" + "onCommand:python.createTerminal", + "onCommand:python.discoverTests" ], "main": "./out/client/extension", "contributes": { @@ -187,6 +188,11 @@ "title": "%python.command.python.runFailedTests.title%", "category": "Python" }, + { + "command": "python.discoverTests", + "title": "%python.command.python.discoverTests.title%", + "category": "Python" + }, { "command": "python.execSelectionInTerminal", "title": "%python.command.python.execSelectionInTerminal.title%", diff --git a/package.nls.json b/package.nls.json index f1fd181184ab..e951092e3762 100644 --- a/package.nls.json +++ b/package.nls.json @@ -16,6 +16,7 @@ "python.command.python.selectAndRunTestFile.title": "Run Unit Test File ...", "python.command.python.runCurrentTestFile.title": "Run Current Unit Test File", "python.command.python.runFailedTests.title": "Run Failed Unit Tests", + "python.command.python.discoverTests.title": "Discover Unit Tests", "python.command.python.execSelectionInTerminal.title": "Run Selection/Line in Python Terminal", "python.command.python.execSelectionInDjangoShell.title": "Run Selection/Line in Django Shell", "python.command.python.goToPythonObject.title": "Go to Python Object", From 67fbef7972b15026023882c6ad3452966673b582 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 7 May 2018 11:52:54 -0700 Subject: [PATCH 231/433] Ensure debugger breaks on `assert` failures (#1599) Fixes #1194 --- news/2 Fixes/1194.md | 1 + .../debugger/PythonProcessCallbackHandler.ts | 2 +- src/test/debugger/misc.test.ts | 36 +++++++++++++++++++ .../debugging/sampleWithAssertEx.py | 1 + 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 news/2 Fixes/1194.md create mode 100644 src/test/pythonFiles/debugging/sampleWithAssertEx.py diff --git a/news/2 Fixes/1194.md b/news/2 Fixes/1194.md new file mode 100644 index 000000000000..8301b2c8d5dd --- /dev/null +++ b/news/2 Fixes/1194.md @@ -0,0 +1 @@ +Ensure debugger breaks on `assert` failures. diff --git a/src/client/debugger/PythonProcessCallbackHandler.ts b/src/client/debugger/PythonProcessCallbackHandler.ts index 2b18c62ee804..da5d32359577 100644 --- a/src/client/debugger/PythonProcessCallbackHandler.ts +++ b/src/client/debugger/PythonProcessCallbackHandler.ts @@ -259,7 +259,7 @@ export class PythonProcessCallbackHandler extends EventEmitter { return; } - if (typeName && desc) { + if (typeName || desc) { let ex: IPythonException = { TypeName: typeName, Description: desc diff --git a/src/test/debugger/misc.test.ts b/src/test/debugger/misc.test.ts index 58fe2676ebc4..7d1dc3e23f33 100644 --- a/src/test/debugger/misc.test.ts +++ b/src/test/debugger/misc.test.ts @@ -440,6 +440,42 @@ let testCounter = 0; const pauseLocation = { path: path.join(debugFilesPath, 'sample3WithEx.py'), line: 5 }; await debugClient.assertStoppedLocation('exception', pauseLocation); }); + test('Test pausing on assert failures', async () => { + const pauseLocation = { path: path.join(debugFilesPath, 'sampleWithAssertEx.py'), line: 1 }; + + function waitToStopDueToException() { + return new Promise((resolve, reject) => { + debugClient.once('stopped', (event: DebugProtocol.StoppedEvent) => { + if (event.body.reason === 'exception' && + event.body.text && event.body.text!.startsWith('AssertionError')) { + resolve(); + } else { + reject(new Error('Stopped for some other reason')); + } + }); + setTimeout(() => { + reject(new Error(`waitToStopDueToException not received after ${debugClient.defaultTimeout} ms`)); + }, debugClient.defaultTimeout); + }); + } + + function setBreakpointFilter(): Promise { + if (debuggerType === 'python') { + return Promise.resolve(); + } else { + return debugClient.waitForEvent('initialized') + .then(() => debugClient.setExceptionBreakpointsRequest({ filters: ['uncaught'] })) + .then(() => debugClient.configurationDoneRequest()); + } + } + await Promise.all([ + debugClient.configurationSequence(), + setBreakpointFilter(), + debugClient.launch(buildLauncArgs('sampleWithAssertEx.py', false)), + waitToStopDueToException(), + debugClient.assertStoppedLocation('exception', pauseLocation) + ]); + }); test('Test multi-threaded debugging', async function () { if (debuggerType !== 'python') { // See GitHub issue #1250 diff --git a/src/test/pythonFiles/debugging/sampleWithAssertEx.py b/src/test/pythonFiles/debugging/sampleWithAssertEx.py new file mode 100644 index 000000000000..2cfffa40db4b --- /dev/null +++ b/src/test/pythonFiles/debugging/sampleWithAssertEx.py @@ -0,0 +1 @@ +assert False From fbec259975c051406dd4e1af7d79e36ade1b3df5 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 7 May 2018 11:56:45 -0700 Subject: [PATCH 232/433] Tests to ensure custom args get passed into exp debugger (#1581) --- news/3 Code Health/1280.md | 1 + src/test/debugger/misc.test.ts | 62 +++++++++++-------- .../pythonFiles/debugging/printSysArgv.py | 4 ++ 3 files changed, 40 insertions(+), 27 deletions(-) create mode 100644 news/3 Code Health/1280.md create mode 100644 src/test/pythonFiles/debugging/printSysArgv.py diff --git a/news/3 Code Health/1280.md b/news/3 Code Health/1280.md new file mode 100644 index 000000000000..813fd9b8c69d --- /dev/null +++ b/news/3 Code Health/1280.md @@ -0,0 +1 @@ +Add tests to ensure custom arguments get passed into python program when using the experimental debugger. diff --git a/src/test/debugger/misc.test.ts b/src/test/debugger/misc.test.ts index 7d1dc3e23f33..7cf1c44ec7af 100644 --- a/src/test/debugger/misc.test.ts +++ b/src/test/debugger/misc.test.ts @@ -67,12 +67,8 @@ let testCounter = 0; return new DebugClientEx(testAdapterFilePath, debuggerType, coverageDirectory, { cwd: EXTENSION_ROOT_DIR }); } } - function buildLauncArgs(pythonFile: string, stopOnEntry: boolean = false): LaunchRequestArguments { - const env = {}; - if (debuggerType === 'pythonExperimental') { - // tslint:disable-next-line:no-string-literal - env['PYTHONPATH'] = PTVSD_PATH; - } + function buildLaunchArgs(pythonFile: string, stopOnEntry: boolean = false): LaunchRequestArguments { + const env = debuggerType === 'pythonExperimental' ? { PYTHONPATH: PTVSD_PATH } : {}; // tslint:disable-next-line:no-unnecessary-local-variable const options: LaunchRequestArguments = { program: path.join(debugFilesPath, pythonFile), @@ -93,7 +89,7 @@ let testCounter = 0; test('Should run program to the end', async () => { await Promise.all([ debugClient.configurationSequence(), - debugClient.launch(buildLauncArgs('simplePrint.py', false)), + debugClient.launch(buildLaunchArgs('simplePrint.py', false)), debugClient.waitForEvent('initialized'), debugClient.waitForEvent('terminated') ]); @@ -104,7 +100,7 @@ let testCounter = 0; } await Promise.all([ debugClient.configurationSequence(), - debugClient.launch(buildLauncArgs('simplePrint.py', true)), + debugClient.launch(buildLaunchArgs('simplePrint.py', true)), debugClient.waitForEvent('initialized'), debugClient.waitForEvent('stopped') ]); @@ -113,7 +109,7 @@ let testCounter = 0; const output = debuggerType === 'python' ? 'stdout' : 'stderr'; await Promise.all([ debugClient.configurationSequence(), - debugClient.launch(buildLauncArgs('stdErrOutput.py', false)), + debugClient.launch(buildLaunchArgs('stdErrOutput.py', false)), debugClient.waitForEvent('initialized'), //TODO: ptvsd does not differentiate. debugClient.assertOutput(output, 'error output'), @@ -123,7 +119,7 @@ let testCounter = 0; test('Test stdout output', async () => { await Promise.all([ debugClient.configurationSequence(), - debugClient.launch(buildLauncArgs('stdOutOutput.py', false)), + debugClient.launch(buildLaunchArgs('stdOutOutput.py', false)), debugClient.waitForEvent('initialized'), debugClient.assertOutput('stdout', 'normal output'), debugClient.waitForEvent('terminated') @@ -137,7 +133,7 @@ let testCounter = 0; await Promise.all([ debugClient.configurationSequence(), - debugClient.launch(buildLauncArgs('simplePrint.py', true)), + debugClient.launch(buildLaunchArgs('simplePrint.py', true)), debugClient.waitForEvent('initialized'), debugClient.waitForEvent('stopped') ]); @@ -156,7 +152,7 @@ let testCounter = 0; await Promise.all([ debugClient.configurationSequence(), - debugClient.launch(buildLauncArgs('simplePrint.py', true)), + debugClient.launch(buildLaunchArgs('simplePrint.py', true)), debugClient.waitForEvent('initialized'), debugClient.waitForEvent('stopped') ]); @@ -169,7 +165,7 @@ let testCounter = 0; ]); }); test('Should break at print statement (line 3)', async () => { - const launchArgs = buildLauncArgs('sample2.py', false); + const launchArgs = buildLaunchArgs('sample2.py', false); const breakpointLocation = { path: path.join(debugFilesPath, 'sample2.py'), column: 1, line: 5 }; await debugClient.hitBreakpoint(launchArgs, breakpointLocation); }); @@ -177,7 +173,7 @@ let testCounter = 0; if (debuggerType === 'python') { return this.skip(); } - const launchArgs = buildLauncArgs('sample2.py', false); + const launchArgs = buildLaunchArgs('sample2.py', false); const breakpointLocation = { path: path.join(debugFilesPath, 'sample2.py'), column: 1, line: 5 }; const processPromise = debugClient.waitForEvent('process') as Promise; await debugClient.hitBreakpoint(launchArgs, breakpointLocation); @@ -196,7 +192,7 @@ let testCounter = 0; await Promise.all([ debugClient.configurationSequence(), - debugClient.launch(buildLauncArgs('forever.py', false)), + debugClient.launch(buildLaunchArgs('forever.py', false)), debugClient.waitForEvent('initialized') ]); @@ -226,7 +222,7 @@ let testCounter = 0; const threadIdPromise = debugClient.waitForEvent('thread'); await Promise.all([ debugClient.configurationSequence(), - debugClient.launch(buildLauncArgs('sample2.py', false)), + debugClient.launch(buildLaunchArgs('sample2.py', false)), debugClient.waitForEvent('initialized') ]); @@ -263,7 +259,7 @@ let testCounter = 0; test('Test editing variables', async () => { await Promise.all([ debugClient.configurationSequence(), - debugClient.launch(buildLauncArgs('sample2.py', false)), + debugClient.launch(buildLaunchArgs('sample2.py', false)), debugClient.waitForEvent('initialized') ]); @@ -297,7 +293,7 @@ let testCounter = 0; await Promise.all([ debugClient.configurationSequence(), - debugClient.launch(buildLauncArgs('sample2.py', false)), + debugClient.launch(buildLaunchArgs('sample2.py', false)), debugClient.waitForEvent('initialized') ]); @@ -323,7 +319,7 @@ let testCounter = 0; await Promise.all([ debugClient.configurationSequence(), - debugClient.launch(buildLauncArgs('sample2.py', false)), + debugClient.launch(buildLaunchArgs('sample2.py', false)), debugClient.waitForEvent('initialized') ]); @@ -361,7 +357,7 @@ let testCounter = 0; await Promise.all([ debugClient.configurationSequence(), - debugClient.launch(buildLauncArgs('sample2.py', false)), + debugClient.launch(buildLaunchArgs('sample2.py', false)), debugClient.waitForEvent('initialized') ]); @@ -412,7 +408,7 @@ let testCounter = 0; await Promise.all([ debugClient.configurationSequence(), - debugClient.launch(buildLauncArgs('forever.py', false)), + debugClient.launch(buildLaunchArgs('forever.py', false)), debugClient.waitForEvent('initialized'), debugClient.waitForEvent('process') ]); @@ -433,7 +429,7 @@ let testCounter = 0; await Promise.all([ debugClient.configurationSequence(), - debugClient.launch(buildLauncArgs('sample3WithEx.py', false)), + debugClient.launch(buildLaunchArgs('sample3WithEx.py', false)), debugClient.waitForEvent('initialized') ]); @@ -484,7 +480,7 @@ let testCounter = 0; } await Promise.all([ debugClient.configurationSequence(), - debugClient.launch(buildLauncArgs('multiThread.py', false)), + debugClient.launch(buildLaunchArgs('multiThread.py', false)), debugClient.waitForEvent('initialized') ]); @@ -508,7 +504,7 @@ let testCounter = 0; test('Test multi-threaded debugging', async function () { this.timeout(30000); await Promise.all([ - debugClient.launch(buildLauncArgs('multiThread.py', false)), + debugClient.launch(buildLaunchArgs('multiThread.py', false)), debugClient.waitForEvent('initialized') ]); @@ -551,7 +547,7 @@ let testCounter = 0; test('Test stack frames', async () => { await Promise.all([ debugClient.configurationSequence(), - debugClient.launch(buildLauncArgs('stackFrame.py', false)), + debugClient.launch(buildLaunchArgs('stackFrame.py', false)), debugClient.waitForEvent('initialized') ]); const pythonFile = path.join(debugFilesPath, 'stackFrame.py'); @@ -580,7 +576,6 @@ let testCounter = 0; if (debuggerType !== 'pythonExperimental') { return this.skip(); } - const breakpointLocation = { path: path.join(debugFilesPath, 'sample2WithoutSleep.py'), column: 1, line: 5 }; const breakpointArgs = { lines: [breakpointLocation.line], @@ -588,7 +583,7 @@ let testCounter = 0; source: { path: breakpointLocation.path } }; await Promise.all([ - debugClient.launch(buildLauncArgs('sample2WithoutSleep.py', false)), + debugClient.launch(buildLaunchArgs('sample2WithoutSleep.py', false)), debugClient.waitForEvent('initialized') .then(() => debugClient.setBreakpointsRequest(breakpointArgs)) .then(() => debugClient.configurationDoneRequest()) @@ -604,5 +599,18 @@ let testCounter = 0; expect(evaluateResponse.body.result).to.equal('5'); await continueDebugging(debugClient); }); + test('Test Passing custom args to python file', async function () { + if (debuggerType !== 'pythonExperimental') { + return this.skip(); + } + const options = buildLaunchArgs('printSysArgv.py', false); + options.args = ['1', '2', '3']; + await Promise.all([ + debugClient.configurationSequence(), + debugClient.launch(options), + debugClient.assertOutput('stdout', options.args.join(',')), + debugClient.waitForEvent('terminated') + ]); + }); }); }); diff --git a/src/test/pythonFiles/debugging/printSysArgv.py b/src/test/pythonFiles/debugging/printSysArgv.py new file mode 100644 index 000000000000..d14add5dd7f9 --- /dev/null +++ b/src/test/pythonFiles/debugging/printSysArgv.py @@ -0,0 +1,4 @@ +import sys +import time +sys.stdout.write(','.join(sys.argv[1:])) +sys.stdout.flush() From d9eb374f2551dba40bec17e3ec9e0baa7bdc7ca8 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 7 May 2018 12:57:33 -0700 Subject: [PATCH 233/433] Add tests for log points in the experimental debugger (#1583) * Add tests for log points in the experimental debugger * remove retry limitation --- news/3 Code Health/1582.md | 1 + src/test/debugger/misc.test.ts | 24 +++++++++++++++++++- src/test/pythonFiles/debugging/logMessage.py | 4 ++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 news/3 Code Health/1582.md create mode 100644 src/test/pythonFiles/debugging/logMessage.py diff --git a/news/3 Code Health/1582.md b/news/3 Code Health/1582.md new file mode 100644 index 000000000000..6d0ff52d4eb1 --- /dev/null +++ b/news/3 Code Health/1582.md @@ -0,0 +1 @@ +Add tests for log points in the experimental debugger. diff --git a/src/test/debugger/misc.test.ts b/src/test/debugger/misc.test.ts index 7cf1c44ec7af..434ec3db4e8b 100644 --- a/src/test/debugger/misc.test.ts +++ b/src/test/debugger/misc.test.ts @@ -467,7 +467,7 @@ let testCounter = 0; await Promise.all([ debugClient.configurationSequence(), setBreakpointFilter(), - debugClient.launch(buildLauncArgs('sampleWithAssertEx.py', false)), + debugClient.launch(buildLaunchArgs('sampleWithAssertEx.py', false)), waitToStopDueToException(), debugClient.assertStoppedLocation('exception', pauseLocation) ]); @@ -612,5 +612,27 @@ let testCounter = 0; debugClient.waitForEvent('terminated') ]); }); + test('Test Logpoints', async function () { + if (debuggerType !== 'pythonExperimental') { + return this.skip(); + } + const breakpointLocation = { path: path.join(debugFilesPath, 'logMessage.py'), line: 4 }; + const breakpointArgs: DebugProtocol.SetBreakpointsArguments = { + lines: [breakpointLocation.line], + breakpoints: [{ line: breakpointLocation.line, logMessage: 'Sum of {a} and {b} is 3' }], + source: { path: breakpointLocation.path } + }; + await Promise.all([ + debugClient.launch(buildLaunchArgs('logMessage.py', false)), + debugClient.waitForEvent('initialized') + .then(() => debugClient.setBreakpointsRequest(breakpointArgs)) + .then(() => debugClient.configurationDoneRequest()) + .then(() => debugClient.threadsRequest()), + debugClient.waitForEvent('thread') + .then(() => debugClient.threadsRequest()), + debugClient.assertOutput('stdout', 'Sum of 1 and 2 is 3'), + debugClient.waitForEvent('terminated') + ]); + }); }); }); diff --git a/src/test/pythonFiles/debugging/logMessage.py b/src/test/pythonFiles/debugging/logMessage.py new file mode 100644 index 000000000000..ec04cd68c76e --- /dev/null +++ b/src/test/pythonFiles/debugging/logMessage.py @@ -0,0 +1,4 @@ +import time +a = 1 +b = 2 +c = a + b From e61d0cbc66f8439a2365add39e49dc6966e57d5a Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 7 May 2018 13:12:00 -0700 Subject: [PATCH 234/433] Add tests for hit count breakpoints for the experimental debugger (#1584) --- news/3 Code Health/1410.md | 1 + src/test/debugger/misc.test.ts | 28 +++++++++++++++++++++ src/test/pythonFiles/debugging/loopyTest.py | 2 ++ 3 files changed, 31 insertions(+) create mode 100644 news/3 Code Health/1410.md create mode 100644 src/test/pythonFiles/debugging/loopyTest.py diff --git a/news/3 Code Health/1410.md b/news/3 Code Health/1410.md new file mode 100644 index 000000000000..ae0895582c9e --- /dev/null +++ b/news/3 Code Health/1410.md @@ -0,0 +1 @@ +Add tests for hit count breakpoints for the experimental debugger. diff --git a/src/test/debugger/misc.test.ts b/src/test/debugger/misc.test.ts index 434ec3db4e8b..180cb88e9a26 100644 --- a/src/test/debugger/misc.test.ts +++ b/src/test/debugger/misc.test.ts @@ -634,5 +634,33 @@ let testCounter = 0; debugClient.waitForEvent('terminated') ]); }); + test('Test Hit Count Breakpoints', async function () { + if (debuggerType !== 'pythonExperimental') { + return this.skip(); + } + + const breakpointLocation = { path: path.join(debugFilesPath, 'loopyTest.py'), column: 1, line: 2 }; + const breakpointArgs: DebugProtocol.SetBreakpointsArguments = { + lines: [breakpointLocation.line], + breakpoints: [{ line: breakpointLocation.line, column: breakpointLocation.column, hitCondition: '5' }], + source: { path: breakpointLocation.path } + }; + await Promise.all([ + debugClient.launch(buildLaunchArgs('loopyTest.py', false)), + debugClient.waitForEvent('initialized') + .then(() => debugClient.setBreakpointsRequest(breakpointArgs)) + .then(() => debugClient.configurationDoneRequest()) + .then(() => debugClient.threadsRequest()), + debugClient.waitForEvent('thread'), + debugClient.assertStoppedLocation('breakpoint', breakpointLocation) + ]); + + //Do not remove this, this is required to ensure PTVSD is ready to accept other requests. + await debugClient.threadsRequest(); + const evaluateResponse = await debugClient.evaluateRequest({ context: 'repl', expression: 'i', frameId: 1 }); + expect(evaluateResponse.body.type).to.equal('int'); + expect(evaluateResponse.body.result).to.equal('4'); + await continueDebugging(debugClient); + }); }); }); diff --git a/src/test/pythonFiles/debugging/loopyTest.py b/src/test/pythonFiles/debugging/loopyTest.py new file mode 100644 index 000000000000..03c95d371918 --- /dev/null +++ b/src/test/pythonFiles/debugging/loopyTest.py @@ -0,0 +1,2 @@ +for i in range(10): + print(i) From f4edffe4acf0558b4d9ca3671d9bd68305f591be Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 7 May 2018 13:28:51 -0700 Subject: [PATCH 235/433] Add quotes around message coming from pipenv --- src/client/interpreter/locators/services/pipEnvService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/interpreter/locators/services/pipEnvService.ts b/src/client/interpreter/locators/services/pipEnvService.ts index 935e075308e8..c2db5ff46020 100644 --- a/src/client/interpreter/locators/services/pipEnvService.ts +++ b/src/client/interpreter/locators/services/pipEnvService.ts @@ -109,7 +109,7 @@ export class PipEnvService extends CacheableLocatorService { console.error(error); const errorMessage = error.message || error; const appShell = this.serviceContainer.get(IApplicationShell); - appShell.showWarningMessage(`Workspace contains pipfile but attempt to run 'pipenv --venv' failed with ${errorMessage}. Make sure pipenv is on the PATH.`); + appShell.showWarningMessage(`Workspace contains pipfile but attempt to run 'pipenv --venv' failed with '${errorMessage}'. Make sure pipenv is on the PATH.`); } } } From c786e60312e83fa4a2a170fa015baaf3e4f78aa1 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 7 May 2018 14:20:15 -0700 Subject: [PATCH 236/433] Add issue verification to the release plan --- .github/release_plan.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/release_plan.md b/.github/release_plan.md index 6a5f2cc466b7..2d0adf4f62d9 100644 --- a/.github/release_plan.md +++ b/.github/release_plan.md @@ -1,8 +1,9 @@ -# Monday, XXX +# Week of Monday, XXX - [ ] Review the state of the current [milestone](https://github.com/Microsoft/vscode-python/milestones) - [ ] Go through telemetry for GDPR - [ ] Go through all [merged pull requests](https://github.com/Microsoft/vscode-python/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged) and add the [`validate fix` label](https://github.com/Microsoft/vscode-python/labels/validate%20fix) as appropriate as well as checking for news entries +- [ ] Triage [unverified issues](https://github.com/Microsoft/vscode-python/labels/needs%20verification) ## Planning - [ ] Evaluate if TypeScript usage needs updating to sync with VS Code's usage @@ -12,21 +13,23 @@ - [ ] Make sure all issues for this [milestone](https://github.com/Microsoft/vscode-python/milestones) are assigned - [ ] Close issues that have [needed more info](https://github.com/Microsoft/vscode-python/issues?q=is%3Aopen+label%3A%22needs+more+info%22+sort%3Aupdated-asc) for over a month -# Monday, XXX +# Week of Monday, XXX - [ ] Review the state of the current [milestone](https://github.com/Microsoft/vscode-python/milestones) - [ ] Go through telemetry for GDPR - [ ] Go through all [merged pull requests](https://github.com/Microsoft/vscode-python/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged) and add the [`validate fix` label](https://github.com/Microsoft/vscode-python/labels/validate%20fix) as appropriate as well as checking for news entries +- [ ] Triage [unverified issues](https://github.com/Microsoft/vscode-python/labels/needs%20verification) ## Planning - [ ] Check if there have been no performance regressions - [ ] Read through [VS Code's iteration plan](https://github.com/Microsoft/vscode/labels/iteration-plan) (it may still be a [draft](https://github.com/Microsoft/vscode/labels/iteration-plan-draft)) -# Monday, XXX +# Week of Monday, XXX - [ ] Review the state of the current [milestone](https://github.com/Microsoft/vscode-python/milestones) - [ ] Go through telemetry for GDPR - [ ] Go through all [merged pull requests](https://github.com/Microsoft/vscode-python/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged) and add the [`validate fix` label](https://github.com/Microsoft/vscode-python/labels/validate%20fix) as appropriate as well as checking for news entries +- [ ] Triage [unverified issues](https://github.com/Microsoft/vscode-python/labels/needs%20verification) ## Legal - [ ] Announce the lock-down of dependencies for this release @@ -37,12 +40,13 @@ - [ ] Announce the beta [development build](https://github.com/Microsoft/vscode-python/blob/master/CONTRIBUTING.md#development-build) (along with how to help [validate fixes](https://github.com/Microsoft/vscode-python/issues?q=label%3A%22validate+fix%22+is%3Aclosed)) - [ ] Open appropriate [documentation issues](https://github.com/microsoft/vscode-docs) -# Monday, XXX +# Week of Monday, XXX - [ ] Review the state of the current [milestone](https://github.com/Microsoft/vscode-python/milestones) - [ ] Go through telemetry for GDPR - [ ] Merge any last-minute [pull requests](https://github.com/Microsoft/vscode-python/pulls) - [ ] Go through all [merged pull requests](https://github.com/Microsoft/vscode-python/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged) and add the [`validate fix` label](https://github.com/Microsoft/vscode-python/labels/validate%20fix) as appropriate as well as checking for news entries +- [ ] Triage [unverified issues](https://github.com/Microsoft/vscode-python/labels/needs%20verification) ## Prep for the release candidate - [ ] Announce feature freeze @@ -59,8 +63,6 @@ - [ ] Make sure no extraneous files are being included in the `.vsix` file (make sure to check for hidden files) - [ ] Make sure the [appropriate pull requests](https://github.com/microsoft/vscode-docs/pulls) for the [documentation](https://code.visualstudio.com/docs/python/python-tutorial) -- including the [WOW](https://code.visualstudio.com/docs/languages/python) page -- are ready -# Wednesday, XXX (hopefully 😉) - ## Release - [ ] Update the [changelog](https://github.com/Microsoft/vscode-python/blob/master/CHANGELOG.md) (including the names of external contributors & projects) - [ ] Update the [version](https://github.com/Microsoft/vscode-python/blob/master/package.json) number to be final @@ -72,13 +74,13 @@ - [ ] Publish the [blog](http://aka.ms/pythonblog) post - [ ] Create a [release](https://github.com/Microsoft/vscode-python/releases) on GitHub (which creates an appropriate git tag) -## Prep for the next release +## Prep for the _next_ release - [ ] Bump the [version](https://github.com/Microsoft/vscode-python/blob/master/package.json) number to the next `alpha` - [ ] Make sure the next **two** [milestones](https://github.com/Microsoft/vscode-python/milestones) exist - [ ] Lift the feature freeze - [ ] Create a new [release plan](https://github.com/Microsoft/vscode-python/labels/release%20plan) -## Clean up after this release +## Clean up after _this_ release - [ ] Clean up any straggling [fixed issues needing validation](https://github.com/Microsoft/vscode-python/issues?q=label%3A%22validate+fix%22+is%3Aclosed) - [ ] Close the (now) old [milestone](https://github.com/Microsoft/vscode-python/labels/release%20plan) - [ ] Delete the previous releases' [branch](https://github.com/Microsoft/vscode-python/branches) From a1498931f9f48fe7aab02dfc48f92f3aa3c6afaf Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 7 May 2018 16:34:52 -0700 Subject: [PATCH 237/433] Allow all tests to fail (#1633) Temporarily allow tests to fail --- .appveyor.yml | 24 ++++++++++++++++++++++++ .travis.yml | 18 ++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/.appveyor.yml b/.appveyor.yml index dbea6e3a5494..6eae0dad42b7 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -36,12 +36,36 @@ environment: matrix: allow_failures: + - PYTHON: "C:\\Python36" + PYTHON_VERSION: "3.6.3" + PYTHON_ARCH: "32" + nodejs_version: "8.9.1" + APPVEYOR: "true" + DEBUGGER_TEST: "true" - PYTHON: "C:\\Python36" PYTHON_VERSION: "3.6.3" PYTHON_ARCH: "32" nodejs_version: "8.9.1" APPVEYOR: "true" DEBUGGER_TEST_RELEASE: "true" + - PYTHON: "C:\\Python36" + PYTHON_VERSION: "3.6.3" + PYTHON_ARCH: "32" + nodejs_version: "8.9.1" + APPVEYOR: "true" + SINGLE_WORKSPACE_TEST: "true" + - PYTHON: "C:\\Python36" + PYTHON_VERSION: "3.6.3" + PYTHON_ARCH: "32" + nodejs_version: "8.9.1" + APPVEYOR: "true" + MULTIROOT_WORKSPACE_TEST: "true" + - PYTHON: "C:\\Python36" + PYTHON_VERSION: "3.6.3" + PYTHON_ARCH: "32" + nodejs_version: "8.9.1" + APPVEYOR: "true" + ANALYSIS_TEST: "true" init: - "ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH%" diff --git a/.travis.yml b/.travis.yml index d4f9b409925e..50471d9ad1d3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,12 +28,30 @@ matrix: python: "3.6-dev" env: MULTIROOT_WORKSPACE_TEST=true allow_failures: + - os: linux + python: "2.7" + env: DEBUGGER_TEST=true - os: linux python: "2.7" env: DEBUGGER_TEST_RELEASE=true + - os: linux + python: "2.7" + env: SINGLE_WORKSPACE_TEST=true + - os: linux + python: "2.7" + env: MULTIROOT_WORKSPACE_TEST=true + - os: linux + python: "3.6-dev" + env: DEBUGGER_TEST=true - os: linux python: "3.6-dev" env: DEBUGGER_TEST_RELEASE=true + - os: linux + python: "3.6-dev" + env: SINGLE_WORKSPACE_TEST=true + - os: linux + python: "3.6-dev" + env: MULTIROOT_WORKSPACE_TEST=true before_install: | if [ $TRAVIS_OS_NAME == "linux" ]; then export CXX="g++-4.9" CC="gcc-4.9" DISPLAY=:99.0; From 98d55bc4964e4cf1e4e1019b9b48f38103035f67 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 7 May 2018 18:00:00 -0700 Subject: [PATCH 238/433] Allow for negative column numbers in messages returned by `pylint` (#1629) --- news/2 Fixes/1628.md | 1 + src/client/common/configSettings.ts | 2 +- src/client/common/types.ts | 2 +- src/client/linters/baseLinter.ts | 11 +++---- src/test/linters/pylint.test.ts | 47 +++++++++++++++++++++++++++-- 5 files changed, 53 insertions(+), 10 deletions(-) create mode 100644 news/2 Fixes/1628.md diff --git a/news/2 Fixes/1628.md b/news/2 Fixes/1628.md new file mode 100644 index 000000000000..519f751b6161 --- /dev/null +++ b/news/2 Fixes/1628.md @@ -0,0 +1 @@ +Allow for negative column numbers in messages returned by `pylint`. diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index e040313e90ff..dd39cb4f5dbe 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -32,7 +32,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { public venvPath = ''; public venvFolders: string[] = []; public devOptions: string[] = []; - public linting?: ILintingSettings; + public linting!: ILintingSettings; public formatting!: IFormattingSettings; public autoComplete?: IAutoCompleteSettings; public unitTest!: IUnitTestSettings; diff --git a/src/client/common/types.ts b/src/client/common/types.ts index fabac04aa35d..697fd461a24c 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -103,7 +103,7 @@ export interface IPythonSettings { readonly jediPath: string; readonly jediMemoryLimit: number; readonly devOptions: string[]; - readonly linting?: ILintingSettings; + readonly linting: ILintingSettings; readonly formatting: IFormattingSettings; readonly unitTest: IUnitTestSettings; readonly autoComplete?: IAutoCompleteSettings; diff --git a/src/client/linters/baseLinter.ts b/src/client/linters/baseLinter.ts index d941f73246b6..c163a3366faf 100644 --- a/src/client/linters/baseLinter.ts +++ b/src/client/linters/baseLinter.ts @@ -3,16 +3,15 @@ import * as vscode from 'vscode'; import { IWorkspaceService } from '../common/application/types'; import '../common/extensions'; import { IPythonToolExecutionService } from '../common/process/types'; -import { IConfigurationService, IPythonSettings } from '../common/types'; -import { ExecutionInfo, ILogger, Product } from '../common/types'; +import { ExecutionInfo, IConfigurationService, ILogger, IPythonSettings, Product } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { ErrorHandler } from './errorHandlers/errorHandler'; import { ILinter, ILinterInfo, ILinterManager, ILintMessage, LintMessageSeverity } from './types'; // tslint:disable-next-line:no-require-imports no-var-requires const namedRegexp = require('named-js-regexp'); - -const REGEX = '(?\\d+),(?\\d+),(?\\w+),(?\\w\\d+):(?.*)\\r?(\\n|$)'; +// Allow negative column numbers (https://github.com/PyCQA/pylint/issues/1822) +const REGEX = '(?\\d+),(?-?\\d+),(?\\w+),(?\\w\\d+):(?.*)\\r?(\\n|$)'; export interface IRegexGroup { line: number; @@ -36,7 +35,7 @@ export abstract class BaseLinter implements ILinter { protected readonly configService: IConfigurationService; private errorHandler: ErrorHandler; - private _pythonSettings: IPythonSettings; + private _pythonSettings!: IPythonSettings; private _info: ILinterInfo; private workspace: IWorkspaceService; @@ -142,7 +141,7 @@ export abstract class BaseLinter implements ILinter { return { code: match.code, message: match.message, - column: isNaN(match.column) || match.column === 0 ? 0 : match.column - this.columnOffset, + column: isNaN(match.column) || match.column <= 0 ? 0 : match.column - this.columnOffset, line: match.line, type: match.type, provider: this.info.id diff --git a/src/test/linters/pylint.test.ts b/src/test/linters/pylint.test.ts index ce08b0ac5c95..ca51e7150834 100644 --- a/src/test/linters/pylint.test.ts +++ b/src/test/linters/pylint.test.ts @@ -6,7 +6,7 @@ import { Container } from 'inversify'; import * as os from 'os'; import * as path from 'path'; import * as TypeMoq from 'typemoq'; -import { CancellationTokenSource, OutputChannel, TextDocument, Uri, WorkspaceFolder } from 'vscode'; +import { CancellationTokenSource, DiagnosticSeverity, OutputChannel, TextDocument, Uri, WorkspaceFolder } from 'vscode'; import { IWorkspaceService } from '../../client/common/application/types'; import { IFileSystem, IPlatformService } from '../../client/common/platform/types'; import { IPythonToolExecutionService } from '../../client/common/process/types'; @@ -19,7 +19,7 @@ import { ILinterManager } from '../../client/linters/types'; import { MockLintingSettings } from '../mockClasses'; // tslint:disable-next-line:max-func-body-length -suite('Linting - Pylintrc search', () => { +suite('Linting - Pylint', () => { const basePath = '/user/a/b/c/d'; const pylintrc = 'pylintrc'; const dotPylintrc = '.pylintrc'; @@ -199,4 +199,47 @@ suite('Linting - Pylintrc search', () => { expect(execInfo!.args.findIndex(x => x.indexOf('--disable=all') >= 0), 'Minimal args passed to pylint while pylintrc exists.').to.be.eq(expectedMinArgs ? 0 : -1); } + test('Negative column numbers should be treated 0', async () => { + const fileFolder = '/user/a/b/c'; + const outputChannel = TypeMoq.Mock.ofType(); + const pylinter = new Pylint(outputChannel.object, serviceContainer); + + const document = TypeMoq.Mock.ofType(); + document.setup(x => x.uri).returns(() => Uri.file(path.join(fileFolder, 'test.py'))); + + const wsf = TypeMoq.Mock.ofType(); + wsf.setup(x => x.uri).returns(() => Uri.file(fileFolder)); + + workspace.setup(x => x.getWorkspaceFolder(TypeMoq.It.isAny())).returns(() => wsf.object); + + const linterOutput = ['No config file found, using default configuration', + '************* Module test', + '1,1,convention,C0111:Missing module docstring', + '3,-1,error,E1305:Too many arguments for format string'].join(os.EOL); + execService + .setup(x => x.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns(() => Promise.resolve({ stdout: linterOutput, stderr: '' })); + + const lintSettings = new MockLintingSettings(); + lintSettings.pylintUseMinimalCheckers = false; + lintSettings.maxNumberOfProblems = 1000; + lintSettings.pylintPath = 'pyLint'; + lintSettings.pylintEnabled = true; + lintSettings.pylintCategorySeverity = { + convention: DiagnosticSeverity.Hint, + error: DiagnosticSeverity.Error, + fatal: DiagnosticSeverity.Error, + refactor: DiagnosticSeverity.Hint, + warning: DiagnosticSeverity.Warning + }; + + const settings = TypeMoq.Mock.ofType(); + settings.setup(x => x.linting).returns(() => lintSettings); + config.setup(x => x.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); + + const messages = await pylinter.lint(document.object, new CancellationTokenSource().token); + expect(messages).to.be.lengthOf(2); + expect(messages[0].column).to.be.equal(1); + expect(messages[1].column).to.be.equal(0); + }); }); From 12b070262a95e1e684c3781741984a1466e2cf1c Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 7 May 2018 19:59:39 -0700 Subject: [PATCH 239/433] WIP: Add support for folding of docstrings and comments (#894) Add support for folding of docstrings and comments --- package.json | 2 +- src/client/extension.ts | 392 +++++------ src/client/language/iterableTextRange.ts | 31 + .../providers/docStringFoldingProvider.ts | 107 +++ src/test/providers/foldingProvider.test.ts | 65 ++ src/test/pythonFiles/folding/attach_server.py | 330 +++++++++ src/test/pythonFiles/folding/empty.py | 0 src/test/pythonFiles/folding/miscSamples.py | 40 ++ src/test/pythonFiles/folding/noComments.py | 278 ++++++++ src/test/pythonFiles/folding/noDocStrings.py | 266 ++++++++ .../folding/visualstudio_ipython_repl.py | 430 ++++++++++++ ...visualstudio_ipython_repl_double_quotes.py | 430 ++++++++++++ .../folding/visualstudio_py_debugger.py | 644 ++++++++++++++++++ .../folding/visualstudio_py_repl.py | 513 ++++++++++++++ 14 files changed, 3332 insertions(+), 196 deletions(-) create mode 100644 src/client/language/iterableTextRange.ts create mode 100644 src/client/providers/docStringFoldingProvider.ts create mode 100644 src/test/providers/foldingProvider.test.ts create mode 100644 src/test/pythonFiles/folding/attach_server.py create mode 100644 src/test/pythonFiles/folding/empty.py create mode 100644 src/test/pythonFiles/folding/miscSamples.py create mode 100644 src/test/pythonFiles/folding/noComments.py create mode 100644 src/test/pythonFiles/folding/noDocStrings.py create mode 100644 src/test/pythonFiles/folding/visualstudio_ipython_repl.py create mode 100644 src/test/pythonFiles/folding/visualstudio_ipython_repl_double_quotes.py create mode 100644 src/test/pythonFiles/folding/visualstudio_py_debugger.py create mode 100644 src/test/pythonFiles/folding/visualstudio_py_repl.py diff --git a/package.json b/package.json index 6fceac114e85..3a8ffd2d11e1 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "theme": "dark" }, "engines": { - "vscode": "^1.18.0" + "vscode": "^1.23.0" }, "recommendations": [ "donjayamanne.jupyter" diff --git a/src/client/extension.ts b/src/client/extension.ts index b14c115bb176..723e471c0761 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -1,195 +1,197 @@ -'use strict'; -// This line should always be right on top. -// tslint:disable-next-line:no-any -if ((Reflect as any).metadata === undefined) { - // tslint:disable-next-line:no-require-imports no-var-requires - require('reflect-metadata'); -} -import { Container } from 'inversify'; -import { - debug, Disposable, ExtensionContext, - extensions, IndentAction, languages, Memento, - OutputChannel, window -} from 'vscode'; -import { AnalysisExtensionActivator } from './activation/analysis'; -import { ClassicExtensionActivator } from './activation/classic'; -import { IExtensionActivator } from './activation/types'; -import { PythonSettings } from './common/configSettings'; -import { isPythonAnalysisEngineTest, PYTHON, PYTHON_LANGUAGE, STANDARD_OUTPUT_CHANNEL } from './common/constants'; -import { FeatureDeprecationManager } from './common/featureDeprecationManager'; -import { createDeferred } from './common/helpers'; -import { PythonInstaller } from './common/installer/pythonInstallation'; -import { registerTypes as installerRegisterTypes } from './common/installer/serviceRegistry'; -import { registerTypes as platformRegisterTypes } from './common/platform/serviceRegistry'; -import { registerTypes as processRegisterTypes } from './common/process/serviceRegistry'; -import { registerTypes as commonRegisterTypes } from './common/serviceRegistry'; -import { StopWatch } from './common/stopWatch'; -import { GLOBAL_MEMENTO, IConfigurationService, IDisposableRegistry, ILogger, IMemento, IOutputChannel, IPersistentStateFactory, WORKSPACE_MEMENTO } from './common/types'; -import { registerTypes as variableRegisterTypes } from './common/variables/serviceRegistry'; -import { AttachRequestArguments, LaunchRequestArguments } from './debugger/Common/Contracts'; -import { BaseConfigurationProvider } from './debugger/configProviders/baseProvider'; -import { registerTypes as debugConfigurationRegisterTypes } from './debugger/configProviders/serviceRegistry'; -import { IDebugConfigurationProvider } from './debugger/types'; -import { registerTypes as formattersRegisterTypes } from './formatters/serviceRegistry'; -import { IInterpreterSelector } from './interpreter/configuration/types'; -import { ICondaService, IInterpreterService } from './interpreter/contracts'; -import { registerTypes as interpretersRegisterTypes } from './interpreter/serviceRegistry'; -import { ServiceContainer } from './ioc/container'; -import { ServiceManager } from './ioc/serviceManager'; -import { IServiceContainer } from './ioc/types'; -import { LinterCommands } from './linters/linterCommands'; -import { registerTypes as lintersRegisterTypes } from './linters/serviceRegistry'; -import { ILintingEngine } from './linters/types'; -import { PythonFormattingEditProvider } from './providers/formatProvider'; -import { LinterProvider } from './providers/linterProvider'; -import { ReplProvider } from './providers/replProvider'; -import { TerminalProvider } from './providers/terminalProvider'; -import { activateUpdateSparkLibraryProvider } from './providers/updateSparkLibraryProvider'; -import * as sortImports from './sortImports'; -import { sendTelemetryEvent } from './telemetry'; -import { EDITOR_LOAD } from './telemetry/constants'; -import { registerTypes as commonRegisterTerminalTypes } from './terminals/serviceRegistry'; -import { ICodeExecutionManager } from './terminals/types'; -import { BlockFormatProviders } from './typeFormatters/blockFormatProvider'; -import { OnEnterFormatter } from './typeFormatters/onEnterFormatter'; -import { TEST_OUTPUT_CHANNEL } from './unittests/common/constants'; -import { registerTypes as unitTestsRegisterTypes } from './unittests/serviceRegistry'; -import { WorkspaceSymbols } from './workspaceSymbols/main'; - -const activationDeferred = createDeferred(); -export const activated = activationDeferred.promise; - -// tslint:disable-next-line:max-func-body-length -export async function activate(context: ExtensionContext) { - const cont = new Container(); - const serviceManager = new ServiceManager(cont); - const serviceContainer = new ServiceContainer(cont); - registerServices(context, serviceManager, serviceContainer); - - const interpreterManager = serviceContainer.get(IInterpreterService); - // This must be completed before we can continue as language server needs the interpreter path. - interpreterManager.initialize(); - await interpreterManager.autoSetInterpreter(); - - const configuration = serviceManager.get(IConfigurationService); - const pythonSettings = configuration.getSettings(); - - const activator: IExtensionActivator = isPythonAnalysisEngineTest() || !pythonSettings.jediEnabled - ? new AnalysisExtensionActivator(serviceManager, pythonSettings) - : new ClassicExtensionActivator(serviceManager, pythonSettings, PYTHON); - - await activator.activate(context); - - const standardOutputChannel = serviceManager.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); - sortImports.activate(context, standardOutputChannel, serviceManager); - - serviceManager.get(ICodeExecutionManager).registerCommands(); - // tslint:disable-next-line:no-floating-promises - sendStartupTelemetry(activated, serviceContainer); - - const pythonInstaller = new PythonInstaller(serviceContainer); - pythonInstaller.checkPythonInstallation(PythonSettings.getInstance()) - .catch(ex => console.error('Python Extension: pythonInstaller.checkPythonInstallation', ex)); - - interpreterManager.refresh() - .catch(ex => console.error('Python Extension: interpreterManager.refresh', ex)); - - const jupyterExtension = extensions.getExtension('donjayamanne.jupyter'); - const lintingEngine = serviceManager.get(ILintingEngine); - lintingEngine.linkJupiterExtension(jupyterExtension).ignoreErrors(); - - context.subscriptions.push(new LinterCommands(serviceManager)); - const linterProvider = new LinterProvider(context, serviceManager); - context.subscriptions.push(linterProvider); - - // Enable indentAction - // tslint:disable-next-line:no-non-null-assertion - languages.setLanguageConfiguration(PYTHON_LANGUAGE, { - onEnterRules: [ - { - beforeText: /^\s*(?:def|class|for|if|elif|else|while|try|with|finally|except)\b.*:\s*\S+/, - action: { indentAction: IndentAction.None } - }, - { - beforeText: /^\s*(?:def|class|for|if|elif|else|while|try|with|finally|except|async)\b.*:\s*/, - action: { indentAction: IndentAction.Indent } - }, - { - beforeText: /^\s*#.*/, - afterText: /.+$/, - action: { indentAction: IndentAction.None, appendText: '# ' } - }, - { - beforeText: /^\s+(continue|break|return)\b.*/, - afterText: /\s+$/, - action: { indentAction: IndentAction.Outdent } - } - ] - }); - - if (pythonSettings && pythonSettings.formatting && pythonSettings.formatting.provider !== 'none') { - const formatProvider = new PythonFormattingEditProvider(context, serviceContainer); - context.subscriptions.push(languages.registerDocumentFormattingEditProvider(PYTHON, formatProvider)); - context.subscriptions.push(languages.registerDocumentRangeFormattingEditProvider(PYTHON, formatProvider)); - } - - context.subscriptions.push(languages.registerOnTypeFormattingEditProvider(PYTHON, new BlockFormatProviders(), ':')); - context.subscriptions.push(languages.registerOnTypeFormattingEditProvider(PYTHON, new OnEnterFormatter(), '\n')); - - const persistentStateFactory = serviceManager.get(IPersistentStateFactory); - const deprecationMgr = new FeatureDeprecationManager(persistentStateFactory, !!jupyterExtension); - deprecationMgr.initialize(); - context.subscriptions.push(new FeatureDeprecationManager(persistentStateFactory, !!jupyterExtension)); - - context.subscriptions.push(serviceContainer.get(IInterpreterSelector)); - context.subscriptions.push(activateUpdateSparkLibraryProvider()); - - context.subscriptions.push(new ReplProvider(serviceContainer)); - context.subscriptions.push(new TerminalProvider(serviceContainer)); - context.subscriptions.push(new WorkspaceSymbols(serviceContainer)); - - type ConfigurationProvider = BaseConfigurationProvider; - serviceContainer.getAll(IDebugConfigurationProvider).forEach(debugConfig => { - context.subscriptions.push(debug.registerDebugConfigurationProvider(debugConfig.debugType, debugConfig)); - }); - activationDeferred.resolve(); -} - -function registerServices(context: ExtensionContext, serviceManager: ServiceManager, serviceContainer: ServiceContainer) { - serviceManager.addSingletonInstance(IServiceContainer, serviceContainer); - serviceManager.addSingletonInstance(IDisposableRegistry, context.subscriptions); - serviceManager.addSingletonInstance(IMemento, context.globalState, GLOBAL_MEMENTO); - serviceManager.addSingletonInstance(IMemento, context.workspaceState, WORKSPACE_MEMENTO); - - const standardOutputChannel = window.createOutputChannel('Python'); - const unitTestOutChannel = window.createOutputChannel('Python Test Log'); - serviceManager.addSingletonInstance(IOutputChannel, standardOutputChannel, STANDARD_OUTPUT_CHANNEL); - serviceManager.addSingletonInstance(IOutputChannel, unitTestOutChannel, TEST_OUTPUT_CHANNEL); - - commonRegisterTypes(serviceManager); - processRegisterTypes(serviceManager); - variableRegisterTypes(serviceManager); - unitTestsRegisterTypes(serviceManager); - lintersRegisterTypes(serviceManager); - interpretersRegisterTypes(serviceManager); - formattersRegisterTypes(serviceManager); - platformRegisterTypes(serviceManager); - installerRegisterTypes(serviceManager); - commonRegisterTerminalTypes(serviceManager); - debugConfigurationRegisterTypes(serviceManager); -} - -async function sendStartupTelemetry(activatedPromise: Promise, serviceContainer: IServiceContainer) { - const stopWatch = new StopWatch(); - const logger = serviceContainer.get(ILogger); - try { - await activatedPromise; - const duration = stopWatch.elapsedTime; - const condaLocator = serviceContainer.get(ICondaService); - const condaVersion = await condaLocator.getCondaVersion().catch(() => undefined); - const props = condaVersion ? { condaVersion } : undefined; - sendTelemetryEvent(EDITOR_LOAD, duration, props); - } catch (ex) { - logger.logError('sendStartupTelemetry failed.', ex); - } -} +'use strict'; +// This line should always be right on top. +// tslint:disable-next-line:no-any +if ((Reflect as any).metadata === undefined) { + // tslint:disable-next-line:no-require-imports no-var-requires + require('reflect-metadata'); +} +import { Container } from 'inversify'; +import { + debug, Disposable, ExtensionContext, + extensions, IndentAction, languages, Memento, + OutputChannel, window +} from 'vscode'; +import { AnalysisExtensionActivator } from './activation/analysis'; +import { ClassicExtensionActivator } from './activation/classic'; +import { IExtensionActivator } from './activation/types'; +import { PythonSettings } from './common/configSettings'; +import { isPythonAnalysisEngineTest, PYTHON, PYTHON_LANGUAGE, STANDARD_OUTPUT_CHANNEL } from './common/constants'; +import { FeatureDeprecationManager } from './common/featureDeprecationManager'; +import { createDeferred } from './common/helpers'; +import { PythonInstaller } from './common/installer/pythonInstallation'; +import { registerTypes as installerRegisterTypes } from './common/installer/serviceRegistry'; +import { registerTypes as platformRegisterTypes } from './common/platform/serviceRegistry'; +import { registerTypes as processRegisterTypes } from './common/process/serviceRegistry'; +import { registerTypes as commonRegisterTypes } from './common/serviceRegistry'; +import { StopWatch } from './common/stopWatch'; +import { GLOBAL_MEMENTO, IConfigurationService, IDisposableRegistry, ILogger, IMemento, IOutputChannel, IPersistentStateFactory, WORKSPACE_MEMENTO } from './common/types'; +import { registerTypes as variableRegisterTypes } from './common/variables/serviceRegistry'; +import { AttachRequestArguments, LaunchRequestArguments } from './debugger/Common/Contracts'; +import { BaseConfigurationProvider } from './debugger/configProviders/baseProvider'; +import { registerTypes as debugConfigurationRegisterTypes } from './debugger/configProviders/serviceRegistry'; +import { IDebugConfigurationProvider } from './debugger/types'; +import { registerTypes as formattersRegisterTypes } from './formatters/serviceRegistry'; +import { IInterpreterSelector } from './interpreter/configuration/types'; +import { ICondaService, IInterpreterService } from './interpreter/contracts'; +import { registerTypes as interpretersRegisterTypes } from './interpreter/serviceRegistry'; +import { ServiceContainer } from './ioc/container'; +import { ServiceManager } from './ioc/serviceManager'; +import { IServiceContainer } from './ioc/types'; +import { LinterCommands } from './linters/linterCommands'; +import { registerTypes as lintersRegisterTypes } from './linters/serviceRegistry'; +import { ILintingEngine } from './linters/types'; +import { DocStringFoldingProvider } from './providers/docStringFoldingProvider'; +import { PythonFormattingEditProvider } from './providers/formatProvider'; +import { LinterProvider } from './providers/linterProvider'; +import { ReplProvider } from './providers/replProvider'; +import { TerminalProvider } from './providers/terminalProvider'; +import { activateUpdateSparkLibraryProvider } from './providers/updateSparkLibraryProvider'; +import * as sortImports from './sortImports'; +import { sendTelemetryEvent } from './telemetry'; +import { EDITOR_LOAD } from './telemetry/constants'; +import { registerTypes as commonRegisterTerminalTypes } from './terminals/serviceRegistry'; +import { ICodeExecutionManager } from './terminals/types'; +import { BlockFormatProviders } from './typeFormatters/blockFormatProvider'; +import { OnEnterFormatter } from './typeFormatters/onEnterFormatter'; +import { TEST_OUTPUT_CHANNEL } from './unittests/common/constants'; +import { registerTypes as unitTestsRegisterTypes } from './unittests/serviceRegistry'; +import { WorkspaceSymbols } from './workspaceSymbols/main'; + +const activationDeferred = createDeferred(); +export const activated = activationDeferred.promise; + +// tslint:disable-next-line:max-func-body-length +export async function activate(context: ExtensionContext) { + const cont = new Container(); + const serviceManager = new ServiceManager(cont); + const serviceContainer = new ServiceContainer(cont); + registerServices(context, serviceManager, serviceContainer); + + const interpreterManager = serviceContainer.get(IInterpreterService); + // This must be completed before we can continue as language server needs the interpreter path. + interpreterManager.initialize(); + await interpreterManager.autoSetInterpreter(); + + const configuration = serviceManager.get(IConfigurationService); + const pythonSettings = configuration.getSettings(); + + const activator: IExtensionActivator = isPythonAnalysisEngineTest() || !pythonSettings.jediEnabled + ? new AnalysisExtensionActivator(serviceManager, pythonSettings) + : new ClassicExtensionActivator(serviceManager, pythonSettings, PYTHON); + + await activator.activate(context); + + const standardOutputChannel = serviceManager.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); + sortImports.activate(context, standardOutputChannel, serviceManager); + + serviceManager.get(ICodeExecutionManager).registerCommands(); + // tslint:disable-next-line:no-floating-promises + sendStartupTelemetry(activated, serviceContainer); + + const pythonInstaller = new PythonInstaller(serviceContainer); + pythonInstaller.checkPythonInstallation(PythonSettings.getInstance()) + .catch(ex => console.error('Python Extension: pythonInstaller.checkPythonInstallation', ex)); + + interpreterManager.refresh() + .catch(ex => console.error('Python Extension: interpreterManager.refresh', ex)); + + const jupyterExtension = extensions.getExtension('donjayamanne.jupyter'); + const lintingEngine = serviceManager.get(ILintingEngine); + lintingEngine.linkJupiterExtension(jupyterExtension).ignoreErrors(); + + context.subscriptions.push(new LinterCommands(serviceManager)); + const linterProvider = new LinterProvider(context, serviceManager); + context.subscriptions.push(linterProvider); + + // Enable indentAction + // tslint:disable-next-line:no-non-null-assertion + languages.setLanguageConfiguration(PYTHON_LANGUAGE, { + onEnterRules: [ + { + beforeText: /^\s*(?:def|class|for|if|elif|else|while|try|with|finally|except)\b.*:\s*\S+/, + action: { indentAction: IndentAction.None } + }, + { + beforeText: /^\s*(?:def|class|for|if|elif|else|while|try|with|finally|except|async)\b.*:\s*/, + action: { indentAction: IndentAction.Indent } + }, + { + beforeText: /^\s*#.*/, + afterText: /.+$/, + action: { indentAction: IndentAction.None, appendText: '# ' } + }, + { + beforeText: /^\s+(continue|break|return)\b.*/, + afterText: /\s+$/, + action: { indentAction: IndentAction.Outdent } + } + ] + }); + + if (pythonSettings && pythonSettings.formatting && pythonSettings.formatting.provider !== 'none') { + const formatProvider = new PythonFormattingEditProvider(context, serviceContainer); + context.subscriptions.push(languages.registerDocumentFormattingEditProvider(PYTHON, formatProvider)); + context.subscriptions.push(languages.registerDocumentRangeFormattingEditProvider(PYTHON, formatProvider)); + } + + context.subscriptions.push(languages.registerOnTypeFormattingEditProvider(PYTHON, new BlockFormatProviders(), ':')); + context.subscriptions.push(languages.registerOnTypeFormattingEditProvider(PYTHON, new OnEnterFormatter(), '\n')); + context.subscriptions.push(languages.registerFoldingRangeProvider(PYTHON, new DocStringFoldingProvider())); + + const persistentStateFactory = serviceManager.get(IPersistentStateFactory); + const deprecationMgr = new FeatureDeprecationManager(persistentStateFactory, !!jupyterExtension); + deprecationMgr.initialize(); + context.subscriptions.push(new FeatureDeprecationManager(persistentStateFactory, !!jupyterExtension)); + + context.subscriptions.push(serviceContainer.get(IInterpreterSelector)); + context.subscriptions.push(activateUpdateSparkLibraryProvider()); + + context.subscriptions.push(new ReplProvider(serviceContainer)); + context.subscriptions.push(new TerminalProvider(serviceContainer)); + context.subscriptions.push(new WorkspaceSymbols(serviceContainer)); + + type ConfigurationProvider = BaseConfigurationProvider; + serviceContainer.getAll(IDebugConfigurationProvider).forEach(debugConfig => { + context.subscriptions.push(debug.registerDebugConfigurationProvider(debugConfig.debugType, debugConfig)); + }); + activationDeferred.resolve(); +} + +function registerServices(context: ExtensionContext, serviceManager: ServiceManager, serviceContainer: ServiceContainer) { + serviceManager.addSingletonInstance(IServiceContainer, serviceContainer); + serviceManager.addSingletonInstance(IDisposableRegistry, context.subscriptions); + serviceManager.addSingletonInstance(IMemento, context.globalState, GLOBAL_MEMENTO); + serviceManager.addSingletonInstance(IMemento, context.workspaceState, WORKSPACE_MEMENTO); + + const standardOutputChannel = window.createOutputChannel('Python'); + const unitTestOutChannel = window.createOutputChannel('Python Test Log'); + serviceManager.addSingletonInstance(IOutputChannel, standardOutputChannel, STANDARD_OUTPUT_CHANNEL); + serviceManager.addSingletonInstance(IOutputChannel, unitTestOutChannel, TEST_OUTPUT_CHANNEL); + + commonRegisterTypes(serviceManager); + processRegisterTypes(serviceManager); + variableRegisterTypes(serviceManager); + unitTestsRegisterTypes(serviceManager); + lintersRegisterTypes(serviceManager); + interpretersRegisterTypes(serviceManager); + formattersRegisterTypes(serviceManager); + platformRegisterTypes(serviceManager); + installerRegisterTypes(serviceManager); + commonRegisterTerminalTypes(serviceManager); + debugConfigurationRegisterTypes(serviceManager); +} + +async function sendStartupTelemetry(activatedPromise: Promise, serviceContainer: IServiceContainer) { + const stopWatch = new StopWatch(); + const logger = serviceContainer.get(ILogger); + try { + await activatedPromise; + const duration = stopWatch.elapsedTime; + const condaLocator = serviceContainer.get(ICondaService); + const condaVersion = await condaLocator.getCondaVersion().catch(() => undefined); + const props = condaVersion ? { condaVersion } : undefined; + sendTelemetryEvent(EDITOR_LOAD, duration, props); + } catch (ex) { + logger.logError('sendStartupTelemetry failed.', ex); + } +} diff --git a/src/client/language/iterableTextRange.ts b/src/client/language/iterableTextRange.ts new file mode 100644 index 000000000000..6f92e1e769de --- /dev/null +++ b/src/client/language/iterableTextRange.ts @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { ITextRange, ITextRangeCollection } from './types'; + +export class IterableTextRange implements Iterable{ + constructor(private textRangeCollection: ITextRangeCollection) { + } + public [Symbol.iterator](): Iterator { + let index = -1; + + return { + next: (): IteratorResult => { + if (index < this.textRangeCollection.count - 1) { + return { + done: false, + value: this.textRangeCollection.getItemAt(index += 1) + }; + } else { + return { + done: true, + // tslint:disable-next-line:no-any + value: undefined as any + }; + } + } + }; + } +} diff --git a/src/client/providers/docStringFoldingProvider.ts b/src/client/providers/docStringFoldingProvider.ts new file mode 100644 index 000000000000..2b163cade5d1 --- /dev/null +++ b/src/client/providers/docStringFoldingProvider.ts @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { CancellationToken, FoldingContext, FoldingRange, FoldingRangeKind, FoldingRangeProvider, ProviderResult, Range, TextDocument } from 'vscode'; +import { IterableTextRange } from '../language/iterableTextRange'; +import { IToken, TokenizerMode, TokenType } from '../language/types'; +import { getDocumentTokens } from './providerUtilities'; + +export class DocStringFoldingProvider implements FoldingRangeProvider { + public provideFoldingRanges(document: TextDocument, _context: FoldingContext, token: CancellationToken): ProviderResult { + return this.getFoldingRanges(document); + } + + private getFoldingRanges(document: TextDocument) { + const tokenCollection = getDocumentTokens(document, document.lineAt(document.lineCount - 1).range.end, TokenizerMode.CommentsAndStrings); + const tokens = new IterableTextRange(tokenCollection); + + const docStringRanges: FoldingRange[] = []; + const commentRanges: FoldingRange[] = []; + + for (const token of tokens) { + const docstringRange = this.getDocStringFoldingRange(document, token); + if (docstringRange) { + docStringRanges.push(docstringRange); + continue; + } + + const commentRange = this.getSingleLineCommentRange(document, token); + if (commentRange) { + this.buildMultiLineCommentRange(commentRange, commentRanges); + } + } + + this.removeLastSingleLineComment(commentRanges); + return docStringRanges.concat(commentRanges); + } + private buildMultiLineCommentRange(commentRange: FoldingRange, commentRanges: FoldingRange[]) { + if (commentRanges.length === 0) { + commentRanges.push(commentRange); + return; + } + const previousComment = commentRanges[commentRanges.length - 1]; + if (previousComment.end + 1 === commentRange.start) { + previousComment.end = commentRange.end; + return; + } + if (previousComment.start === previousComment.end) { + commentRanges[commentRanges.length - 1] = commentRange; + return; + } + commentRanges.push(commentRange); + } + private removeLastSingleLineComment(commentRanges: FoldingRange[]) { + // Remove last comment folding range if its a single line entry. + if (commentRanges.length === 0) { + return; + } + const lastComment = commentRanges[commentRanges.length - 1]; + if (lastComment.start === lastComment.end) { + commentRanges.pop(); + } + } + private getDocStringFoldingRange(document: TextDocument, token: IToken) { + if (token.type !== TokenType.String) { + return; + } + + const startPosition = document.positionAt(token.start); + const endPosition = document.positionAt(token.end); + if (startPosition.line === endPosition.line) { + return; + } + + const startLine = document.lineAt(startPosition); + if (startLine.firstNonWhitespaceCharacterIndex !== startPosition.character) { + return; + } + const startIndex1 = startLine.text.indexOf('\'\'\''); + const startIndex2 = startLine.text.indexOf('"""'); + if (startIndex1 !== startPosition.character && startIndex2 !== startPosition.character) { + return; + } + + const range = new Range(startPosition, endPosition); + + return new FoldingRange(range.start.line, range.end.line); + } + private getSingleLineCommentRange(document: TextDocument, token: IToken) { + if (token.type !== TokenType.Comment) { + return; + } + + const startPosition = document.positionAt(token.start); + const endPosition = document.positionAt(token.end); + if (startPosition.line !== endPosition.line) { + return; + } + if (document.lineAt(startPosition).firstNonWhitespaceCharacterIndex !== startPosition.character) { + return; + } + + const range = new Range(startPosition, endPosition); + return new FoldingRange(range.start.line, range.end.line, FoldingRangeKind.Comment); + } +} diff --git a/src/test/providers/foldingProvider.test.ts b/src/test/providers/foldingProvider.test.ts new file mode 100644 index 000000000000..9de189afb47f --- /dev/null +++ b/src/test/providers/foldingProvider.test.ts @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { expect } from 'chai'; +import * as path from 'path'; +import { CancellationTokenSource, FoldingRange, FoldingRangeKind, workspace } from 'vscode'; +import { DocStringFoldingProvider } from '../../client/providers/docStringFoldingProvider'; + +type FileFoldingRanges = { file: string; ranges: FoldingRange[] }; +const pythonFilesPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'folding'); + +// tslint:disable-next-line:max-func-body-length +suite('Provider - Folding Provider', () => { + const docStringFileAndExpectedFoldingRanges: FileFoldingRanges[] = [ + { + file: path.join(pythonFilesPath, 'attach_server.py'), ranges: [ + new FoldingRange(0, 14), new FoldingRange(44, 73, FoldingRangeKind.Comment), + new FoldingRange(95, 143), new FoldingRange(149, 150, FoldingRangeKind.Comment), + new FoldingRange(305, 313), new FoldingRange(320, 322) + ] + }, + { + file: path.join(pythonFilesPath, 'visualstudio_ipython_repl.py'), ranges: [ + new FoldingRange(0, 14), new FoldingRange(78, 79, FoldingRangeKind.Comment), + new FoldingRange(81, 82, FoldingRangeKind.Comment), new FoldingRange(92, 93, FoldingRangeKind.Comment), + new FoldingRange(108, 109, FoldingRangeKind.Comment), new FoldingRange(139, 140, FoldingRangeKind.Comment), + new FoldingRange(169, 170, FoldingRangeKind.Comment), new FoldingRange(275, 277, FoldingRangeKind.Comment), + new FoldingRange(319, 320, FoldingRangeKind.Comment) + ] + }, + { + file: path.join(pythonFilesPath, 'visualstudio_py_debugger.py'), ranges: [ + new FoldingRange(0, 15, FoldingRangeKind.Comment), new FoldingRange(22, 25, FoldingRangeKind.Comment), + new FoldingRange(47, 48, FoldingRangeKind.Comment), new FoldingRange(69, 70, FoldingRangeKind.Comment), + new FoldingRange(96, 97, FoldingRangeKind.Comment), new FoldingRange(105, 106, FoldingRangeKind.Comment), + new FoldingRange(141, 142, FoldingRangeKind.Comment), new FoldingRange(149, 162, FoldingRangeKind.Comment), + new FoldingRange(165, 166, FoldingRangeKind.Comment), new FoldingRange(207, 208, FoldingRangeKind.Comment), + new FoldingRange(235, 237, FoldingRangeKind.Comment), new FoldingRange(240, 241, FoldingRangeKind.Comment), + new FoldingRange(300, 301, FoldingRangeKind.Comment), new FoldingRange(334, 335, FoldingRangeKind.Comment), + new FoldingRange(346, 348, FoldingRangeKind.Comment), new FoldingRange(499, 500, FoldingRangeKind.Comment), + new FoldingRange(558, 559, FoldingRangeKind.Comment), new FoldingRange(602, 604, FoldingRangeKind.Comment), + new FoldingRange(608, 609, FoldingRangeKind.Comment), new FoldingRange(612, 614, FoldingRangeKind.Comment), + new FoldingRange(637, 638, FoldingRangeKind.Comment) + ] + }, + { + file: path.join(pythonFilesPath, 'visualstudio_py_repl.py'), ranges: [] + } + ]; + + docStringFileAndExpectedFoldingRanges.forEach(item => { + test(`Test Docstring folding regions '${path.basename(item.file)}'`, async () => { + const document = await workspace.openTextDocument(item.file); + const provider = new DocStringFoldingProvider(); + const ranges = await provider.provideFoldingRanges(document, {}, new CancellationTokenSource().token); + expect(ranges).to.be.lengthOf(item.ranges.length); + ranges!.forEach(range => { + const index = item.ranges + .findIndex(searchItem => searchItem.start === range.start && + searchItem.end === range.end); + expect(index).to.be.greaterThan(-1, `${range.start}, ${range.end} not found`); + }); + }); + }); +}); diff --git a/src/test/pythonFiles/folding/attach_server.py b/src/test/pythonFiles/folding/attach_server.py new file mode 100644 index 000000000000..c67dc9f106a6 --- /dev/null +++ b/src/test/pythonFiles/folding/attach_server.py @@ -0,0 +1,330 @@ +# Python Tools for Visual Studio +# Copyright(c) Microsoft Corporation +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the License); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at http://www.apache.org/licenses/LICENSE-2.0 +# +# THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS +# OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY +# IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +# MERCHANTABLITY OR NON-INFRINGEMENT. +# +# See the Apache Version 2.0 License for specific language governing +# permissions and limitations under the License. + +__author__ = "Microsoft Corporation " +__version__ = "3.0.0.0" + +__all__ = ['enable_attach', 'wait_for_attach', 'break_into_debugger', 'settrace', 'is_attached', 'AttachAlreadyEnabledError'] + +import atexit +import getpass +import os +import os.path +import platform +import socket +import struct +import sys +import threading +try: + import thread +except ImportError: + import _thread as thread +try: + import ssl +except ImportError: + ssl = None + +import ptvsd.visualstudio_py_debugger as vspd +import ptvsd.visualstudio_py_repl as vspr +from ptvsd.visualstudio_py_util import to_bytes, read_bytes, read_int, read_string, write_bytes, write_int, write_string + + +# The server (i.e. the Python app) waits on a TCP port provided. Whenever anything connects to that port, +# it immediately sends the octet sequence 'PTVSDBG', followed by version number represented as int64, +# and then waits for the client to respond with the same exact byte sequence. After signatures are thereby +# exchanged and found to match, the client is expected to provide a string secret (in the usual debugger +# string format, None/ACII/Unicode prefix + length + data), which can be an empty string to designate the +# lack of a specified secret. +# +# If the secret does not match the one expected by the server, it responds with 'RJCT', and then closes +# the connection. Otherwise, the server responds with 'ACPT', and awaits a 4-octet command. The following +# commands are recognized: +# +# 'INFO' +# Report information about the process. The server responds with the following information, in order: +# - Process ID (int64) +# - Executable name (string) +# - User name (string) +# - Implementation name (string) +# and then immediately closes connection. Note, all string fields can be empty or null strings. +# +# 'ATCH' +# Attach debugger to the process. If successful, the server responds with 'ACPT', followed by process ID +# (int64), and then the Python language version that the server is running represented by three int64s - +# major, minor, micro; From there on the socket is assumed to be using the normal PTVS debugging protocol. +# If attaching was not successful (which can happen if some other debugger is already attached), the server +# responds with 'RJCT' and closes the connection. +# +# 'REPL' +# Attach REPL to the process. If successful, the server responds with 'ACPT', and from there on the socket +# is assumed to be using the normal PTVS REPL protocol. If not successful (which can happen if there is +# no debugger attached), the server responds with 'RJCT' and closes the connection. + +PTVS_VER = '2.2' +DEFAULT_PORT = 5678 +PTVSDBG_VER = 6 # must be kept in sync with DebuggerProtocolVersion in PythonRemoteProcess.cs +PTVSDBG = to_bytes('PTVSDBG') +ACPT = to_bytes('ACPT') +RJCT = to_bytes('RJCT') +INFO = to_bytes('INFO') +ATCH = to_bytes('ATCH') +REPL = to_bytes('REPL') + +_attach_enabled = False +_attached = threading.Event() +vspd.DONT_DEBUG.append(os.path.normcase(__file__)) + + +class AttachAlreadyEnabledError(Exception): + """`ptvsd.enable_attach` has already been called in this process.""" + + +def enable_attach(secret, address = ('0.0.0.0', DEFAULT_PORT), certfile = None, keyfile = None, redirect_output = True): + """Enables Python Tools for Visual Studio to attach to this process remotely + to debug Python code. + + Parameters + ---------- + secret : str + Used to validate the clients - only those clients providing the valid + secret will be allowed to connect to this server. On client side, the + secret is prepended to the Qualifier string, separated from the + hostname by ``'@'``, e.g.: ``'secret@myhost.cloudapp.net:5678'``. If + secret is ``None``, there's no validation, and any client can connect + freely. + address : (str, int), optional + Specifies the interface and port on which the debugging server should + listen for TCP connections. It is in the same format as used for + regular sockets of the `socket.AF_INET` family, i.e. a tuple of + ``(hostname, port)``. On client side, the server is identified by the + Qualifier string in the usual ``'hostname:port'`` format, e.g.: + ``'myhost.cloudapp.net:5678'``. Default is ``('0.0.0.0', 5678)``. + certfile : str, optional + Used to enable SSL. If not specified, or if set to ``None``, the + connection between this program and the debugger will be unsecure, + and can be intercepted on the wire. If specified, the meaning of this + parameter is the same as for `ssl.wrap_socket`. + keyfile : str, optional + Used together with `certfile` when SSL is enabled. Its meaning is the + same as for ``ssl.wrap_socket``. + redirect_output : bool, optional + Specifies whether any output (on both `stdout` and `stderr`) produced + by this program should be sent to the debugger. Default is ``True``. + + Notes + ----- + This function returns immediately after setting up the debugging server, + and does not block program execution. If you need to block until debugger + is attached, call `ptvsd.wait_for_attach`. The debugger can be detached + and re-attached multiple times after `enable_attach` is called. + + This function can only be called once during the lifetime of the process. + On a second call, `AttachAlreadyEnabledError` is raised. In circumstances + where the caller does not control how many times the function will be + called (e.g. when a script with a single call is run more than once by + a hosting app or framework), the call should be wrapped in ``try..except``. + + Only the thread on which this function is called, and any threads that are + created after it returns, will be visible in the debugger once it is + attached. Any threads that are already running before this function is + called will not be visible. + """ + + if not ssl and (certfile or keyfile): + raise ValueError('could not import the ssl module - SSL is not supported on this version of Python') + + if sys.platform == 'cli': + # Check that IronPython was launched with -X:Frames and -X:Tracing, since we can't register our trace + # func on the thread that calls enable_attach otherwise + import clr + x_tracing = clr.GetCurrentRuntime().GetLanguageByExtension('py').Options.Tracing + x_frames = clr.GetCurrentRuntime().GetLanguageByExtension('py').Options.Frames + if not x_tracing or not x_frames: + raise RuntimeError('IronPython must be started with -X:Tracing and -X:Frames options to support PTVS remote debugging.') + + global _attach_enabled + if _attach_enabled: + raise AttachAlreadyEnabledError('ptvsd.enable_attach() has already been called in this process.') + _attach_enabled = True + + atexit.register(vspd.detach_process_and_notify_debugger) + + server = socket.socket(proto=socket.IPPROTO_TCP) + server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server.bind(address) + server.listen(1) + def server_thread_func(): + while True: + client = None + raw_client = None + try: + client, addr = server.accept() + if certfile: + client = ssl.wrap_socket(client, server_side = True, ssl_version = ssl.PROTOCOL_TLSv1, certfile = certfile, keyfile = keyfile) + write_bytes(client, PTVSDBG) + write_int(client, PTVSDBG_VER) + + response = read_bytes(client, 7) + if response != PTVSDBG: + continue + dbg_ver = read_int(client) + if dbg_ver != PTVSDBG_VER: + continue + + client_secret = read_string(client) + if secret is None or secret == client_secret: + write_bytes(client, ACPT) + else: + write_bytes(client, RJCT) + continue + + response = read_bytes(client, 4) + + if response == INFO: + try: + pid = os.getpid() + except AttributeError: + pid = 0 + write_int(client, pid) + + exe = sys.executable or '' + write_string(client, exe) + + try: + username = getpass.getuser() + except AttributeError: + username = '' + write_string(client, username) + + try: + impl = platform.python_implementation() + except AttributeError: + try: + impl = sys.implementation.name + except AttributeError: + impl = 'Python' + + major, minor, micro, release_level, serial = sys.version_info + + os_and_arch = platform.system() + if os_and_arch == "": + os_and_arch = sys.platform + try: + if sys.maxsize > 2**32: + os_and_arch += ' 64-bit' + else: + os_and_arch += ' 32-bit' + except AttributeError: + pass + + version = '%s %s.%s.%s (%s)' % (impl, major, minor, micro, os_and_arch) + write_string(client, version) + + # Don't just drop the connection - let the debugger close it after it finishes reading. + client.recv(1) + + elif response == ATCH: + debug_options = vspd.parse_debug_options(read_string(client)) + if redirect_output: + debug_options.add('RedirectOutput') + + if vspd.DETACHED: + write_bytes(client, ACPT) + try: + pid = os.getpid() + except AttributeError: + pid = 0 + write_int(client, pid) + + major, minor, micro, release_level, serial = sys.version_info + write_int(client, major) + write_int(client, minor) + write_int(client, micro) + + vspd.attach_process_from_socket(client, debug_options, report = True) + vspd.mark_all_threads_for_break(vspd.STEPPING_ATTACH_BREAK) + _attached.set() + client = None + else: + write_bytes(client, RJCT) + + elif response == REPL: + if not vspd.DETACHED: + write_bytes(client, ACPT) + vspd.connect_repl_using_socket(client) + client = None + else: + write_bytes(client, RJCT) + + except (socket.error, OSError): + pass + finally: + if client is not None: + client.close() + + server_thread = threading.Thread(target = server_thread_func) + server_thread.setDaemon(True) + server_thread.start() + + frames = [] + f = sys._getframe() + while True: + f = f.f_back + if f is None: + break + frames.append(f) + frames.reverse() + cur_thread = vspd.new_thread() + for f in frames: + cur_thread.push_frame(f) + def replace_trace_func(): + for f in frames: + f.f_trace = cur_thread.trace_func + replace_trace_func() + sys.settrace(cur_thread.trace_func) + vspd.intercept_threads(for_attach = True) + + +# Alias for convenience of users of pydevd +settrace = enable_attach + + +def wait_for_attach(timeout = None): + """If a PTVS remote debugger is attached, returns immediately. Otherwise, + blocks until a remote debugger attaches to this process, or until the + optional timeout occurs. + + Parameters + ---------- + timeout : float, optional + The timeout for the operation in seconds (or fractions thereof). + """ + if vspd.DETACHED: + _attached.clear() + _attached.wait(timeout) + + +def break_into_debugger(): + """If a PTVS remote debugger is attached, pauses execution of all threads, + and breaks into the debugger with current thread as active. + """ + if not vspd.DETACHED: + vspd.SEND_BREAK_COMPLETE = thread.get_ident() + vspd.mark_all_threads_for_break() + +def is_attached(): + """Returns ``True`` if debugger is attached, ``False`` otherwise.""" + return not vspd.DETACHED diff --git a/src/test/pythonFiles/folding/empty.py b/src/test/pythonFiles/folding/empty.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/test/pythonFiles/folding/miscSamples.py b/src/test/pythonFiles/folding/miscSamples.py new file mode 100644 index 000000000000..01495fb0ee9c --- /dev/null +++ b/src/test/pythonFiles/folding/miscSamples.py @@ -0,0 +1,40 @@ + +def one(): + """comment""" + pass + +def two(): + value = """a doc string with single and double quotes "This is how it's done" """ + pass + +def three(): + """a doc string with single and double quotes "This is how it's done" + Another line + """ + pass + +def four(): + '''a doc string with single and double quotes "This is how it's done" ''' + pass + +def five(): + '''a doc string with single and double quotes "This is how it's done" + Another line + ''' + pass + +def six(): + """ s1 """ """ s2 """ + pass + +def seven(): + value = """ s1 """ """ s2 """ + pass + +def eight(): + ''' s1 ''' ''' s2 ''' + pass + +def nine(): + value = ''' s1 ''' ''' s2 ''' + pass diff --git a/src/test/pythonFiles/folding/noComments.py b/src/test/pythonFiles/folding/noComments.py new file mode 100644 index 000000000000..ca4d3f4140a6 --- /dev/null +++ b/src/test/pythonFiles/folding/noComments.py @@ -0,0 +1,278 @@ +__author__ = "Microsoft Corporation " +__version__ = "3.0.0.0" + +__all__ = ['enable_attach', 'wait_for_attach', 'break_into_debugger', 'settrace', 'is_attached', 'AttachAlreadyEnabledError'] + +import atexit +import getpass +import os +import os.path +import platform +import socket +import struct +import sys +import threading +try: + import thread +except ImportError: + import _thread as thread +try: + import ssl +except ImportError: + ssl = None + +import ptvsd.visualstudio_py_debugger as vspd +import ptvsd.visualstudio_py_repl as vspr +from ptvsd.visualstudio_py_util import to_bytes, read_bytes, read_int, read_string, write_bytes, write_int, write_string + +PTVS_VER = '2.2' +DEFAULT_PORT = 5678 +PTVSDBG_VER = 6 +PTVSDBG = to_bytes('PTVSDBG') +ACPT = to_bytes('ACPT') +RJCT = to_bytes('RJCT') +INFO = to_bytes('INFO') +ATCH = to_bytes('ATCH') +REPL = to_bytes('REPL') + +_attach_enabled = False +_attached = threading.Event() +vspd.DONT_DEBUG.append(os.path.normcase(__file__)) + + +class AttachAlreadyEnabledError(Exception): + """`ptvsd.enable_attach` has already been called in this process.""" + + +def enable_attach(secret, address = ('0.0.0.0', DEFAULT_PORT), certfile = None, keyfile = None, redirect_output = True): + """Enables Python Tools for Visual Studio to attach to this process remotely + to debug Python code. + + Parameters + ---------- + secret : str + Used to validate the clients - only those clients providing the valid + secret will be allowed to connect to this server. On client side, the + secret is prepended to the Qualifier string, separated from the + hostname by ``'@'``, e.g.: ``'secret@myhost.cloudapp.net:5678'``. If + secret is ``None``, there's no validation, and any client can connect + freely. + address : (str, int), optional + Specifies the interface and port on which the debugging server should + listen for TCP connections. It is in the same format as used for + regular sockets of the `socket.AF_INET` family, i.e. a tuple of + ``(hostname, port)``. On client side, the server is identified by the + Qualifier string in the usual ``'hostname:port'`` format, e.g.: + ``'myhost.cloudapp.net:5678'``. Default is ``('0.0.0.0', 5678)``. + certfile : str, optional + Used to enable SSL. If not specified, or if set to ``None``, the + connection between this program and the debugger will be unsecure, + and can be intercepted on the wire. If specified, the meaning of this + parameter is the same as for `ssl.wrap_socket`. + keyfile : str, optional + Used together with `certfile` when SSL is enabled. Its meaning is the + same as for ``ssl.wrap_socket``. + redirect_output : bool, optional + Specifies whether any output (on both `stdout` and `stderr`) produced + by this program should be sent to the debugger. Default is ``True``. + + Notes + ----- + This function returns immediately after setting up the debugging server, + and does not block program execution. If you need to block until debugger + is attached, call `ptvsd.wait_for_attach`. The debugger can be detached + and re-attached multiple times after `enable_attach` is called. + + This function can only be called once during the lifetime of the process. + On a second call, `AttachAlreadyEnabledError` is raised. In circumstances + where the caller does not control how many times the function will be + called (e.g. when a script with a single call is run more than once by + a hosting app or framework), the call should be wrapped in ``try..except``. + + Only the thread on which this function is called, and any threads that are + created after it returns, will be visible in the debugger once it is + attached. Any threads that are already running before this function is + called will not be visible. + """ + + if not ssl and (certfile or keyfile): + raise ValueError('could not import the ssl module - SSL is not supported on this version of Python') + + if sys.platform == 'cli': + import clr + x_tracing = clr.GetCurrentRuntime().GetLanguageByExtension('py').Options.Tracing + x_frames = clr.GetCurrentRuntime().GetLanguageByExtension('py').Options.Frames + if not x_tracing or not x_frames: + raise RuntimeError('IronPython must be started with -X:Tracing and -X:Frames options to support PTVS remote debugging.') + + global _attach_enabled + if _attach_enabled: + raise AttachAlreadyEnabledError('ptvsd.enable_attach() has already been called in this process.') + _attach_enabled = True + + atexit.register(vspd.detach_process_and_notify_debugger) + + server = socket.socket(proto=socket.IPPROTO_TCP) + server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server.bind(address) + server.listen(1) + def server_thread_func(): + while True: + client = None + raw_client = None + try: + client, addr = server.accept() + if certfile: + client = ssl.wrap_socket(client, server_side = True, ssl_version = ssl.PROTOCOL_TLSv1, certfile = certfile, keyfile = keyfile) + write_bytes(client, PTVSDBG) + write_int(client, PTVSDBG_VER) + + response = read_bytes(client, 7) + if response != PTVSDBG: + continue + dbg_ver = read_int(client) + if dbg_ver != PTVSDBG_VER: + continue + + client_secret = read_string(client) + if secret is None or secret == client_secret: + write_bytes(client, ACPT) + else: + write_bytes(client, RJCT) + continue + + response = read_bytes(client, 4) + + if response == INFO: + try: + pid = os.getpid() + except AttributeError: + pid = 0 + write_int(client, pid) + + exe = sys.executable or '' + write_string(client, exe) + + try: + username = getpass.getuser() + except AttributeError: + username = '' + write_string(client, username) + + try: + impl = platform.python_implementation() + except AttributeError: + try: + impl = sys.implementation.name + except AttributeError: + impl = 'Python' + + major, minor, micro, release_level, serial = sys.version_info + + os_and_arch = platform.system() + if os_and_arch == "": + os_and_arch = sys.platform + try: + if sys.maxsize > 2**32: + os_and_arch += ' 64-bit' + else: + os_and_arch += ' 32-bit' + except AttributeError: + pass + + version = '%s %s.%s.%s (%s)' % (impl, major, minor, micro, os_and_arch) + write_string(client, version) + + client.recv(1) + + elif response == ATCH: + debug_options = vspd.parse_debug_options(read_string(client)) + if redirect_output: + debug_options.add('RedirectOutput') + + if vspd.DETACHED: + write_bytes(client, ACPT) + try: + pid = os.getpid() + except AttributeError: + pid = 0 + write_int(client, pid) + + major, minor, micro, release_level, serial = sys.version_info + write_int(client, major) + write_int(client, minor) + write_int(client, micro) + + vspd.attach_process_from_socket(client, debug_options, report = True) + vspd.mark_all_threads_for_break(vspd.STEPPING_ATTACH_BREAK) + _attached.set() + client = None + else: + write_bytes(client, RJCT) + + elif response == REPL: + if not vspd.DETACHED: + write_bytes(client, ACPT) + vspd.connect_repl_using_socket(client) + client = None + else: + write_bytes(client, RJCT) + + except (socket.error, OSError): + pass + finally: + if client is not None: + client.close() + + server_thread = threading.Thread(target = server_thread_func) + server_thread.setDaemon(True) + server_thread.start() + + frames = [] + f = sys._getframe() + while True: + f = f.f_back + if f is None: + break + frames.append(f) + frames.reverse() + cur_thread = vspd.new_thread() + for f in frames: + cur_thread.push_frame(f) + def replace_trace_func(): + for f in frames: + f.f_trace = cur_thread.trace_func + replace_trace_func() + sys.settrace(cur_thread.trace_func) + vspd.intercept_threads(for_attach = True) + + +settrace = enable_attach + + +def wait_for_attach(timeout = None): + """If a PTVS remote debugger is attached, returns immediately. Otherwise, + blocks until a remote debugger attaches to this process, or until the + optional timeout occurs. + + Parameters + ---------- + timeout : float, optional + The timeout for the operation in seconds (or fractions thereof). + """ + if vspd.DETACHED: + _attached.clear() + _attached.wait(timeout) + + +def break_into_debugger(): + """If a PTVS remote debugger is attached, pauses execution of all threads, + and breaks into the debugger with current thread as active. + """ + if not vspd.DETACHED: + vspd.SEND_BREAK_COMPLETE = thread.get_ident() + vspd.mark_all_threads_for_break() + +def is_attached(): + """Returns ``True`` if debugger is attached, ``False`` otherwise.""" + return not vspd.DETACHED diff --git a/src/test/pythonFiles/folding/noDocStrings.py b/src/test/pythonFiles/folding/noDocStrings.py new file mode 100644 index 000000000000..9fd4b4874a57 --- /dev/null +++ b/src/test/pythonFiles/folding/noDocStrings.py @@ -0,0 +1,266 @@ +# Python Tools for Visual Studio +# Copyright(c) Microsoft Corporation +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the License); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at http://www.apache.org/licenses/LICENSE-2.0 +# +# THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS +# OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY +# IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +# MERCHANTABLITY OR NON-INFRINGEMENT. +# +# See the Apache Version 2.0 License for specific language governing +# permissions and limitations under the License. + +__author__ = "Microsoft Corporation " +__version__ = "3.0.0.0" + +__all__ = ['enable_attach', 'wait_for_attach', 'break_into_debugger', 'settrace', 'is_attached', 'AttachAlreadyEnabledError'] + +import atexit +import getpass +import os +import os.path +import platform +import socket +import struct +import sys +import threading +try: + import thread +except ImportError: + import _thread as thread +try: + import ssl +except ImportError: + ssl = None + +import ptvsd.visualstudio_py_debugger as vspd +import ptvsd.visualstudio_py_repl as vspr +from ptvsd.visualstudio_py_util import to_bytes, read_bytes, read_int, read_string, write_bytes, write_int, write_string + + +# The server (i.e. the Python app) waits on a TCP port provided. Whenever anything connects to that port, +# it immediately sends the octet sequence 'PTVSDBG', followed by version number represented as int64, +# and then waits for the client to respond with the same exact byte sequence. After signatures are thereby +# exchanged and found to match, the client is expected to provide a string secret (in the usual debugger +# string format, None/ACII/Unicode prefix + length + data), which can be an empty string to designate the +# lack of a specified secret. +# +# If the secret does not match the one expected by the server, it responds with 'RJCT', and then closes +# the connection. Otherwise, the server responds with 'ACPT', and awaits a 4-octet command. The following +# commands are recognized: +# +# 'INFO' +# Report information about the process. The server responds with the following information, in order: +# - Process ID (int64) +# - Executable name (string) +# - User name (string) +# - Implementation name (string) +# and then immediately closes connection. Note, all string fields can be empty or null strings. +# +# 'ATCH' +# Attach debugger to the process. If successful, the server responds with 'ACPT', followed by process ID +# (int64), and then the Python language version that the server is running represented by three int64s - +# major, minor, micro; From there on the socket is assumed to be using the normal PTVS debugging protocol. +# If attaching was not successful (which can happen if some other debugger is already attached), the server +# responds with 'RJCT' and closes the connection. +# +# 'REPL' +# Attach REPL to the process. If successful, the server responds with 'ACPT', and from there on the socket +# is assumed to be using the normal PTVS REPL protocol. If not successful (which can happen if there is +# no debugger attached), the server responds with 'RJCT' and closes the connection. + +PTVS_VER = '2.2' +DEFAULT_PORT = 5678 +PTVSDBG_VER = 6 # must be kept in sync with DebuggerProtocolVersion in PythonRemoteProcess.cs +PTVSDBG = to_bytes('PTVSDBG') +ACPT = to_bytes('ACPT') +RJCT = to_bytes('RJCT') +INFO = to_bytes('INFO') +ATCH = to_bytes('ATCH') +REPL = to_bytes('REPL') + +_attach_enabled = False +_attached = threading.Event() +vspd.DONT_DEBUG.append(os.path.normcase(__file__)) + + +class AttachAlreadyEnabledError(Exception): + + +def enable_attach(secret, address = ('0.0.0.0', DEFAULT_PORT), certfile = None, keyfile = None, redirect_output = True): + if not ssl and (certfile or keyfile): + raise ValueError('could not import the ssl module - SSL is not supported on this version of Python') + + if sys.platform == 'cli': + # Check that IronPython was launched with -X:Frames and -X:Tracing, since we can't register our trace + # func on the thread that calls enable_attach otherwise + import clr + x_tracing = clr.GetCurrentRuntime().GetLanguageByExtension('py').Options.Tracing + x_frames = clr.GetCurrentRuntime().GetLanguageByExtension('py').Options.Frames + if not x_tracing or not x_frames: + raise RuntimeError('IronPython must be started with -X:Tracing and -X:Frames options to support PTVS remote debugging.') + + global _attach_enabled + if _attach_enabled: + raise AttachAlreadyEnabledError('ptvsd.enable_attach() has already been called in this process.') + _attach_enabled = True + + atexit.register(vspd.detach_process_and_notify_debugger) + + server = socket.socket(proto=socket.IPPROTO_TCP) + server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server.bind(address) + server.listen(1) + def server_thread_func(): + while True: + client = None + raw_client = None + try: + client, addr = server.accept() + if certfile: + client = ssl.wrap_socket(client, server_side = True, ssl_version = ssl.PROTOCOL_TLSv1, certfile = certfile, keyfile = keyfile) + write_bytes(client, PTVSDBG) + write_int(client, PTVSDBG_VER) + + response = read_bytes(client, 7) + if response != PTVSDBG: + continue + dbg_ver = read_int(client) + if dbg_ver != PTVSDBG_VER: + continue + + client_secret = read_string(client) + if secret is None or secret == client_secret: + write_bytes(client, ACPT) + else: + write_bytes(client, RJCT) + continue + + response = read_bytes(client, 4) + + if response == INFO: + try: + pid = os.getpid() + except AttributeError: + pid = 0 + write_int(client, pid) + + exe = sys.executable or '' + write_string(client, exe) + + try: + username = getpass.getuser() + except AttributeError: + username = '' + write_string(client, username) + + try: + impl = platform.python_implementation() + except AttributeError: + try: + impl = sys.implementation.name + except AttributeError: + impl = 'Python' + + major, minor, micro, release_level, serial = sys.version_info + + os_and_arch = platform.system() + if os_and_arch == "": + os_and_arch = sys.platform + try: + if sys.maxsize > 2**32: + os_and_arch += ' 64-bit' + else: + os_and_arch += ' 32-bit' + except AttributeError: + pass + + version = '%s %s.%s.%s (%s)' % (impl, major, minor, micro, os_and_arch) + write_string(client, version) + + # Don't just drop the connection - let the debugger close it after it finishes reading. + client.recv(1) + + elif response == ATCH: + debug_options = vspd.parse_debug_options(read_string(client)) + if redirect_output: + debug_options.add('RedirectOutput') + + if vspd.DETACHED: + write_bytes(client, ACPT) + try: + pid = os.getpid() + except AttributeError: + pid = 0 + write_int(client, pid) + + major, minor, micro, release_level, serial = sys.version_info + write_int(client, major) + write_int(client, minor) + write_int(client, micro) + + vspd.attach_process_from_socket(client, debug_options, report = True) + vspd.mark_all_threads_for_break(vspd.STEPPING_ATTACH_BREAK) + _attached.set() + client = None + else: + write_bytes(client, RJCT) + + elif response == REPL: + if not vspd.DETACHED: + write_bytes(client, ACPT) + vspd.connect_repl_using_socket(client) + client = None + else: + write_bytes(client, RJCT) + + except (socket.error, OSError): + pass + finally: + if client is not None: + client.close() + + server_thread = threading.Thread(target = server_thread_func) + server_thread.setDaemon(True) + server_thread.start() + + frames = [] + f = sys._getframe() + while True: + f = f.f_back + if f is None: + break + frames.append(f) + frames.reverse() + cur_thread = vspd.new_thread() + for f in frames: + cur_thread.push_frame(f) + def replace_trace_func(): + for f in frames: + f.f_trace = cur_thread.trace_func + replace_trace_func() + sys.settrace(cur_thread.trace_func) + vspd.intercept_threads(for_attach = True) + + +# Alias for convenience of users of pydevd +settrace = enable_attach + + +def wait_for_attach(timeout = None): + if vspd.DETACHED: + _attached.clear() + _attached.wait(timeout) + + +def break_into_debugger(): + if not vspd.DETACHED: + vspd.SEND_BREAK_COMPLETE = thread.get_ident() + vspd.mark_all_threads_for_break() + +def is_attached(): + return not vspd.DETACHED diff --git a/src/test/pythonFiles/folding/visualstudio_ipython_repl.py b/src/test/pythonFiles/folding/visualstudio_ipython_repl.py new file mode 100644 index 000000000000..33aa109de971 --- /dev/null +++ b/src/test/pythonFiles/folding/visualstudio_ipython_repl.py @@ -0,0 +1,430 @@ +# Python Tools for Visual Studio +# Copyright(c) Microsoft Corporation +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the License); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at http://www.apache.org/licenses/LICENSE-2.0 +# +# THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS +# OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY +# IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +# MERCHANTABLITY OR NON-INFRINGEMENT. +# +# See the Apache Version 2.0 License for specific language governing +# permissions and limitations under the License. + +"""Implements REPL support over IPython/ZMQ for VisualStudio""" + +__author__ = "Microsoft Corporation " +__version__ = "3.0.0.0" + +import re +import sys +from visualstudio_py_repl import BasicReplBackend, ReplBackend, UnsupportedReplException, _command_line_to_args_list +from visualstudio_py_util import to_bytes +try: + import thread +except: + import _thread as thread # Renamed as Py3k + +from base64 import decodestring + +try: + import IPython +except ImportError: + exc_value = sys.exc_info()[1] + raise UnsupportedReplException('IPython mode requires IPython 0.11 or later: ' + str(exc_value)) + +def is_ipython_versionorgreater(major, minor): + """checks if we are at least a specific IPython version""" + match = re.match('(\d+).(\d+)', IPython.__version__) + if match: + groups = match.groups() + if int(groups[0]) > major: + return True + elif int(groups[0]) == major: + return int(groups[1]) >= minor + + return False + +remove_escapes = re.compile(r'\x1b[^m]*m') + +try: + if is_ipython_versionorgreater(3, 0): + from IPython.kernel import KernelManager + from IPython.kernel.channels import HBChannel + from IPython.kernel.threaded import (ThreadedZMQSocketChannel, ThreadedKernelClient as KernelClient) + ShellChannel = StdInChannel = IOPubChannel = ThreadedZMQSocketChannel + elif is_ipython_versionorgreater(1, 0): + from IPython.kernel import KernelManager, KernelClient + from IPython.kernel.channels import ShellChannel, HBChannel, StdInChannel, IOPubChannel + else: + import IPython.zmq + KernelClient = object # was split out from KernelManager in 1.0 + from IPython.zmq.kernelmanager import (KernelManager, + ShellSocketChannel as ShellChannel, + SubSocketChannel as IOPubChannel, + StdInSocketChannel as StdInChannel, + HBSocketChannel as HBChannel) + + from IPython.utils.traitlets import Type +except ImportError: + exc_value = sys.exc_info()[1] + raise UnsupportedReplException(str(exc_value)) + + +# TODO: SystemExit exceptions come back to us as strings, can we automatically exit when ones raised somehow? + +##### +# Channels which forward events + +# Description of the messaging protocol +# http://ipython.scipy.org/doc/manual/html/development/messaging.html + + +class DefaultHandler(object): + def unknown_command(self, content): + import pprint + print('unknown command ' + str(type(self))) + pprint.pprint(content) + + def call_handlers(self, msg): + # msg_type: + # execute_reply + msg_type = 'handle_' + msg['msg_type'] + + getattr(self, msg_type, self.unknown_command)(msg['content']) + +class VsShellChannel(DefaultHandler, ShellChannel): + + def handle_execute_reply(self, content): + # we could have a payload here... + payload = content['payload'] + + for item in payload: + data = item.get('data') + if data is not None: + try: + # Could be named km.sub_channel for very old IPython, but + # those versions should not put 'data' in this payload + write_data = self._vs_backend.km.iopub_channel.write_data + except AttributeError: + pass + else: + write_data(data) + continue + + output = item.get('text', None) + if output is not None: + self._vs_backend.write_stdout(output) + self._vs_backend.send_command_executed() + + def handle_inspect_reply(self, content): + self.handle_object_info_reply(content) + + def handle_object_info_reply(self, content): + self._vs_backend.object_info_reply = content + self._vs_backend.members_lock.release() + + def handle_complete_reply(self, content): + self._vs_backend.complete_reply = content + self._vs_backend.members_lock.release() + + def handle_kernel_info_reply(self, content): + self._vs_backend.write_stdout(content['banner']) + + +class VsIOPubChannel(DefaultHandler, IOPubChannel): + def call_handlers(self, msg): + # only output events from our session or no sessions + # https://pytools.codeplex.com/workitem/1622 + parent = msg.get('parent_header') + if not parent or parent.get('session') == self.session.session: + msg_type = 'handle_' + msg['msg_type'] + getattr(self, msg_type, self.unknown_command)(msg['content']) + + def handle_display_data(self, content): + # called when user calls display() + data = content.get('data', None) + + if data is not None: + self.write_data(data) + + def handle_stream(self, content): + stream_name = content['name'] + if is_ipython_versionorgreater(3, 0): + output = content['text'] + else: + output = content['data'] + if stream_name == 'stdout': + self._vs_backend.write_stdout(output) + elif stream_name == 'stderr': + self._vs_backend.write_stderr(output) + # TODO: stdin can show up here, do we echo that? + + def handle_execute_result(self, content): + self.handle_execute_output(content) + + def handle_execute_output(self, content): + # called when an expression statement is printed, we treat + # identical to stream output but it always goes to stdout + output = content['data'] + execution_count = content['execution_count'] + self._vs_backend.execution_count = execution_count + 1 + self._vs_backend.send_prompt( + '\r\nIn [%d]: ' % (execution_count + 1), + ' ' + ('.' * (len(str(execution_count + 1)) + 2)) + ': ', + allow_multiple_statements=True + ) + self.write_data(output, execution_count) + + def write_data(self, data, execution_count = None): + output_xaml = data.get('application/xaml+xml', None) + if output_xaml is not None: + try: + if isinstance(output_xaml, str) and sys.version_info[0] >= 3: + output_xaml = output_xaml.encode('ascii') + self._vs_backend.write_xaml(decodestring(output_xaml)) + self._vs_backend.write_stdout('\n') + return + except: + pass + + output_png = data.get('image/png', None) + if output_png is not None: + try: + if isinstance(output_png, str) and sys.version_info[0] >= 3: + output_png = output_png.encode('ascii') + self._vs_backend.write_png(decodestring(output_png)) + self._vs_backend.write_stdout('\n') + return + except: + pass + + output_str = data.get('text/plain', None) + if output_str is not None: + if execution_count is not None: + if '\n' in output_str: + output_str = '\n' + output_str + output_str = 'Out[' + str(execution_count) + ']: ' + output_str + + self._vs_backend.write_stdout(output_str) + self._vs_backend.write_stdout('\n') + return + + def handle_error(self, content): + # TODO: this includes escape sequences w/ color, we need to unescape that + ename = content['ename'] + evalue = content['evalue'] + tb = content['traceback'] + self._vs_backend.write_stderr('\n'.join(tb)) + self._vs_backend.write_stdout('\n') + + def handle_execute_input(self, content): + # just a rebroadcast of the command to be executed, can be ignored + self._vs_backend.execution_count += 1 + self._vs_backend.send_prompt( + '\r\nIn [%d]: ' % (self._vs_backend.execution_count), + ' ' + ('.' * (len(str(self._vs_backend.execution_count)) + 2)) + ': ', + allow_multiple_statements=True + ) + pass + + def handle_status(self, content): + pass + + # Backwards compat w/ 0.13 + handle_pyin = handle_execute_input + handle_pyout = handle_execute_output + handle_pyerr = handle_error + + +class VsStdInChannel(DefaultHandler, StdInChannel): + def handle_input_request(self, content): + # queue this to another thread so we don't block the channel + def read_and_respond(): + value = self._vs_backend.read_line() + + self.input(value) + + thread.start_new_thread(read_and_respond, ()) + + +class VsHBChannel(DefaultHandler, HBChannel): + pass + + +class VsKernelManager(KernelManager, KernelClient): + shell_channel_class = Type(VsShellChannel) + if is_ipython_versionorgreater(1, 0): + iopub_channel_class = Type(VsIOPubChannel) + else: + sub_channel_class = Type(VsIOPubChannel) + stdin_channel_class = Type(VsStdInChannel) + hb_channel_class = Type(VsHBChannel) + + +class IPythonBackend(ReplBackend): + def __init__(self, mod_name = '__main__', launch_file = None): + ReplBackend.__init__(self) + self.launch_file = launch_file + self.mod_name = mod_name + self.km = VsKernelManager() + + if is_ipython_versionorgreater(0, 13): + # http://pytools.codeplex.com/workitem/759 + # IPython stopped accepting the ipython flag and switched to launcher, the new + # default is what we want though. + self.km.start_kernel(**{'extra_arguments': self.get_extra_arguments()}) + else: + self.km.start_kernel(**{'ipython': True, 'extra_arguments': self.get_extra_arguments()}) + self.km.start_channels() + self.exit_lock = thread.allocate_lock() + self.exit_lock.acquire() # used as an event + self.members_lock = thread.allocate_lock() + self.members_lock.acquire() + + self.km.shell_channel._vs_backend = self + self.km.stdin_channel._vs_backend = self + if is_ipython_versionorgreater(1, 0): + self.km.iopub_channel._vs_backend = self + else: + self.km.sub_channel._vs_backend = self + self.km.hb_channel._vs_backend = self + self.execution_count = 1 + + def get_extra_arguments(self): + if sys.version <= '2.': + return [unicode('--pylab=inline')] + return ['--pylab=inline'] + + def execute_file_as_main(self, filename, arg_string): + f = open(filename, 'rb') + try: + contents = f.read().replace(to_bytes("\r\n"), to_bytes("\n")) + finally: + f.close() + args = [filename] + _command_line_to_args_list(arg_string) + code = ''' +import sys +sys.argv = %(args)r +__file__ = %(filename)r +del sys +exec(compile(%(contents)r, %(filename)r, 'exec')) +''' % {'filename' : filename, 'contents':contents, 'args': args} + + self.run_command(code, True) + + def execution_loop(self): + # we've got a bunch of threads setup for communication, we just block + # here until we're requested to exit. + self.send_prompt('\r\nIn [1]: ', ' ...: ', allow_multiple_statements=True) + self.exit_lock.acquire() + + def run_command(self, command, silent = False): + if is_ipython_versionorgreater(3, 0): + self.km.execute(command, silent) + else: + self.km.shell_channel.execute(command, silent) + + def execute_file_ex(self, filetype, filename, args): + if filetype == 'script': + self.execute_file_as_main(filename, args) + else: + raise NotImplementedError("Cannot execute %s file" % filetype) + + def exit_process(self): + self.exit_lock.release() + + def get_members(self, expression): + """returns a tuple of the type name, instance members, and type members""" + text = expression + '.' + if is_ipython_versionorgreater(3, 0): + self.km.complete(text) + else: + self.km.shell_channel.complete(text, text, 1) + + self.members_lock.acquire() + + reply = self.complete_reply + + res = {} + text_len = len(text) + for member in reply['matches']: + res[member[text_len:]] = 'object' + + return ('unknown', res, {}) + + def get_signatures(self, expression): + """returns doc, args, vargs, varkw, defaults.""" + + if is_ipython_versionorgreater(3, 0): + self.km.inspect(expression, None, 2) + else: + self.km.shell_channel.object_info(expression) + + self.members_lock.acquire() + + reply = self.object_info_reply + if is_ipython_versionorgreater(3, 0): + data = reply['data'] + text = data['text/plain'] + text = remove_escapes.sub('', text) + return [(text, (), None, None, [])] + else: + argspec = reply['argspec'] + defaults = argspec['defaults'] + if defaults is not None: + defaults = [repr(default) for default in defaults] + else: + defaults = [] + return [(reply['docstring'], argspec['args'], argspec['varargs'], argspec['varkw'], defaults)] + + def interrupt_main(self): + """aborts the current running command""" + self.km.interrupt_kernel() + + def set_current_module(self, module): + pass + + def get_module_names(self): + """returns a list of module names""" + return [] + + def flush(self): + pass + + def init_debugger(self): + from os import path + self.run_command(''' +def __visualstudio_debugger_init(): + import sys + sys.path.append(''' + repr(path.dirname(__file__)) + ''') + import visualstudio_py_debugger + new_thread = visualstudio_py_debugger.new_thread() + sys.settrace(new_thread.trace_func) + visualstudio_py_debugger.intercept_threads(True) + +__visualstudio_debugger_init() +del __visualstudio_debugger_init +''', True) + + def attach_process(self, port, debugger_id): + self.run_command(''' +def __visualstudio_debugger_attach(): + import visualstudio_py_debugger + + def do_detach(): + visualstudio_py_debugger.DETACH_CALLBACKS.remove(do_detach) + + visualstudio_py_debugger.DETACH_CALLBACKS.append(do_detach) + visualstudio_py_debugger.attach_process(''' + str(port) + ''', ''' + repr(debugger_id) + ''', report = True, block = True) + +__visualstudio_debugger_attach() +del __visualstudio_debugger_attach +''', True) + +class IPythonBackendWithoutPyLab(IPythonBackend): + def get_extra_arguments(self): + return [] \ No newline at end of file diff --git a/src/test/pythonFiles/folding/visualstudio_ipython_repl_double_quotes.py b/src/test/pythonFiles/folding/visualstudio_ipython_repl_double_quotes.py new file mode 100644 index 000000000000..473046639147 --- /dev/null +++ b/src/test/pythonFiles/folding/visualstudio_ipython_repl_double_quotes.py @@ -0,0 +1,430 @@ +# Python Tools for Visual Studio +# Copyright(c) Microsoft Corporation +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the License); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at http://www.apache.org/licenses/LICENSE-2.0 +# +# THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS +# OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY +# IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +# MERCHANTABLITY OR NON-INFRINGEMENT. +# +# See the Apache Version 2.0 License for specific language governing +# permissions and limitations under the License. + +"""Implements REPL support over IPython/ZMQ for VisualStudio""" + +__author__ = "Microsoft Corporation " +__version__ = "3.0.0.0" + +import re +import sys +from visualstudio_py_repl import BasicReplBackend, ReplBackend, UnsupportedReplException, _command_line_to_args_list +from visualstudio_py_util import to_bytes +try: + import thread +except: + import _thread as thread # Renamed as Py3k + +from base64 import decodestring + +try: + import IPython +except ImportError: + exc_value = sys.exc_info()[1] + raise UnsupportedReplException('IPython mode requires IPython 0.11 or later: ' + str(exc_value)) + +def is_ipython_versionorgreater(major, minor): + """checks if we are at least a specific IPython version""" + match = re.match('(\d+).(\d+)', IPython.__version__) + if match: + groups = match.groups() + if int(groups[0]) > major: + return True + elif int(groups[0]) == major: + return int(groups[1]) >= minor + + return False + +remove_escapes = re.compile(r'\x1b[^m]*m') + +try: + if is_ipython_versionorgreater(3, 0): + from IPython.kernel import KernelManager + from IPython.kernel.channels import HBChannel + from IPython.kernel.threaded import (ThreadedZMQSocketChannel, ThreadedKernelClient as KernelClient) + ShellChannel = StdInChannel = IOPubChannel = ThreadedZMQSocketChannel + elif is_ipython_versionorgreater(1, 0): + from IPython.kernel import KernelManager, KernelClient + from IPython.kernel.channels import ShellChannel, HBChannel, StdInChannel, IOPubChannel + else: + import IPython.zmq + KernelClient = object # was split out from KernelManager in 1.0 + from IPython.zmq.kernelmanager import (KernelManager, + ShellSocketChannel as ShellChannel, + SubSocketChannel as IOPubChannel, + StdInSocketChannel as StdInChannel, + HBSocketChannel as HBChannel) + + from IPython.utils.traitlets import Type +except ImportError: + exc_value = sys.exc_info()[1] + raise UnsupportedReplException(str(exc_value)) + + +# TODO: SystemExit exceptions come back to us as strings, can we automatically exit when ones raised somehow? + +##### +# Channels which forward events + +# Description of the messaging protocol +# http://ipython.scipy.org/doc/manual/html/development/messaging.html + + +class DefaultHandler(object): + def unknown_command(self, content): + import pprint + print('unknown command ' + str(type(self))) + pprint.pprint(content) + + def call_handlers(self, msg): + # msg_type: + # execute_reply + msg_type = 'handle_' + msg['msg_type'] + + getattr(self, msg_type, self.unknown_command)(msg['content']) + +class VsShellChannel(DefaultHandler, ShellChannel): + + def handle_execute_reply(self, content): + # we could have a payload here... + payload = content['payload'] + + for item in payload: + data = item.get('data') + if data is not None: + try: + # Could be named km.sub_channel for very old IPython, but + # those versions should not put 'data' in this payload + write_data = self._vs_backend.km.iopub_channel.write_data + except AttributeError: + pass + else: + write_data(data) + continue + + output = item.get('text', None) + if output is not None: + self._vs_backend.write_stdout(output) + self._vs_backend.send_command_executed() + + def handle_inspect_reply(self, content): + self.handle_object_info_reply(content) + + def handle_object_info_reply(self, content): + self._vs_backend.object_info_reply = content + self._vs_backend.members_lock.release() + + def handle_complete_reply(self, content): + self._vs_backend.complete_reply = content + self._vs_backend.members_lock.release() + + def handle_kernel_info_reply(self, content): + self._vs_backend.write_stdout(content['banner']) + + +class VsIOPubChannel(DefaultHandler, IOPubChannel): + def call_handlers(self, msg): + # only output events from our session or no sessions + # https://pytools.codeplex.com/workitem/1622 + parent = msg.get('parent_header') + if not parent or parent.get('session') == self.session.session: + msg_type = 'handle_' + msg['msg_type'] + getattr(self, msg_type, self.unknown_command)(msg['content']) + + def handle_display_data(self, content): + # called when user calls display() + data = content.get('data', None) + + if data is not None: + self.write_data(data) + + def handle_stream(self, content): + stream_name = content['name'] + if is_ipython_versionorgreater(3, 0): + output = content['text'] + else: + output = content['data'] + if stream_name == 'stdout': + self._vs_backend.write_stdout(output) + elif stream_name == 'stderr': + self._vs_backend.write_stderr(output) + # TODO: stdin can show up here, do we echo that? + + def handle_execute_result(self, content): + self.handle_execute_output(content) + + def handle_execute_output(self, content): + # called when an expression statement is printed, we treat + # identical to stream output but it always goes to stdout + output = content['data'] + execution_count = content['execution_count'] + self._vs_backend.execution_count = execution_count + 1 + self._vs_backend.send_prompt( + '\r\nIn [%d]: ' % (execution_count + 1), + ' ' + ('.' * (len(str(execution_count + 1)) + 2)) + ': ', + allow_multiple_statements=True + ) + self.write_data(output, execution_count) + + def write_data(self, data, execution_count = None): + output_xaml = data.get('application/xaml+xml', None) + if output_xaml is not None: + try: + if isinstance(output_xaml, str) and sys.version_info[0] >= 3: + output_xaml = output_xaml.encode('ascii') + self._vs_backend.write_xaml(decodestring(output_xaml)) + self._vs_backend.write_stdout('\n') + return + except: + pass + + output_png = data.get('image/png', None) + if output_png is not None: + try: + if isinstance(output_png, str) and sys.version_info[0] >= 3: + output_png = output_png.encode('ascii') + self._vs_backend.write_png(decodestring(output_png)) + self._vs_backend.write_stdout('\n') + return + except: + pass + + output_str = data.get('text/plain', None) + if output_str is not None: + if execution_count is not None: + if '\n' in output_str: + output_str = '\n' + output_str + output_str = 'Out[' + str(execution_count) + ']: ' + output_str + + self._vs_backend.write_stdout(output_str) + self._vs_backend.write_stdout('\n') + return + + def handle_error(self, content): + # TODO: this includes escape sequences w/ color, we need to unescape that + ename = content['ename'] + evalue = content['evalue'] + tb = content['traceback'] + self._vs_backend.write_stderr('\n'.join(tb)) + self._vs_backend.write_stdout('\n') + + def handle_execute_input(self, content): + # just a rebroadcast of the command to be executed, can be ignored + self._vs_backend.execution_count += 1 + self._vs_backend.send_prompt( + '\r\nIn [%d]: ' % (self._vs_backend.execution_count), + ' ' + ('.' * (len(str(self._vs_backend.execution_count)) + 2)) + ': ', + allow_multiple_statements=True + ) + pass + + def handle_status(self, content): + pass + + # Backwards compat w/ 0.13 + handle_pyin = handle_execute_input + handle_pyout = handle_execute_output + handle_pyerr = handle_error + + +class VsStdInChannel(DefaultHandler, StdInChannel): + def handle_input_request(self, content): + # queue this to another thread so we don't block the channel + def read_and_respond(): + value = self._vs_backend.read_line() + + self.input(value) + + thread.start_new_thread(read_and_respond, ()) + + +class VsHBChannel(DefaultHandler, HBChannel): + pass + + +class VsKernelManager(KernelManager, KernelClient): + shell_channel_class = Type(VsShellChannel) + if is_ipython_versionorgreater(1, 0): + iopub_channel_class = Type(VsIOPubChannel) + else: + sub_channel_class = Type(VsIOPubChannel) + stdin_channel_class = Type(VsStdInChannel) + hb_channel_class = Type(VsHBChannel) + + +class IPythonBackend(ReplBackend): + def __init__(self, mod_name = '__main__', launch_file = None): + ReplBackend.__init__(self) + self.launch_file = launch_file + self.mod_name = mod_name + self.km = VsKernelManager() + + if is_ipython_versionorgreater(0, 13): + # http://pytools.codeplex.com/workitem/759 + # IPython stopped accepting the ipython flag and switched to launcher, the new + # default is what we want though. + self.km.start_kernel(**{'extra_arguments': self.get_extra_arguments()}) + else: + self.km.start_kernel(**{'ipython': True, 'extra_arguments': self.get_extra_arguments()}) + self.km.start_channels() + self.exit_lock = thread.allocate_lock() + self.exit_lock.acquire() # used as an event + self.members_lock = thread.allocate_lock() + self.members_lock.acquire() + + self.km.shell_channel._vs_backend = self + self.km.stdin_channel._vs_backend = self + if is_ipython_versionorgreater(1, 0): + self.km.iopub_channel._vs_backend = self + else: + self.km.sub_channel._vs_backend = self + self.km.hb_channel._vs_backend = self + self.execution_count = 1 + + def get_extra_arguments(self): + if sys.version <= '2.': + return [unicode('--pylab=inline')] + return ['--pylab=inline'] + + def execute_file_as_main(self, filename, arg_string): + f = open(filename, 'rb') + try: + contents = f.read().replace(to_bytes("\r\n"), to_bytes("\n")) + finally: + f.close() + args = [filename] + _command_line_to_args_list(arg_string) + code = """ +import sys +sys.argv = %(args)r +__file__ = %(filename)r +del sys +exec(compile(%(contents)r, %(filename)r, 'exec')) +""" % {'filename' : filename, 'contents':contents, 'args': args} + + self.run_command(code, True) + + def execution_loop(self): + # we've got a bunch of threads setup for communication, we just block + # here until we're requested to exit. + self.send_prompt('\r\nIn [1]: ', ' ...: ', allow_multiple_statements=True) + self.exit_lock.acquire() + + def run_command(self, command, silent = False): + if is_ipython_versionorgreater(3, 0): + self.km.execute(command, silent) + else: + self.km.shell_channel.execute(command, silent) + + def execute_file_ex(self, filetype, filename, args): + if filetype == 'script': + self.execute_file_as_main(filename, args) + else: + raise NotImplementedError("Cannot execute %s file" % filetype) + + def exit_process(self): + self.exit_lock.release() + + def get_members(self, expression): + """returns a tuple of the type name, instance members, and type members""" + text = expression + '.' + if is_ipython_versionorgreater(3, 0): + self.km.complete(text) + else: + self.km.shell_channel.complete(text, text, 1) + + self.members_lock.acquire() + + reply = self.complete_reply + + res = {} + text_len = len(text) + for member in reply['matches']: + res[member[text_len:]] = 'object' + + return ('unknown', res, {}) + + def get_signatures(self, expression): + """returns doc, args, vargs, varkw, defaults.""" + + if is_ipython_versionorgreater(3, 0): + self.km.inspect(expression, None, 2) + else: + self.km.shell_channel.object_info(expression) + + self.members_lock.acquire() + + reply = self.object_info_reply + if is_ipython_versionorgreater(3, 0): + data = reply['data'] + text = data['text/plain'] + text = remove_escapes.sub('', text) + return [(text, (), None, None, [])] + else: + argspec = reply['argspec'] + defaults = argspec['defaults'] + if defaults is not None: + defaults = [repr(default) for default in defaults] + else: + defaults = [] + return [(reply['docstring'], argspec['args'], argspec['varargs'], argspec['varkw'], defaults)] + + def interrupt_main(self): + """aborts the current running command""" + self.km.interrupt_kernel() + + def set_current_module(self, module): + pass + + def get_module_names(self): + """returns a list of module names""" + return [] + + def flush(self): + pass + + def init_debugger(self): + from os import path + self.run_command(""" +def __visualstudio_debugger_init(): + import sys + sys.path.append(""" + repr(path.dirname(__file__)) + """) + import visualstudio_py_debugger + new_thread = visualstudio_py_debugger.new_thread() + sys.settrace(new_thread.trace_func) + visualstudio_py_debugger.intercept_threads(True) + +__visualstudio_debugger_init() +del __visualstudio_debugger_init +""", True) + + def attach_process(self, port, debugger_id): + self.run_command(""" +def __visualstudio_debugger_attach(): + import visualstudio_py_debugger + + def do_detach(): + visualstudio_py_debugger.DETACH_CALLBACKS.remove(do_detach) + + visualstudio_py_debugger.DETACH_CALLBACKS.append(do_detach) + visualstudio_py_debugger.attach_process(""" + str(port) + """, """ + repr(debugger_id) + """, report = True, block = True) + +__visualstudio_debugger_attach() +del __visualstudio_debugger_attach +""", True) + +class IPythonBackendWithoutPyLab(IPythonBackend): + def get_extra_arguments(self): + return [] diff --git a/src/test/pythonFiles/folding/visualstudio_py_debugger.py b/src/test/pythonFiles/folding/visualstudio_py_debugger.py new file mode 100644 index 000000000000..ec18ff8c63b0 --- /dev/null +++ b/src/test/pythonFiles/folding/visualstudio_py_debugger.py @@ -0,0 +1,644 @@ +# Python Tools for Visual Studio +# Copyright(c) Microsoft Corporation +# All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the License); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at http://www.apache.org/licenses/LICENSE-2.0 +# +# THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS +# OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY +# IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +# MERCHANTABLITY OR NON-INFRINGEMENT. +# +# See the Apache Version 2.0 License for specific language governing +# permissions and limitations under the License. +# With number of modifications by Don Jayamanne + +from __future__ import with_statement + +__author__ = "Microsoft Corporation " +__version__ = "3.0.0.0" + +# This module MUST NOT import threading in global scope. This is because in a direct (non-ptvsd) +# attach scenario, it is loaded on the injected debugger attach thread, and if threading module +# hasn't been loaded already, it will assume that the thread on which it is being loaded is the +# main thread. This will cause issues when the thread goes away after attach completes. +_threading = None + +import sys +import ctypes +try: + import thread +except ImportError: + import _thread as thread +import socket +import struct +import weakref +import traceback +import types +import bisect +from os import path +import ntpath +import runpy +import datetime +from codecs import BOM_UTF8 + +try: + # In the local attach scenario, visualstudio_py_util is injected into globals() + # by PyDebugAttach before loading this module, and cannot be imported. + _vspu = visualstudio_py_util +except: + try: + import visualstudio_py_util as _vspu + except ImportError: + import ptvsd.visualstudio_py_util as _vspu + +to_bytes = _vspu.to_bytes +exec_file = _vspu.exec_file +exec_module = _vspu.exec_module +exec_code = _vspu.exec_code +read_bytes = _vspu.read_bytes +read_int = _vspu.read_int +read_string = _vspu.read_string +write_bytes = _vspu.write_bytes +write_int = _vspu.write_int +write_string = _vspu.write_string +safe_repr = _vspu.SafeRepr() + +try: + # In the local attach scenario, visualstudio_py_repl is injected into globals() + # by PyDebugAttach before loading this module, and cannot be imported. + _vspr = visualstudio_py_repl +except: + try: + import visualstudio_py_repl as _vspr + except ImportError: + import ptvsd.visualstudio_py_repl as _vspr + +try: + import stackless +except ImportError: + stackless = None + +try: + xrange +except: + xrange = range + +if sys.platform == 'cli': + import clr + from System.Runtime.CompilerServices import ConditionalWeakTable + IPY_SEEN_MODULES = ConditionalWeakTable[object, object]() + +# Import encodings early to avoid import on the debugger thread, which may cause deadlock +from encodings import utf_8 + +# WARNING: Avoid imports beyond this point, specifically on the debugger thread, as this may cause +# deadlock where the debugger thread performs an import while a user thread has the import lock + +# save start_new_thread so we can call it later, we'll intercept others calls to it. + +debugger_dll_handle = None +DETACHED = True +def thread_creator(func, args, kwargs = {}, *extra_args): + if not isinstance(args, tuple): + # args is not a tuple. This may be because we have become bound to a + # class, which has offset our arguments by one. + if isinstance(kwargs, tuple): + func, args = args, kwargs + kwargs = extra_args[0] if len(extra_args) > 0 else {} + + return _start_new_thread(new_thread_wrapper, (func, args, kwargs)) + +_start_new_thread = thread.start_new_thread +THREADS = {} +THREADS_LOCK = thread.allocate_lock() +MODULES = [] + +BREAK_ON_SYSTEMEXIT_ZERO = False +DEBUG_STDLIB = False +DJANGO_DEBUG = False + +RICH_EXCEPTIONS = False +IGNORE_DJANGO_TEMPLATE_WARNINGS = False + +# Py3k compat - alias unicode to str +try: + unicode +except: + unicode = str + +# A value of a synthesized child. The string is passed through to the variable list, and type is not displayed at all. +class SynthesizedValue(object): + def __init__(self, repr_value='', len_value=None): + self.repr_value = repr_value + self.len_value = len_value + def __repr__(self): + return self.repr_value + def __len__(self): + return self.len_value + +# Specifies list of files not to debug. Can be extended by other modules +# (the REPL does this for $attach support and not stepping into the REPL). +DONT_DEBUG = [path.normcase(__file__), path.normcase(_vspu.__file__)] +if sys.version_info >= (3, 3): + DONT_DEBUG.append(path.normcase('')) +if sys.version_info >= (3, 5): + DONT_DEBUG.append(path.normcase('')) + +# Contains information about all breakpoints in the process. Keys are line numbers on which +# there are breakpoints in any file, and values are dicts. For every line number, the +# corresponding dict contains all the breakpoints that fall on that line. The keys in that +# dict are tuples of the form (filename, breakpoint_id), each entry representing a single +# breakpoint, and values are BreakpointInfo objects. +# +# For example, given the following breakpoints: +# +# 1. In 'main.py' at line 10. +# 2. In 'main.py' at line 20. +# 3. In 'module.py' at line 10. +# +# the contents of BREAKPOINTS would be: +# {10: {('main.py', 1): ..., ('module.py', 3): ...}, 20: {('main.py', 2): ... }} +BREAKPOINTS = {} + +# Contains information about all pending (i.e. not yet bound) breakpoints in the process. +# Elements are BreakpointInfo objects. +PENDING_BREAKPOINTS = set() + +# Must be in sync with enum PythonBreakpointConditionKind in PythonBreakpoint.cs +BREAKPOINT_CONDITION_ALWAYS = 0 +BREAKPOINT_CONDITION_WHEN_TRUE = 1 +BREAKPOINT_CONDITION_WHEN_CHANGED = 2 + +# Must be in sync with enum PythonBreakpointPassCountKind in PythonBreakpoint.cs +BREAKPOINT_PASS_COUNT_ALWAYS = 0 +BREAKPOINT_PASS_COUNT_EVERY = 1 +BREAKPOINT_PASS_COUNT_WHEN_EQUAL = 2 +BREAKPOINT_PASS_COUNT_WHEN_EQUAL_OR_GREATER = 3 + +## Begin modification by Don Jayamanne +DJANGO_VERSIONS_IDENTIFIED = False +IS_DJANGO18 = False +IS_DJANGO19 = False +IS_DJANGO19_OR_HIGHER = False + +try: + dict_contains = dict.has_key +except: + try: + #Py3k does not have has_key anymore, and older versions don't have __contains__ + dict_contains = dict.__contains__ + except: + try: + dict_contains = dict.has_key + except NameError: + def dict_contains(d, key): + return d.has_key(key) +## End modification by Don Jayamanne + +class BreakpointInfo(object): + __slots__ = [ + 'breakpoint_id', 'filename', 'lineno', 'condition_kind', 'condition', + 'pass_count_kind', 'pass_count', 'is_bound', 'last_condition_value', + 'hit_count' + ] + + # For "when changed" breakpoints, this is used as the initial value of last_condition_value, + # such that it is guaranteed to not compare equal to any other value that it will get later. + _DUMMY_LAST_VALUE = object() + + def __init__(self, breakpoint_id, filename, lineno, condition_kind, condition, pass_count_kind, pass_count): + self.breakpoint_id = breakpoint_id + self.filename = filename + self.lineno = lineno + self.condition_kind = condition_kind + self.condition = condition + self.pass_count_kind = pass_count_kind + self.pass_count = pass_count + self.is_bound = False + self.last_condition_value = BreakpointInfo._DUMMY_LAST_VALUE + self.hit_count = 0 + + @staticmethod + def find_by_id(breakpoint_id): + for line, bp_dict in BREAKPOINTS.items(): + for (filename, bp_id), bp in bp_dict.items(): + if bp_id == breakpoint_id: + return bp + return None + +# lock for calling .send on the socket +send_lock = thread.allocate_lock() + +class _SendLockContextManager(object): + """context manager for send lock. Handles both acquiring/releasing the + send lock as well as detaching the debugger if the remote process + is disconnected""" + + def __enter__(self): + # mark that we're about to do socket I/O so we won't deliver + # debug events when we're debugging the standard library + cur_thread = get_thread_from_id(thread.get_ident()) + if cur_thread is not None: + cur_thread.is_sending = True + + send_lock.acquire() + + def __exit__(self, exc_type, exc_value, tb): + send_lock.release() + + # start sending debug events again + cur_thread = get_thread_from_id(thread.get_ident()) + if cur_thread is not None: + cur_thread.is_sending = False + + if exc_type is not None: + detach_threads() + detach_process() + # swallow the exception, we're no longer debugging + return True + +_SendLockCtx = _SendLockContextManager() + +SEND_BREAK_COMPLETE = False + +STEPPING_OUT = -1 # first value, we decrement below this +STEPPING_NONE = 0 +STEPPING_BREAK = 1 +STEPPING_LAUNCH_BREAK = 2 +STEPPING_ATTACH_BREAK = 3 +STEPPING_INTO = 4 +STEPPING_OVER = 5 # last value, we increment past this. + +USER_STEPPING = (STEPPING_OUT, STEPPING_INTO, STEPPING_OVER) + +FRAME_KIND_NONE = 0 +FRAME_KIND_PYTHON = 1 +FRAME_KIND_DJANGO = 2 + +DJANGO_BUILTINS = {'True': True, 'False': False, 'None': None} + +PYTHON_EVALUATION_RESULT_REPR_KIND_NORMAL = 0 # regular repr and hex repr (if applicable) for the evaluation result; length is len(result) +PYTHON_EVALUATION_RESULT_REPR_KIND_RAW = 1 # repr is raw representation of the value - see TYPES_WITH_RAW_REPR; length is len(repr) +PYTHON_EVALUATION_RESULT_REPR_KIND_RAWLEN = 2 # same as above, but only the length is reported, not the actual value + +PYTHON_EVALUATION_RESULT_EXPANDABLE = 1 +PYTHON_EVALUATION_RESULT_METHOD_CALL = 2 +PYTHON_EVALUATION_RESULT_SIDE_EFFECTS = 4 +PYTHON_EVALUATION_RESULT_RAW = 8 +PYTHON_EVALUATION_RESULT_HAS_RAW_REPR = 16 + +# Don't show attributes of these types if they come from the class (assume they are methods). +METHOD_TYPES = ( + types.FunctionType, + types.MethodType, + types.BuiltinFunctionType, + type("".__repr__), # method-wrapper +) + +# repr() for these types can be used as input for eval() to get the original value. +# float is intentionally not included because it is not always round-trippable (e.g inf, nan). +TYPES_WITH_ROUND_TRIPPING_REPR = set((type(None), int, bool, str, unicode)) +if sys.version[0] == '3': + TYPES_WITH_ROUND_TRIPPING_REPR.add(bytes) +else: + TYPES_WITH_ROUND_TRIPPING_REPR.add(long) + +# repr() for these types can be used as input for eval() to get the original value, provided that the same is true for all their elements. +COLLECTION_TYPES_WITH_ROUND_TRIPPING_REPR = set((tuple, list, set, frozenset)) + +# eval(repr(x)), but optimized for common types for which it is known that result == x. +def eval_repr(x): + def is_repr_round_tripping(x): + # Do exact type checks here - subclasses can override __repr__. + if type(x) in TYPES_WITH_ROUND_TRIPPING_REPR: + return True + elif type(x) in COLLECTION_TYPES_WITH_ROUND_TRIPPING_REPR: + # All standard sequence types are round-trippable if their elements are. + return all((is_repr_round_tripping(item) for item in x)) + else: + return False + if is_repr_round_tripping(x): + return x + else: + return eval(repr(x), {}) + +# key is type, value is function producing the raw repr +TYPES_WITH_RAW_REPR = { + unicode: (lambda s: s) +} + +# bytearray is 2.6+ +try: + # getfilesystemencoding is used here because it effectively corresponds to the notion of "locale encoding": + # current ANSI codepage on Windows, LC_CTYPE on Linux, UTF-8 on OS X - which is exactly what we want. + TYPES_WITH_RAW_REPR[bytearray] = lambda b: b.decode(sys.getfilesystemencoding(), 'ignore') +except: + pass + +if sys.version[0] == '3': + TYPES_WITH_RAW_REPR[bytes] = TYPES_WITH_RAW_REPR[bytearray] +else: + TYPES_WITH_RAW_REPR[str] = TYPES_WITH_RAW_REPR[unicode] + +if sys.version[0] == '3': + # work around a crashing bug on CPython 3.x where they take a hard stack overflow + # we'll never see this exception but it'll allow us to keep our try/except handler + # the same across all versions of Python + class StackOverflowException(Exception): pass +else: + StackOverflowException = RuntimeError + +ASBR = to_bytes('ASBR') +SETL = to_bytes('SETL') +THRF = to_bytes('THRF') +DETC = to_bytes('DETC') +NEWT = to_bytes('NEWT') +EXTT = to_bytes('EXTT') +EXIT = to_bytes('EXIT') +EXCP = to_bytes('EXCP') +EXC2 = to_bytes('EXC2') +MODL = to_bytes('MODL') +STPD = to_bytes('STPD') +BRKS = to_bytes('BRKS') +BRKF = to_bytes('BRKF') +BRKH = to_bytes('BRKH') +BRKC = to_bytes('BRKC') +BKHC = to_bytes('BKHC') +LOAD = to_bytes('LOAD') +EXCE = to_bytes('EXCE') +EXCR = to_bytes('EXCR') +CHLD = to_bytes('CHLD') +OUTP = to_bytes('OUTP') +REQH = to_bytes('REQH') +LAST = to_bytes('LAST') + +def get_thread_from_id(id): + THREADS_LOCK.acquire() + try: + return THREADS.get(id) + finally: + THREADS_LOCK.release() + +def should_send_frame(frame): + return (frame is not None and + frame.f_code not in DEBUG_ENTRYPOINTS and + path.normcase(frame.f_code.co_filename) not in DONT_DEBUG) + +KNOWN_DIRECTORIES = set((None, '')) +KNOWN_ZIPS = set() + +def is_file_in_zip(filename): + parent, name = path.split(path.abspath(filename)) + if parent in KNOWN_DIRECTORIES: + return False + elif parent in KNOWN_ZIPS: + return True + elif path.isdir(parent): + KNOWN_DIRECTORIES.add(parent) + return False + else: + KNOWN_ZIPS.add(parent) + return True + +def lookup_builtin(name, frame): + try: + return frame.f_builtins.get(bits) + except: + # http://ironpython.codeplex.com/workitem/30908 + builtins = frame.f_globals['__builtins__'] + if not isinstance(builtins, dict): + builtins = builtins.__dict__ + return builtins.get(name) + +def lookup_local(frame, name): + bits = name.split('.') + obj = frame.f_locals.get(bits[0]) or frame.f_globals.get(bits[0]) or lookup_builtin(bits[0], frame) + bits.pop(0) + while bits and obj is not None and type(obj) is types.ModuleType: + obj = getattr(obj, bits.pop(0), None) + return obj + +if sys.version_info[0] >= 3: + _EXCEPTIONS_MODULE = 'builtins' +else: + _EXCEPTIONS_MODULE = 'exceptions' + +def get_exception_name(exc_type): + if exc_type.__module__ == _EXCEPTIONS_MODULE: + return exc_type.__name__ + else: + return exc_type.__module__ + '.' + exc_type.__name__ + +# These constants come from Visual Studio - enum_EXCEPTION_STATE +BREAK_MODE_NEVER = 0 +BREAK_MODE_ALWAYS = 1 +BREAK_MODE_UNHANDLED = 32 + +BREAK_TYPE_NONE = 0 +BREAK_TYPE_UNHANDLED = 1 +BREAK_TYPE_HANDLED = 2 + +class ExceptionBreakInfo(object): + BUILT_IN_HANDLERS = { + path.normcase(''): ((None, None, '*'),), + path.normcase('build\\bdist.win32\\egg\\pkg_resources.py'): ((None, None, '*'),), + path.normcase('build\\bdist.win-amd64\\egg\\pkg_resources.py'): ((None, None, '*'),), + } + + def __init__(self): + self.default_mode = BREAK_MODE_UNHANDLED + self.break_on = { } + self.handler_cache = dict(self.BUILT_IN_HANDLERS) + self.handler_lock = thread.allocate_lock() + self.add_exception('exceptions.IndexError', BREAK_MODE_NEVER) + self.add_exception('builtins.IndexError', BREAK_MODE_NEVER) + self.add_exception('exceptions.KeyError', BREAK_MODE_NEVER) + self.add_exception('builtins.KeyError', BREAK_MODE_NEVER) + self.add_exception('exceptions.AttributeError', BREAK_MODE_NEVER) + self.add_exception('builtins.AttributeError', BREAK_MODE_NEVER) + self.add_exception('exceptions.StopIteration', BREAK_MODE_NEVER) + self.add_exception('builtins.StopIteration', BREAK_MODE_NEVER) + self.add_exception('exceptions.GeneratorExit', BREAK_MODE_NEVER) + self.add_exception('builtins.GeneratorExit', BREAK_MODE_NEVER) + + def clear(self): + self.default_mode = BREAK_MODE_UNHANDLED + self.break_on.clear() + self.handler_cache = dict(self.BUILT_IN_HANDLERS) + + def should_break(self, thread, ex_type, ex_value, trace): + probe_stack() + name = get_exception_name(ex_type) + mode = self.break_on.get(name, self.default_mode) + break_type = BREAK_TYPE_NONE + if mode & BREAK_MODE_ALWAYS: + if self.is_handled(thread, ex_type, ex_value, trace): + break_type = BREAK_TYPE_HANDLED + else: + break_type = BREAK_TYPE_UNHANDLED + elif (mode & BREAK_MODE_UNHANDLED) and not self.is_handled(thread, ex_type, ex_value, trace): + break_type = BREAK_TYPE_UNHANDLED + + if break_type: + if issubclass(ex_type, SystemExit): + if not BREAK_ON_SYSTEMEXIT_ZERO: + if not ex_value or (isinstance(ex_value, SystemExit) and not ex_value.code): + break_type = BREAK_TYPE_NONE + + return break_type + + def is_handled(self, thread, ex_type, ex_value, trace): + if trace is None: + # get out if we didn't get a traceback + return False + + if trace.tb_next is not None: + if should_send_frame(trace.tb_next.tb_frame) and should_debug_code(trace.tb_next.tb_frame.f_code): + # don't break if this is not the top of the traceback, + # unless the previous frame was not debuggable + return True + + cur_frame = trace.tb_frame + + while should_send_frame(cur_frame) and cur_frame.f_code is not None and cur_frame.f_code.co_filename is not None: + filename = path.normcase(cur_frame.f_code.co_filename) + if is_file_in_zip(filename): + # File is in a zip, so assume it handles exceptions + return True + + if not is_same_py_file(filename, __file__): + handlers = self.handler_cache.get(filename) + + if handlers is None: + # req handlers for this file from the debug engine + self.handler_lock.acquire() + + with _SendLockCtx: + write_bytes(conn, REQH) + write_string(conn, filename) + + # wait for the handler data to be received + self.handler_lock.acquire() + self.handler_lock.release() + + handlers = self.handler_cache.get(filename) + + if handlers is None: + # no code available, so assume unhandled + return False + + line = cur_frame.f_lineno + for line_start, line_end, expressions in handlers: + if line_start is None or line_start <= line < line_end: + if '*' in expressions: + return True + + for text in expressions: + try: + res = lookup_local(cur_frame, text) + if res is not None and issubclass(ex_type, res): + return True + except: + pass + + cur_frame = cur_frame.f_back + + return False + + def add_exception(self, name, mode=BREAK_MODE_UNHANDLED): + if name.startswith(_EXCEPTIONS_MODULE + '.'): + name = name[len(_EXCEPTIONS_MODULE) + 1:] + self.break_on[name] = mode + +BREAK_ON = ExceptionBreakInfo() + +def probe_stack(depth = 10): + """helper to make sure we have enough stack space to proceed w/o corrupting + debugger state.""" + if depth == 0: + return + probe_stack(depth - 1) + +PREFIXES = [path.normcase(sys.prefix)] +# If we're running in a virtual env, DEBUG_STDLIB should respect this too. +if hasattr(sys, 'base_prefix'): + PREFIXES.append(path.normcase(sys.base_prefix)) +if hasattr(sys, 'real_prefix'): + PREFIXES.append(path.normcase(sys.real_prefix)) + +def should_debug_code(code): + if not code or not code.co_filename: + return False + + filename = path.normcase(code.co_filename) + if not DEBUG_STDLIB: + for prefix in PREFIXES: + if prefix != '' and filename.startswith(prefix): + return False + + for dont_debug_file in DONT_DEBUG: + if is_same_py_file(filename, dont_debug_file): + return False + + if is_file_in_zip(filename): + # file in inside an egg or zip, so we can't debug it + return False + + return True + +attach_lock = thread.allocate() +attach_sent_break = False + +local_path_to_vs_path = {} + +def breakpoint_path_match(vs_path, local_path): + vs_path_norm = path.normcase(vs_path) + local_path_norm = path.normcase(local_path) + if local_path_to_vs_path.get(local_path_norm) == vs_path_norm: + return True + + # Walk the local filesystem from local_path up, matching agains win_path component by component, + # and stop when we no longer see an __init__.py. This should give a reasonably close approximation + # of matching the package name. + while True: + local_path, local_name = path.split(local_path) + vs_path, vs_name = ntpath.split(vs_path) + # Match the last component in the path. If one or both components are unavailable, then + # we have reached the root on the corresponding path without successfully matching. + if not local_name or not vs_name or path.normcase(local_name) != path.normcase(vs_name): + return False + # If we have an __init__.py, this module was inside the package, and we still need to match + # thatpackage, so walk up one level and keep matching. Otherwise, we've walked as far as we + # needed to, and matched all names on our way, so this is a match. + if not path.exists(path.join(local_path, '__init__.py')): + break + + local_path_to_vs_path[local_path_norm] = vs_path_norm + return True + +def update_all_thread_stacks(blocking_thread = None, check_is_blocked = True): + THREADS_LOCK.acquire() + all_threads = list(THREADS.values()) + THREADS_LOCK.release() + + for cur_thread in all_threads: + if cur_thread is blocking_thread: + continue + + cur_thread._block_starting_lock.acquire() + if not check_is_blocked or not cur_thread._is_blocked: + # release the lock, we're going to run user code to evaluate the frames + cur_thread._block_starting_lock.release() + + frames = cur_thread.get_frame_list() + + # re-acquire the lock and make sure we're still not blocked. If so send + # the frame list. + cur_thread._block_starting_lock.acquire() + if not check_is_blocked or not cur_thread._is_blocked: + cur_thread.send_frame_list(frames) + + cur_thread._block_starting_lock.release() diff --git a/src/test/pythonFiles/folding/visualstudio_py_repl.py b/src/test/pythonFiles/folding/visualstudio_py_repl.py new file mode 100644 index 000000000000..14259db2e30e --- /dev/null +++ b/src/test/pythonFiles/folding/visualstudio_py_repl.py @@ -0,0 +1,513 @@ +# Python Tools for Visual Studio + +# Copyright(c) Microsoft Corporation + +# All rights reserved. + +from __future__ import with_statement + +__author__ = "Microsoft Corporation " +__version__ = "3.0.0.0" + +# This module MUST NOT import threading in global scope. This is because in a direct (non-ptvsd) + +# attach scenario, it is loaded on the injected debugger attach thread, and if threading module + +# hasn't been loaded already, it will assume that the thread on which it is being loaded is the + +# main thread. This will cause issues when the thread goes away after attach completes. + +try: + import thread +except ImportError: + # Renamed in Python3k + import _thread as thread +try: + from ssl import SSLError +except: + SSLError = None + +import sys +import socket +import select +import time +import struct +import imp +import traceback +import random +import os +import inspect +import types +from collections import deque + +try: + # In the local attach scenario, visualstudio_py_util is injected into globals() + + # by PyDebugAttach before loading this module, and cannot be imported. + _vspu = visualstudio_py_util +except: + try: + import visualstudio_py_util as _vspu + except ImportError: + import ptvsd.visualstudio_py_util as _vspu +to_bytes = _vspu.to_bytes +read_bytes = _vspu.read_bytes +read_int = _vspu.read_int +read_string = _vspu.read_string +write_bytes = _vspu.write_bytes +write_int = _vspu.write_int +write_string = _vspu.write_string + +try: + unicode +except NameError: + unicode = str + +try: + BaseException +except NameError: + # BaseException not defined until Python 2.5 + BaseException = Exception + +DEBUG = os.environ.get('DEBUG_REPL') is not None + +__all__ = ['ReplBackend', 'BasicReplBackend', 'BACKEND'] + +def _debug_write(out): + if DEBUG: + sys.__stdout__.write(out) + sys.__stdout__.flush() + + +class SafeSendLock(object): + """a lock which ensures we're released if we take a KeyboardInterrupt exception acquiring it""" + def __init__(self): + self.lock = thread.allocate_lock() + + def __enter__(self): + self.acquire() + + def __exit__(self, exc_type, exc_value, tb): + self.release() + + def acquire(self): + try: + self.lock.acquire() + except KeyboardInterrupt: + try: + self.lock.release() + except: + pass + raise + + def release(self): + self.lock.release() + +def _command_line_to_args_list(cmdline): + """splits a string into a list using Windows command line syntax.""" + args_list = [] + + if cmdline and cmdline.strip(): + from ctypes import c_int, c_voidp, c_wchar_p + from ctypes import byref, POINTER, WinDLL + + clta = WinDLL('shell32').CommandLineToArgvW + clta.argtypes = [c_wchar_p, POINTER(c_int)] + clta.restype = POINTER(c_wchar_p) + + lf = WinDLL('kernel32').LocalFree + lf.argtypes = [c_voidp] + + pNumArgs = c_int() + r = clta(cmdline, byref(pNumArgs)) + if r: + for index in range(0, pNumArgs.value): + if sys.hexversion >= 0x030000F0: + argval = r[index] + else: + argval = r[index].encode('ascii', 'replace') + args_list.append(argval) + lf(r) + else: + sys.stderr.write('Error parsing script arguments:\n') + sys.stderr.write(cmdline + '\n') + + return args_list + + +class UnsupportedReplException(Exception): + def __init__(self, reason): + self.reason = reason + +# save the start_new_thread so we won't debug/break into the REPL comm thread. +start_new_thread = thread.start_new_thread +class ReplBackend(object): + """back end for executing REPL code. This base class handles all of the communication with the remote process while derived classes implement the actual inspection and introspection.""" + _MRES = to_bytes('MRES') + _SRES = to_bytes('SRES') + _MODS = to_bytes('MODS') + _IMGD = to_bytes('IMGD') + _PRPC = to_bytes('PRPC') + _RDLN = to_bytes('RDLN') + _STDO = to_bytes('STDO') + _STDE = to_bytes('STDE') + _DBGA = to_bytes('DBGA') + _DETC = to_bytes('DETC') + _DPNG = to_bytes('DPNG') + _DXAM = to_bytes('DXAM') + _CHWD = to_bytes('CHWD') + + _MERR = to_bytes('MERR') + _SERR = to_bytes('SERR') + _ERRE = to_bytes('ERRE') + _EXIT = to_bytes('EXIT') + _DONE = to_bytes('DONE') + _MODC = to_bytes('MODC') + + def __init__(self, *args, **kwargs): + import threading + self.conn = None + self.send_lock = SafeSendLock() + self.input_event = threading.Lock() + self.input_event.acquire() # lock starts acquired (we use it like a manual reset event) + self.input_string = None + self.exit_requested = False + + def connect(self, port): + self.conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.conn.connect(('127.0.0.1', port)) + + # start a new thread for communicating w/ the remote process + start_new_thread(self._repl_loop, ()) + + def connect_using_socket(self, socket): + self.conn = socket + start_new_thread(self._repl_loop, ()) + + def _repl_loop(self): + """loop on created thread which processes communicates with the REPL window""" + try: + while True: + if self.check_for_exit_repl_loop(): + break + + # we receive a series of 4 byte commands. Each command then + + # has it's own format which we must parse before continuing to + + # the next command. + self.flush() + self.conn.settimeout(10) + + # 2.x raises SSLError in case of timeout (http://bugs.python.org/issue10272) + if SSLError: + timeout_exc_types = (socket.timeout, SSLError) + else: + timeout_exc_types = socket.timeout + try: + inp = read_bytes(self.conn, 4) + except timeout_exc_types: + r, w, x = select.select([], [], [self.conn], 0) + if x: + # an exception event has occured on the socket... + raise + continue + + self.conn.settimeout(None) + if inp == '': + break + self.flush() + + cmd = ReplBackend._COMMANDS.get(inp) + if cmd is not None: + cmd(self) + except: + _debug_write('error in repl loop') + _debug_write(traceback.format_exc()) + self.exit_process() + + time.sleep(2) # try and exit gracefully, then interrupt main if necessary + + if sys.platform == 'cli': + # just kill us as fast as possible + import System + System.Environment.Exit(1) + + self.interrupt_main() + + def check_for_exit_repl_loop(self): + return False + + def _cmd_run(self): + """runs the received snippet of code""" + self.run_command(read_string(self.conn)) + + def _cmd_abrt(self): + """aborts the current running command""" + # abort command, interrupts execution of the main thread. + self.interrupt_main() + + def _cmd_exit(self): + """exits the interactive process""" + self.exit_requested = True + self.exit_process() + + def _cmd_mems(self): + """gets the list of members available for the given expression""" + expression = read_string(self.conn) + try: + name, inst_members, type_members = self.get_members(expression) + except: + with self.send_lock: + write_bytes(self.conn, ReplBackend._MERR) + _debug_write('error in eval') + _debug_write(traceback.format_exc()) + else: + with self.send_lock: + write_bytes(self.conn, ReplBackend._MRES) + write_string(self.conn, name) + self._write_member_dict(inst_members) + self._write_member_dict(type_members) + + def _cmd_sigs(self): + """gets the signatures for the given expression""" + expression = read_string(self.conn) + try: + sigs = self.get_signatures(expression) + except: + with self.send_lock: + write_bytes(self.conn, ReplBackend._SERR) + _debug_write('error in eval') + _debug_write(traceback.format_exc()) + else: + with self.send_lock: + write_bytes(self.conn, ReplBackend._SRES) + # single overload + write_int(self.conn, len(sigs)) + for doc, args, vargs, varkw, defaults in sigs: + # write overload + write_string(self.conn, (doc or '')[:4096]) + arg_count = len(args) + (vargs is not None) + (varkw is not None) + write_int(self.conn, arg_count) + + def_values = [''] * (len(args) - len(defaults)) + ['=' + d for d in defaults] + for arg, def_value in zip(args, def_values): + write_string(self.conn, (arg or '') + def_value) + if vargs is not None: + write_string(self.conn, '*' + vargs) + if varkw is not None: + write_string(self.conn, '**' + varkw) + + def _cmd_setm(self): + global exec_mod + """sets the current module which code will execute against""" + mod_name = read_string(self.conn) + self.set_current_module(mod_name) + + def _cmd_sett(self): + """sets the current thread and frame which code will execute against""" + thread_id = read_int(self.conn) + frame_id = read_int(self.conn) + frame_kind = read_int(self.conn) + self.set_current_thread_and_frame(thread_id, frame_id, frame_kind) + + def _cmd_mods(self): + """gets the list of available modules""" + try: + res = self.get_module_names() + res.sort() + except: + res = [] + + with self.send_lock: + write_bytes(self.conn, ReplBackend._MODS) + write_int(self.conn, len(res)) + for name, filename in res: + write_string(self.conn, name) + write_string(self.conn, filename) + + def _cmd_inpl(self): + """handles the input command which returns a string of input""" + self.input_string = read_string(self.conn) + self.input_event.release() + + def _cmd_excf(self): + """handles executing a single file""" + filename = read_string(self.conn) + args = read_string(self.conn) + self.execute_file(filename, args) + + def _cmd_excx(self): + """handles executing a single file, module or process""" + filetype = read_string(self.conn) + filename = read_string(self.conn) + args = read_string(self.conn) + self.execute_file_ex(filetype, filename, args) + + def _cmd_debug_attach(self): + import visualstudio_py_debugger + port = read_int(self.conn) + id = read_string(self.conn) + debug_options = visualstudio_py_debugger.parse_debug_options(read_string(self.conn)) + self.attach_process(port, id, debug_options) + + _COMMANDS = { + to_bytes('run '): _cmd_run, + to_bytes('abrt'): _cmd_abrt, + to_bytes('exit'): _cmd_exit, + to_bytes('mems'): _cmd_mems, + to_bytes('sigs'): _cmd_sigs, + to_bytes('mods'): _cmd_mods, + to_bytes('setm'): _cmd_setm, + to_bytes('sett'): _cmd_sett, + to_bytes('inpl'): _cmd_inpl, + to_bytes('excf'): _cmd_excf, + to_bytes('excx'): _cmd_excx, + to_bytes('dbga'): _cmd_debug_attach, + } + + def _write_member_dict(self, mem_dict): + write_int(self.conn, len(mem_dict)) + for name, type_name in mem_dict.items(): + write_string(self.conn, name) + write_string(self.conn, type_name) + + def on_debugger_detach(self): + with self.send_lock: + write_bytes(self.conn, ReplBackend._DETC) + + def init_debugger(self): + from os import path + sys.path.append(path.dirname(__file__)) + import visualstudio_py_debugger + visualstudio_py_debugger.DONT_DEBUG.append(path.normcase(__file__)) + new_thread = visualstudio_py_debugger.new_thread() + sys.settrace(new_thread.trace_func) + visualstudio_py_debugger.intercept_threads(True) + + def send_image(self, filename): + with self.send_lock: + write_bytes(self.conn, ReplBackend._IMGD) + write_string(self.conn, filename) + + def write_png(self, image_bytes): + with self.send_lock: + write_bytes(self.conn, ReplBackend._DPNG) + write_int(self.conn, len(image_bytes)) + write_bytes(self.conn, image_bytes) + + def write_xaml(self, xaml_bytes): + with self.send_lock: + write_bytes(self.conn, ReplBackend._DXAM) + write_int(self.conn, len(xaml_bytes)) + write_bytes(self.conn, xaml_bytes) + + def send_prompt(self, ps1, ps2, allow_multiple_statements): + """sends the current prompt to the interactive window""" + with self.send_lock: + write_bytes(self.conn, ReplBackend._PRPC) + write_string(self.conn, ps1) + write_string(self.conn, ps2) + write_int(self.conn, 1 if allow_multiple_statements else 0) + + def send_cwd(self): + """sends the current working directory""" + with self.send_lock: + write_bytes(self.conn, ReplBackend._CHWD) + write_string(self.conn, os.getcwd()) + + def send_error(self): + """reports that an error occured to the interactive window""" + with self.send_lock: + write_bytes(self.conn, ReplBackend._ERRE) + + def send_exit(self): + """reports the that the REPL process has exited to the interactive window""" + with self.send_lock: + write_bytes(self.conn, ReplBackend._EXIT) + + def send_command_executed(self): + with self.send_lock: + write_bytes(self.conn, ReplBackend._DONE) + + def send_modules_changed(self): + with self.send_lock: + write_bytes(self.conn, ReplBackend._MODC) + + def read_line(self): + """reads a line of input from standard input""" + with self.send_lock: + write_bytes(self.conn, ReplBackend._RDLN) + self.input_event.acquire() + return self.input_string + + def write_stdout(self, value): + """writes a string to standard output in the remote console""" + with self.send_lock: + write_bytes(self.conn, ReplBackend._STDO) + write_string(self.conn, value) + + def write_stderr(self, value): + """writes a string to standard input in the remote console""" + with self.send_lock: + write_bytes(self.conn, ReplBackend._STDE) + write_string(self.conn, value) + + ################################################################ + + # Implementation of execution, etc... + + def execution_loop(self): + """starts processing execution requests""" + raise NotImplementedError + + def run_command(self, command): + """runs the specified command which is a string containing code""" + raise NotImplementedError + + def execute_file(self, filename, args): + """executes the given filename as the main module""" + return self.execute_file_ex('script', filename, args) + + def execute_file_ex(self, filetype, filename, args): + """executes the given filename as a 'script', 'module' or 'process'.""" + raise NotImplementedError + + def interrupt_main(self): + """aborts the current running command""" + raise NotImplementedError + + def exit_process(self): + """exits the REPL process""" + raise NotImplementedError + + def get_members(self, expression): + """returns a tuple of the type name, instance members, and type members""" + raise NotImplementedError + + def get_signatures(self, expression): + """returns doc, args, vargs, varkw, defaults.""" + raise NotImplementedError + + def set_current_module(self, module): + """sets the module which code executes against""" + raise NotImplementedError + + def set_current_thread_and_frame(self, thread_id, frame_id, frame_kind): + """sets the current thread and frame which code will execute against""" + raise NotImplementedError + + def get_module_names(self): + """returns a list of module names""" + raise NotImplementedError + + def flush(self): + """flushes the stdout/stderr buffers""" + raise NotImplementedError + + def attach_process(self, port, debugger_id, debug_options): + """starts processing execution requests""" + raise NotImplementedError + +def exit_work_item(): + sys.exit(0) From 94b9116dff554ba8de380db2d44a7fc58a5ce926 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 7 May 2018 23:43:41 -0700 Subject: [PATCH 240/433] Add factory class to create the process service (#1342) * Fixes #1339 * factory to create the process service * :hammer: pass resource * :memo: news entry * remove process service * :hammer: use factory to create the service * Remove dependency on IProcessService * Remove use of env provider service * Revert changes * Fix merge issue --- news/3 Code Health/1339.md | 1 + src/client/activation/analysis.ts | 4 +-- src/client/common/configSettings.ts | 4 +-- .../common/installer/productInstaller.ts | 4 +-- src/client/common/process/proc.ts | 11 ++++--- src/client/common/process/processFactory.ts | 24 ++++++++++++++ .../common/process/pythonExecutionFactory.ts | 13 +++----- src/client/common/process/pythonProcess.ts | 21 ++---------- .../common/process/pythonToolService.ts | 15 ++++----- src/client/common/process/serviceRegistry.ts | 6 ++-- src/client/common/process/types.ts | 8 +++-- src/client/common/types.ts | 4 +-- .../variables/environmentVariablesProvider.ts | 2 +- .../display/shebangCodeLensProvider.ts | 28 ++++++++-------- src/client/interpreter/interpreterVersion.ts | 10 +++--- .../locators/services/condaService.ts | 26 ++++++++------- .../locators/services/currentPathService.ts | 20 +++++++----- .../locators/services/pipEnvService.ts | 10 +++--- src/client/interpreter/virtualEnvs/index.ts | 9 +++--- src/client/providers/importSortProvider.ts | 14 +++++--- src/client/refactor/proxy.ts | 25 +++++++-------- src/client/sortImports.ts | 8 ++--- src/client/terminals/codeExecution/helper.ts | 13 +++----- src/client/workspaceSymbols/generator.ts | 19 +++++------ src/client/workspaceSymbols/main.ts | 27 +++++++--------- src/test/common/installer.test.ts | 7 ++-- src/test/common/moduleInstaller.test.ts | 6 ++-- src/test/common/process/execFactory.test.ts | 15 ++++++--- .../common/terminals/activation.conda.test.ts | 11 +++++-- src/test/format/extension.format.test.ts | 17 +++++----- src/test/format/extension.sort.test.ts | 5 +-- src/test/interpreters/condaService.test.ts | 12 ++++--- .../interpreters/currentPathService.test.ts | 12 ++++--- .../interpreters/interpreterVersion.test.ts | 6 ++-- src/test/interpreters/pipEnvService.test.ts | 14 ++++---- .../interpreters/virtualEnvManager.test.ts | 18 +++++++---- src/test/mocks/proc.ts | 4 +-- src/test/serviceRegistry.ts | 12 ++++--- .../terminals/codeExecution/helper.test.ts | 10 +++--- src/test/unittests/nosetest.disovery.test.ts | 16 +++++----- src/test/unittests/nosetest.run.test.ts | 32 +++++++++---------- src/test/unittests/pytest.discovery.test.ts | 16 +++++----- src/test/unittests/pytest.run.test.ts | 32 +++++++++---------- src/test/unittests/serviceRegistry.ts | 1 + src/test/unittests/unittest.discovery.test.ts | 14 ++++---- src/test/unittests/unittest.run.test.ts | 22 ++++++------- src/test/workspaceSymbols/multiroot.test.ts | 14 ++++---- src/test/workspaceSymbols/standard.test.ts | 18 +++++------ 48 files changed, 340 insertions(+), 300 deletions(-) create mode 100644 news/3 Code Health/1339.md create mode 100644 src/client/common/process/processFactory.ts diff --git a/news/3 Code Health/1339.md b/news/3 Code Health/1339.md new file mode 100644 index 000000000000..e06d19240f67 --- /dev/null +++ b/news/3 Code Health/1339.md @@ -0,0 +1 @@ +Ensure custom environment variables are always used when spawning any process from within the extension. diff --git a/src/client/activation/analysis.ts b/src/client/activation/analysis.ts index d2e853dc7dda..239dbef44edb 100644 --- a/src/client/activation/analysis.ts +++ b/src/client/activation/analysis.ts @@ -9,7 +9,7 @@ import { IApplicationShell } from '../common/application/types'; import { isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; import { createDeferred, Deferred } from '../common/helpers'; import { IFileSystem, IPlatformService } from '../common/platform/types'; -import { IProcessService } from '../common/process/types'; +import { IProcessServiceFactory } from '../common/process/types'; import { StopWatch } from '../common/stopWatch'; import { IConfigurationService, IOutputChannel, IPythonSettings } from '../common/types'; import { IEnvironmentVariablesProvider } from '../common/variables/types'; @@ -227,7 +227,7 @@ export class AnalysisExtensionActivator implements IExtensionActivator { } private async isDotNetInstalled(): Promise { - const ps = this.services.get(IProcessService); + const ps = await this.services.get(IProcessServiceFactory).create(); const result = await ps.exec('dotnet', ['--version']).catch(() => { return { stdout: '' }; }); return result.stdout.trim().startsWith('2.'); } diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index dd39cb4f5dbe..85a6ac61bc14 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -34,10 +34,10 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { public devOptions: string[] = []; public linting!: ILintingSettings; public formatting!: IFormattingSettings; - public autoComplete?: IAutoCompleteSettings; + public autoComplete!: IAutoCompleteSettings; public unitTest!: IUnitTestSettings; public terminal!: ITerminalSettings; - public sortImports?: ISortImportSettings; + public sortImports!: ISortImportSettings; public workspaceSymbols!: IWorkspaceSymbolSettings; public disableInstallationChecks = false; public globalModuleInstallation = false; diff --git a/src/client/common/installer/productInstaller.ts b/src/client/common/installer/productInstaller.ts index a0929e86de66..d214653c6f43 100644 --- a/src/client/common/installer/productInstaller.ts +++ b/src/client/common/installer/productInstaller.ts @@ -9,7 +9,7 @@ import { ITestsHelper } from '../../unittests/common/types'; import { IApplicationShell } from '../application/types'; import { STANDARD_OUTPUT_CHANNEL } from '../constants'; import { IPlatformService } from '../platform/types'; -import { IProcessService, IPythonExecutionFactory } from '../process/types'; +import { IProcessServiceFactory, IPythonExecutionFactory } from '../process/types'; import { ITerminalServiceFactory } from '../terminal/types'; import { IConfigurationService, IInstaller, ILogger, InstallerResponse, IOutputChannel, ModuleNamePurpose, Product } from '../types'; import { ProductNames } from './productNames'; @@ -77,7 +77,7 @@ abstract class BaseInstaller { const pythonProcess = await this.serviceContainer.get(IPythonExecutionFactory).create(resource); return pythonProcess.isModuleInstalled(executableName); } else { - const process = this.serviceContainer.get(IProcessService); + const process = await this.serviceContainer.get(IProcessServiceFactory).create(resource); return process.exec(executableName, ['--version'], { mergeStdOutErr: true }) .then(() => true) .catch(() => false); diff --git a/src/client/common/process/proc.ts b/src/client/common/process/proc.ts index 426519f2ba68..ed511447db1f 100644 --- a/src/client/common/process/proc.ts +++ b/src/client/common/process/proc.ts @@ -4,22 +4,22 @@ // tslint:disable:no-any import { spawn } from 'child_process'; -import { inject, injectable } from 'inversify'; import { Observable } from 'rxjs/Observable'; import { Disposable } from 'vscode'; import { createDeferred } from '../helpers'; +import { EnvironmentVariables } from '../variables/types'; import { DEFAULT_ENCODING } from './constants'; import { ExecutionResult, IBufferDecoder, IProcessService, ObservableExecutionResult, Output, SpawnOptions, StdErrError } from './types'; -@injectable() export class ProcessService implements IProcessService { - constructor(@inject(IBufferDecoder) private decoder: IBufferDecoder) { } + constructor(private readonly decoder: IBufferDecoder, private readonly env?: EnvironmentVariables) { } public execObservable(file: string, args: string[], options: SpawnOptions = {}): ObservableExecutionResult { const encoding = options.encoding = typeof options.encoding === 'string' && options.encoding.length > 0 ? options.encoding : DEFAULT_ENCODING; delete options.encoding; const spawnOptions = { ...options }; if (!spawnOptions.env || Object.keys(spawnOptions).length === 0) { - spawnOptions.env = { ...process.env }; + const env = this.env ? this.env : process.env; + spawnOptions.env = { ...env }; } // Always ensure we have unbuffered output. @@ -79,7 +79,8 @@ export class ProcessService implements IProcessService { delete options.encoding; const spawnOptions = { ...options }; if (!spawnOptions.env || Object.keys(spawnOptions).length === 0) { - spawnOptions.env = { ...process.env }; + const env = this.env ? this.env : process.env; + spawnOptions.env = { ...env }; } // Always ensure we have unbuffered output. diff --git a/src/client/common/process/processFactory.ts b/src/client/common/process/processFactory.ts new file mode 100644 index 000000000000..91440cf9bddd --- /dev/null +++ b/src/client/common/process/processFactory.ts @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { Uri } from 'vscode'; +import { IServiceContainer } from '../../ioc/types'; +import { IEnvironmentVariablesProvider } from '../variables/types'; +import { ProcessService } from './proc'; +import { IBufferDecoder, IProcessService, IProcessServiceFactory } from './types'; + +@injectable() +export class ProcessServiceFactory implements IProcessServiceFactory { + private envVarsService: IEnvironmentVariablesProvider; + constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { + this.envVarsService = serviceContainer.get(IEnvironmentVariablesProvider); + } + public async create(resource?: Uri): Promise { + const customEnvVars = await this.envVarsService.getEnvironmentVariables(resource); + const decoder = this.serviceContainer.get(IBufferDecoder); + return new ProcessService(decoder, customEnvVars); + } +} diff --git a/src/client/common/process/pythonExecutionFactory.ts b/src/client/common/process/pythonExecutionFactory.ts index 4866e6e504f8..ceafb3aa827b 100644 --- a/src/client/common/process/pythonExecutionFactory.ts +++ b/src/client/common/process/pythonExecutionFactory.ts @@ -4,20 +4,17 @@ import { inject, injectable } from 'inversify'; import { Uri } from 'vscode'; import { IServiceContainer } from '../../ioc/types'; -import { IEnvironmentVariablesProvider } from '../variables/types'; import { PythonExecutionService } from './pythonProcess'; -import { IPythonExecutionFactory, IPythonExecutionService } from './types'; +import { IProcessServiceFactory, IPythonExecutionFactory, IPythonExecutionService } from './types'; @injectable() export class PythonExecutionFactory implements IPythonExecutionFactory { - private envVarsService: IEnvironmentVariablesProvider; + private processServiceFactory: IProcessServiceFactory; constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { - this.envVarsService = serviceContainer.get(IEnvironmentVariablesProvider); + this.processServiceFactory = serviceContainer.get(IProcessServiceFactory); } public async create(resource?: Uri): Promise { - return this.envVarsService.getEnvironmentVariables(resource) - .then(customEnvVars => { - return new PythonExecutionService(this.serviceContainer, customEnvVars, resource); - }); + const processService = await this.processServiceFactory.create(resource); + return new PythonExecutionService(this.serviceContainer, processService, resource); } } diff --git a/src/client/common/process/pythonProcess.ts b/src/client/common/process/pythonProcess.ts index 0d6622bed472..419b5d88dcba 100644 --- a/src/client/common/process/pythonProcess.ts +++ b/src/client/common/process/pythonProcess.ts @@ -9,17 +9,14 @@ import { ErrorUtils } from '../errors/errorUtils'; import { ModuleNotInstalledError } from '../errors/moduleNotInstalledError'; import { IFileSystem } from '../platform/types'; import { IConfigurationService } from '../types'; -import { EnvironmentVariables } from '../variables/types'; import { ExecutionResult, IProcessService, IPythonExecutionService, ObservableExecutionResult, SpawnOptions } from './types'; @injectable() export class PythonExecutionService implements IPythonExecutionService { - private readonly procService: IProcessService; private readonly configService: IConfigurationService; private readonly fileSystem: IFileSystem; - constructor(private serviceContainer: IServiceContainer, private envVars: EnvironmentVariables | undefined, private resource?: Uri) { - this.procService = serviceContainer.get(IProcessService); + constructor(private serviceContainer: IServiceContainer, private readonly procService: IProcessService, private resource?: Uri) { this.configService = serviceContainer.get(IConfigurationService); this.fileSystem = serviceContainer.get(IFileSystem); } @@ -34,40 +31,28 @@ export class PythonExecutionService implements IPythonExecutionService { if (await this.fileSystem.fileExistsAsync(this.pythonPath)) { return this.pythonPath; } - return this.procService.exec(this.pythonPath, ['-c', 'import sys;print(sys.executable)'], { env: this.envVars, throwOnStdErr: true }) + return this.procService.exec(this.pythonPath, ['-c', 'import sys;print(sys.executable)'], { throwOnStdErr: true }) .then(output => output.stdout.trim()); } public async isModuleInstalled(moduleName: string): Promise { - return this.procService.exec(this.pythonPath, ['-c', `import ${moduleName}`], { env: this.envVars, throwOnStdErr: true }) + return this.procService.exec(this.pythonPath, ['-c', `import ${moduleName}`], { throwOnStdErr: true }) .then(() => true).catch(() => false); } public execObservable(args: string[], options: SpawnOptions): ObservableExecutionResult { const opts: SpawnOptions = { ...options }; - if (this.envVars) { - opts.env = this.envVars; - } return this.procService.execObservable(this.pythonPath, args, opts); } public execModuleObservable(moduleName: string, args: string[], options: SpawnOptions): ObservableExecutionResult { const opts: SpawnOptions = { ...options }; - if (this.envVars) { - opts.env = this.envVars; - } return this.procService.execObservable(this.pythonPath, ['-m', moduleName, ...args], opts); } public async exec(args: string[], options: SpawnOptions): Promise> { const opts: SpawnOptions = { ...options }; - if (this.envVars) { - opts.env = this.envVars; - } return this.procService.exec(this.pythonPath, args, opts); } public async execModule(moduleName: string, args: string[], options: SpawnOptions): Promise> { const opts: SpawnOptions = { ...options }; - if (this.envVars) { - opts.env = this.envVars; - } const result = await this.procService.exec(this.pythonPath, ['-m', moduleName, ...args], opts); // If a module is not installed we'll have something in stderr. diff --git a/src/client/common/process/pythonToolService.ts b/src/client/common/process/pythonToolService.ts index 9419b005250b..3369c35ac6b6 100644 --- a/src/client/common/process/pythonToolService.ts +++ b/src/client/common/process/pythonToolService.ts @@ -5,12 +5,11 @@ import { inject, injectable } from 'inversify'; import { Uri } from 'vscode'; import { IServiceContainer } from '../../ioc/types'; import { ExecutionInfo } from '../types'; -import { IEnvironmentVariablesProvider } from '../variables/types'; -import { ExecutionResult, IProcessService, IPythonExecutionFactory, IPythonToolExecutionService, ObservableExecutionResult, SpawnOptions } from './types'; +import { ExecutionResult, IProcessServiceFactory, IPythonExecutionFactory, IPythonToolExecutionService, ObservableExecutionResult, SpawnOptions } from './types'; @injectable() export class PythonToolExecutionService implements IPythonToolExecutionService { - constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer) { } + constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { } public async execObservable(executionInfo: ExecutionInfo, options: SpawnOptions, resource: Uri): Promise> { if (options.env) { throw new Error('Environment variables are not supported'); @@ -19,9 +18,8 @@ export class PythonToolExecutionService implements IPythonToolExecutionService { const pythonExecutionService = await this.serviceContainer.get(IPythonExecutionFactory).create(resource); return pythonExecutionService.execModuleObservable(executionInfo.moduleName, executionInfo.args, options); } else { - const env = await this.serviceContainer.get(IEnvironmentVariablesProvider).getEnvironmentVariables(resource); - const processService = this.serviceContainer.get(IProcessService); - return processService.execObservable(executionInfo.execPath!, executionInfo.args, { ...options, env }); + const processService = await this.serviceContainer.get(IProcessServiceFactory).create(resource); + return processService.execObservable(executionInfo.execPath!, executionInfo.args, { ...options }); } } public async exec(executionInfo: ExecutionInfo, options: SpawnOptions, resource: Uri): Promise> { @@ -32,9 +30,8 @@ export class PythonToolExecutionService implements IPythonToolExecutionService { const pythonExecutionService = await this.serviceContainer.get(IPythonExecutionFactory).create(resource); return pythonExecutionService.execModule(executionInfo.moduleName!, executionInfo.args, options); } else { - const env = await this.serviceContainer.get(IEnvironmentVariablesProvider).getEnvironmentVariables(resource); - const processService = this.serviceContainer.get(IProcessService); - return processService.exec(executionInfo.execPath!, executionInfo.args, { ...options, env }); + const processService = await this.serviceContainer.get(IProcessServiceFactory).create(resource); + return processService.exec(executionInfo.execPath!, executionInfo.args, { ...options }); } } } diff --git a/src/client/common/process/serviceRegistry.ts b/src/client/common/process/serviceRegistry.ts index dd4242fec55e..27684a20cc32 100644 --- a/src/client/common/process/serviceRegistry.ts +++ b/src/client/common/process/serviceRegistry.ts @@ -3,14 +3,14 @@ import { IServiceManager } from '../../ioc/types'; import { BufferDecoder } from './decoder'; -import { ProcessService } from './proc'; +import { ProcessServiceFactory } from './processFactory'; import { PythonExecutionFactory } from './pythonExecutionFactory'; import { PythonToolExecutionService } from './pythonToolService'; -import { IBufferDecoder, IProcessService, IPythonExecutionFactory, IPythonToolExecutionService } from './types'; +import { IBufferDecoder, IProcessServiceFactory, IPythonExecutionFactory, IPythonToolExecutionService } from './types'; export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IBufferDecoder, BufferDecoder); - serviceManager.addSingleton(IProcessService, ProcessService); + serviceManager.addSingleton(IProcessServiceFactory, ProcessServiceFactory); serviceManager.addSingleton(IPythonExecutionFactory, PythonExecutionFactory); serviceManager.addSingleton(IPythonToolExecutionService, PythonToolExecutionService); } diff --git a/src/client/common/process/types.ts b/src/client/common/process/types.ts index c4a79d6435a6..22fb6965be55 100644 --- a/src/client/common/process/types.ts +++ b/src/client/common/process/types.ts @@ -34,13 +34,17 @@ export type ExecutionResult = { stderr?: T; }; -export const IProcessService = Symbol('IProcessService'); - export interface IProcessService { execObservable(file: string, args: string[], options?: SpawnOptions): ObservableExecutionResult; exec(file: string, args: string[], options?: SpawnOptions): Promise>; } +export const IProcessServiceFactory = Symbol('IProcessServiceFactory'); + +export interface IProcessServiceFactory { + create(resource?: Uri): Promise; +} + export const IPythonExecutionFactory = Symbol('IPythonExecutionFactory'); export interface IPythonExecutionFactory { diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 697fd461a24c..027437f169cc 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -106,9 +106,9 @@ export interface IPythonSettings { readonly linting: ILintingSettings; readonly formatting: IFormattingSettings; readonly unitTest: IUnitTestSettings; - readonly autoComplete?: IAutoCompleteSettings; + readonly autoComplete: IAutoCompleteSettings; readonly terminal: ITerminalSettings; - readonly sortImports?: ISortImportSettings; + readonly sortImports: ISortImportSettings; readonly workspaceSymbols: IWorkspaceSymbolSettings; readonly envFile: string; readonly disableInstallationChecks: boolean; diff --git a/src/client/common/variables/environmentVariablesProvider.ts b/src/client/common/variables/environmentVariablesProvider.ts index f1e2ca6541bf..a10bb94c0a65 100644 --- a/src/client/common/variables/environmentVariablesProvider.ts +++ b/src/client/common/variables/environmentVariablesProvider.ts @@ -14,7 +14,7 @@ export class EnvironmentVariablesProvider implements IEnvironmentVariablesProvid private fileWatchers = new Map(); private disposables: Disposable[] = []; private changeEventEmitter: EventEmitter; - constructor( @inject(IEnvironmentVariablesService) private envVarsService: IEnvironmentVariablesService, + constructor(@inject(IEnvironmentVariablesService) private envVarsService: IEnvironmentVariablesService, @inject(IDisposableRegistry) disposableRegistry: Disposable[], @inject(IsWindows) private isWidows: boolean, @inject(ICurrentProcess) private process: ICurrentProcess) { disposableRegistry.push(this); diff --git a/src/client/interpreter/display/shebangCodeLensProvider.ts b/src/client/interpreter/display/shebangCodeLensProvider.ts index 8bb2595e752b..457e914200a8 100644 --- a/src/client/interpreter/display/shebangCodeLensProvider.ts +++ b/src/client/interpreter/display/shebangCodeLensProvider.ts @@ -1,8 +1,7 @@ import { inject, injectable } from 'inversify'; -import * as vscode from 'vscode'; -import { CancellationToken, CodeLens, TextDocument } from 'vscode'; +import { CancellationToken, CodeLens, Command, Event, Position, Range, TextDocument, Uri, workspace } from 'vscode'; import * as settings from '../../common/configSettings'; -import { IProcessService } from '../../common/process/types'; +import { IProcessServiceFactory } from '../../common/process/types'; import { IS_WINDOWS } from '../../common/utils'; import { IServiceContainer } from '../../ioc/types'; import { IShebangCodeLensProvider } from '../contracts'; @@ -10,10 +9,10 @@ import { IShebangCodeLensProvider } from '../contracts'; @injectable() export class ShebangCodeLensProvider implements IShebangCodeLensProvider { // tslint:disable-next-line:no-any - public onDidChangeCodeLenses: vscode.Event = vscode.workspace.onDidChangeConfiguration as any as vscode.Event; - private readonly processService: IProcessService; + public onDidChangeCodeLenses: Event = workspace.onDidChangeConfiguration as any as Event; + private readonly processServiceFactory: IProcessServiceFactory; constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { - this.processService = serviceContainer.get(IProcessService); + this.processServiceFactory = serviceContainer.get(IProcessServiceFactory); } public async detectShebang(document: TextDocument): Promise { const firstLine = document.lineAt(0); @@ -26,14 +25,14 @@ export class ShebangCodeLensProvider implements IShebangCodeLensProvider { } const shebang = firstLine.text.substr(2).trim(); - const pythonPath = await this.getFullyQualifiedPathToInterpreter(shebang); + const pythonPath = await this.getFullyQualifiedPathToInterpreter(shebang, document.uri); return typeof pythonPath === 'string' && pythonPath.length > 0 ? pythonPath : undefined; } public async provideCodeLenses(document: TextDocument, token: CancellationToken): Promise { const codeLenses = await this.createShebangCodeLens(document); return Promise.resolve(codeLenses); } - private async getFullyQualifiedPathToInterpreter(pythonPath: string) { + private async getFullyQualifiedPathToInterpreter(pythonPath: string, resource: Uri) { let cmdFile = pythonPath; let args = ['-c', 'import sys;print(sys.executable)']; if (pythonPath.indexOf('bin/env ') >= 0 && !IS_WINDOWS) { @@ -42,24 +41,25 @@ export class ShebangCodeLensProvider implements IShebangCodeLensProvider { cmdFile = parts.shift()!; args = parts.concat(args); } - return this.processService.exec(cmdFile, args) + const processService = await this.processServiceFactory.create(resource); + return processService.exec(cmdFile, args) .then(output => output.stdout.trim()) .catch(() => ''); } private async createShebangCodeLens(document: TextDocument) { const shebang = await this.detectShebang(document); const pythonPath = settings.PythonSettings.getInstance(document.uri).pythonPath; - const resolvedPythonPath = await this.getFullyQualifiedPathToInterpreter(pythonPath); + const resolvedPythonPath = await this.getFullyQualifiedPathToInterpreter(pythonPath, document.uri); if (!shebang || shebang === resolvedPythonPath) { return []; } const firstLine = document.lineAt(0); - const startOfShebang = new vscode.Position(0, 0); - const endOfShebang = new vscode.Position(0, firstLine.text.length - 1); - const shebangRange = new vscode.Range(startOfShebang, endOfShebang); + const startOfShebang = new Position(0, 0); + const endOfShebang = new Position(0, firstLine.text.length - 1); + const shebangRange = new Range(startOfShebang, endOfShebang); - const cmd: vscode.Command = { + const cmd: Command = { command: 'python.setShebangInterpreter', title: 'Set as interpreter' }; diff --git a/src/client/interpreter/interpreterVersion.ts b/src/client/interpreter/interpreterVersion.ts index 5235d60f52ce..4b00ddadead2 100644 --- a/src/client/interpreter/interpreterVersion.ts +++ b/src/client/interpreter/interpreterVersion.ts @@ -1,21 +1,23 @@ import { inject, injectable } from 'inversify'; import '../common/extensions'; -import { IProcessService } from '../common/process/types'; +import { IProcessServiceFactory } from '../common/process/types'; import { IInterpreterVersionService } from './contracts'; export const PIP_VERSION_REGEX = '\\d+\\.\\d+(\\.\\d+)'; @injectable() export class InterpreterVersionService implements IInterpreterVersionService { - constructor(@inject(IProcessService) private processService: IProcessService) { } + constructor(@inject(IProcessServiceFactory) private readonly processServiceFactory: IProcessServiceFactory) { } public async getVersion(pythonPath: string, defaultValue: string): Promise { - return this.processService.exec(pythonPath, ['--version'], { mergeStdOutErr: true }) + const processService = await this.processServiceFactory.create(); + return processService.exec(pythonPath, ['--version'], { mergeStdOutErr: true }) .then(output => output.stdout.splitLines()[0]) .then(version => version.length === 0 ? defaultValue : version) .catch(() => defaultValue); } public async getPipVersion(pythonPath: string): Promise { - const output = await this.processService.exec(pythonPath, ['-m', 'pip', '--version'], { mergeStdOutErr: true }); + const processService = await this.processServiceFactory.create(); + const output = await processService.exec(pythonPath, ['-m', 'pip', '--version'], { mergeStdOutErr: true }); if (output.stdout.length > 0) { // Here's a sample output: // pip 9.0.1 from /Users/donjayamanne/anaconda3/lib/python3.6/site-packages (python 3.6). diff --git a/src/client/interpreter/locators/services/condaService.ts b/src/client/interpreter/locators/services/condaService.ts index f73cb277fd35..2e990c30fcbe 100644 --- a/src/client/interpreter/locators/services/condaService.ts +++ b/src/client/interpreter/locators/services/condaService.ts @@ -1,7 +1,7 @@ import { inject, injectable, named, optional } from 'inversify'; import * as path from 'path'; import { IFileSystem, IPlatformService } from '../../../common/platform/types'; -import { IProcessService } from '../../../common/process/types'; +import { IProcessServiceFactory } from '../../../common/process/types'; import { ILogger, IPersistentStateFactory } from '../../../common/types'; import { VersionUtils } from '../../../common/versionUtils'; import { IServiceContainer } from '../../../ioc/types'; @@ -17,9 +17,9 @@ export const KNOWN_CONDA_LOCATIONS = ['~/anaconda/bin/conda', '~/miniconda/bin/c @injectable() export class CondaService implements ICondaService { - private condaFile: Promise; + private condaFile!: Promise; private isAvailable: boolean | undefined; - private readonly processService: IProcessService; + private readonly processServiceFactory: IProcessServiceFactory; private readonly platform: IPlatformService; private readonly logger: ILogger; private readonly fileSystem: IFileSystem; @@ -30,7 +30,7 @@ export class CondaService implements ICondaService { } constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer, @inject(IInterpreterLocatorService) @named(WINDOWS_REGISTRY_SERVICE) @optional() private registryLookupForConda?: IInterpreterLocatorService) { - this.processService = this.serviceContainer.get(IProcessService); + this.processServiceFactory = this.serviceContainer.get(IProcessServiceFactory); this.platform = this.serviceContainer.get(IPlatformService); this.logger = this.serviceContainer.get(ILogger); this.fileSystem = this.serviceContainer.get(IFileSystem); @@ -54,20 +54,23 @@ export class CondaService implements ICondaService { .catch(() => this.isAvailable = false); } public async getCondaVersion(): Promise { + const processService = await this.processServiceFactory.create(); return this.getCondaFile() - .then(condaFile => this.processService.exec(condaFile, ['--version'], {})) + .then(condaFile => processService.exec(condaFile, ['--version'], {})) .then(result => result.stdout.trim()) .catch(() => undefined); } public async isCondaInCurrentPath() { - return this.processService.exec('conda', ['--version']) + const processService = await this.processServiceFactory.create(); + return processService.exec('conda', ['--version']) .then(output => output.stdout.length > 0) .catch(() => false); } public async getCondaInfo(): Promise { try { const condaFile = await this.getCondaFile(); - const condaInfo = await this.processService.exec(condaFile, ['info', '--json']).then(output => output.stdout); + const processService = await this.processServiceFactory.create(); + const condaInfo = await processService.exec(condaFile, ['info', '--json']).then(output => output.stdout); return JSON.parse(condaInfo) as CondaInfo; } catch (ex) { @@ -90,7 +93,7 @@ export class CondaService implements ICondaService { const condaMetaDirectory = isWindows ? path.join(dir, 'conda-meta') : path.join(dir, '..', 'conda-meta'); return fs.directoryExistsAsync(condaMetaDirectory); } - public async getCondaEnvironment(interpreterPath: string): Promise<{ name: string, path: string } | undefined> { + public async getCondaEnvironment(interpreterPath: string): Promise<{ name: string; path: string } | undefined> { const isCondaEnv = await this.isCondaEnvironment(interpreterPath); if (!isCondaEnv) { return; @@ -118,18 +121,19 @@ export class CondaService implements ICondaService { // If still not available, then the user created the env after starting vs code. // The only solution is to get the user to re-start vscode. } - public async getCondaEnvironments(ignoreCache: boolean): Promise<({ name: string, path: string }[]) | undefined> { + public async getCondaEnvironments(ignoreCache: boolean): Promise<({ name: string; path: string }[]) | undefined> { // Global cache. const persistentFactory = this.serviceContainer.get(IPersistentStateFactory); // tslint:disable-next-line:no-any - const globalPersistence = persistentFactory.createGlobalPersistentState<{ data: { name: string, path: string }[] | undefined }>('CONDA_ENVIRONMENTS', undefined as any); + const globalPersistence = persistentFactory.createGlobalPersistentState<{ data: { name: string; path: string }[] | undefined }>('CONDA_ENVIRONMENTS', undefined as any); if (!ignoreCache && globalPersistence.value) { return globalPersistence.value.data; } try { const condaFile = await this.getCondaFile(); - const envInfo = await this.processService.exec(condaFile, ['env', 'list']).then(output => output.stdout); + const processService = await this.processServiceFactory.create(); + const envInfo = await processService.exec(condaFile, ['env', 'list']).then(output => output.stdout); const environments = this.condaHelper.parseCondaEnvironmentNames(envInfo); await globalPersistence.updateValue({ data: environments }); return environments; diff --git a/src/client/interpreter/locators/services/currentPathService.ts b/src/client/interpreter/locators/services/currentPathService.ts index 48444e0dfba9..cf2f5dc8d321 100644 --- a/src/client/interpreter/locators/services/currentPathService.ts +++ b/src/client/interpreter/locators/services/currentPathService.ts @@ -3,7 +3,7 @@ import * as _ from 'lodash'; import * as path from 'path'; import { Uri } from 'vscode'; import { IFileSystem } from '../../../common/platform/types'; -import { IProcessService } from '../../../common/process/types'; +import { IProcessServiceFactory } from '../../../common/process/types'; import { IConfigurationService } from '../../../common/types'; import { IServiceContainer } from '../../../ioc/types'; import { IInterpreterVersionService, InterpreterType, PythonInterpreter } from '../../contracts'; @@ -15,7 +15,7 @@ export class CurrentPathService extends CacheableLocatorService { private readonly fs: IFileSystem; public constructor(@inject(IVirtualEnvironmentManager) private virtualEnvMgr: IVirtualEnvironmentManager, @inject(IInterpreterVersionService) private versionProvider: IInterpreterVersionService, - @inject(IProcessService) private processService: IProcessService, + @inject(IProcessServiceFactory) private readonly processServiceFactory: IProcessServiceFactory, @inject(IServiceContainer) serviceContainer: IServiceContainer) { super('CurrentPathService', serviceContainer); this.fs = serviceContainer.get(IFileSystem); @@ -54,12 +54,16 @@ export class CurrentPathService extends CacheableLocatorService { } private async getInterpreter(pythonPath: string, defaultValue: string) { try { - const output = await this.processService.exec(pythonPath, ['-c', 'import sys;print(sys.executable)'], {}); - const executablePath = output.stdout.trim(); - if (executablePath.length > 0 && await this.fs.fileExistsAsync(executablePath)) { - return executablePath; - } - return defaultValue; + const processService = await this.processServiceFactory.create(); + return processService.exec(pythonPath, ['-c', 'import sys;print(sys.executable)'], {}) + .then(output => output.stdout.trim()) + .then(async value => { + if (value.length > 0 && await this.fs.fileExistsAsync(value)) { + return value; + } + return defaultValue; + }) + .catch(() => defaultValue); // Ignore exceptions in getting the executable. } catch { return defaultValue; // Ignore exceptions in getting the executable. } diff --git a/src/client/interpreter/locators/services/pipEnvService.ts b/src/client/interpreter/locators/services/pipEnvService.ts index c2db5ff46020..10d4d451a44a 100644 --- a/src/client/interpreter/locators/services/pipEnvService.ts +++ b/src/client/interpreter/locators/services/pipEnvService.ts @@ -6,7 +6,7 @@ import * as path from 'path'; import { Uri } from 'vscode'; import { IApplicationShell, IWorkspaceService } from '../../../common/application/types'; import { IFileSystem } from '../../../common/platform/types'; -import { IProcessService } from '../../../common/process/types'; +import { IProcessServiceFactory } from '../../../common/process/types'; import { ICurrentProcess } from '../../../common/types'; import { IEnvironmentVariablesProvider } from '../../../common/variables/types'; import { getPythonExecutable } from '../../../debugger/Common/Utils'; @@ -20,7 +20,7 @@ const pipEnvFileNameVariable = 'PIPENV_PIPFILE'; @injectable() export class PipEnvService extends CacheableLocatorService { private readonly versionService: IInterpreterVersionService; - private readonly process: IProcessService; + private readonly processServiceFactory: IProcessServiceFactory; private readonly workspace: IWorkspaceService; private readonly fs: IFileSystem; private readonly envVarsProvider: IEnvironmentVariablesProvider; @@ -28,7 +28,7 @@ export class PipEnvService extends CacheableLocatorService { constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { super('PipEnvService', serviceContainer); this.versionService = this.serviceContainer.get(IInterpreterVersionService); - this.process = this.serviceContainer.get(IProcessService); + this.processServiceFactory = this.serviceContainer.get(IProcessServiceFactory); this.workspace = this.serviceContainer.get(IWorkspaceService); this.fs = this.serviceContainer.get(IFileSystem); this.envVarsProvider = this.serviceContainer.get(IEnvironmentVariablesProvider); @@ -94,8 +94,8 @@ export class PipEnvService extends CacheableLocatorService { private async invokePipenv(arg: string, rootPath: string): Promise { try { - const env = await this.envVarsProvider.getEnvironmentVariables(Uri.file(rootPath)); - const result = await this.process.exec(execName, [arg], { cwd: rootPath, env }); + const processService = await this.processServiceFactory.create(Uri.file(rootPath)); + const result = await processService.exec(execName, [arg], { cwd: rootPath }); if (result) { const stdout = result.stdout ? result.stdout.trim() : ''; const stderr = result.stderr ? result.stderr.trim() : ''; diff --git a/src/client/interpreter/virtualEnvs/index.ts b/src/client/interpreter/virtualEnvs/index.ts index 4f535cb99151..bf05b6a3b917 100644 --- a/src/client/interpreter/virtualEnvs/index.ts +++ b/src/client/interpreter/virtualEnvs/index.ts @@ -2,22 +2,23 @@ // Licensed under the MIT License. import { inject, injectable } from 'inversify'; -import { IProcessService } from '../../common/process/types'; +import { IProcessServiceFactory } from '../../common/process/types'; import { IServiceContainer } from '../../ioc/types'; import { IVirtualEnvironmentManager } from './types'; @injectable() export class VirtualEnvironmentManager implements IVirtualEnvironmentManager { - private processService: IProcessService; + private processServiceFactory: IProcessServiceFactory; constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { - this.processService = serviceContainer.get(IProcessService); + this.processServiceFactory = serviceContainer.get(IProcessServiceFactory); } public async getEnvironmentName(pythonPath: string): Promise { // https://stackoverflow.com/questions/1871549/determine-if-python-is-running-inside-virtualenv // hasattr(sys, 'real_prefix') works for virtualenv while // '(hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix))' works for venv const code = 'import sys\nif hasattr(sys, "real_prefix"):\n print("virtualenv")\nelif hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix:\n print("venv")'; - const output = await this.processService.exec(pythonPath, ['-c', code]); + const processService = await this.processServiceFactory.create(); + const output = await processService.exec(pythonPath, ['-c', code]); if (output.stdout.length > 0) { return output.stdout.trim(); } diff --git a/src/client/providers/importSortProvider.ts b/src/client/providers/importSortProvider.ts index e7e6d3347133..74effd123947 100644 --- a/src/client/providers/importSortProvider.ts +++ b/src/client/providers/importSortProvider.ts @@ -3,14 +3,19 @@ import * as path from 'path'; import { TextDocument, TextEdit } from 'vscode'; import { PythonSettings } from '../common/configSettings'; import { getTempFileWithDocumentContents, getTextEditsFromPatch } from '../common/editor'; -import { ExecutionResult, IProcessService, IPythonExecutionFactory } from '../common/process/types'; +import { ExecutionResult, IProcessServiceFactory, IPythonExecutionFactory } from '../common/process/types'; +import { IServiceContainer } from '../ioc/types'; import { captureTelemetry } from '../telemetry'; import { FORMAT_SORT_IMPORTS } from '../telemetry/constants'; // tslint:disable-next-line:completed-docs export class PythonImportSortProvider { - constructor(private pythonExecutionFactory: IPythonExecutionFactory, - private processService: IProcessService) { } + private readonly processServiceFactory: IProcessServiceFactory; + private readonly pythonExecutionFactory: IPythonExecutionFactory; + constructor(serviceContainer: IServiceContainer) { + this.pythonExecutionFactory = serviceContainer.get(IPythonExecutionFactory); + this.processServiceFactory = serviceContainer.get(IProcessServiceFactory); + } @captureTelemetry(FORMAT_SORT_IMPORTS) public async sortImports(extensionDir: string, document: TextDocument): Promise { if (document.lineCount === 1) { @@ -30,7 +35,8 @@ export class PythonImportSortProvider { if (typeof isort === 'string' && isort.length > 0) { // Lets just treat this as a standard tool. - promise = this.processService.exec(isort, args, { throwOnStdErr: true }); + const processService = await this.processServiceFactory.create(document.uri); + promise = processService.exec(isort, args, { throwOnStdErr: true }); } else { promise = this.pythonExecutionFactory.create(document.uri) .then(executionService => executionService.exec([importScript].concat(args), { throwOnStdErr: true })); diff --git a/src/client/refactor/proxy.ts b/src/client/refactor/proxy.ts index 523b4e726084..725f5eb65005 100644 --- a/src/client/refactor/proxy.ts +++ b/src/client/refactor/proxy.ts @@ -2,8 +2,7 @@ import { ChildProcess } from 'child_process'; import * as path from 'path'; -import * as vscode from 'vscode'; -import { Uri } from 'vscode'; +import { Disposable, Position, Range, TextDocument, TextEditorOptions, Uri, window } from 'vscode'; import '../common/extensions'; import { createDeferred, Deferred } from '../common/helpers'; import { IPythonExecutionFactory } from '../common/process/types'; @@ -11,15 +10,15 @@ import { IPythonSettings } from '../common/types'; import { getWindowsLineEndingCount, IS_WINDOWS } from '../common/utils'; import { IServiceContainer } from '../ioc/types'; -export class RefactorProxy extends vscode.Disposable { +export class RefactorProxy extends Disposable { private _process?: ChildProcess; private _extensionDir: string; private _previousOutData: string = ''; private _previousStdErrData: string = ''; private _startedSuccessfully: boolean = false; private _commandResolve?: (value?: any | PromiseLike) => void; - private _commandReject: (reason?: any) => void; - private initialized: Deferred; + private _commandReject!: (reason?: any) => void; + private initialized!: Deferred; constructor(extensionDir: string, private pythonSettings: IPythonSettings, private workspaceRoot: string, private serviceContainer: IServiceContainer) { super(() => { }); @@ -33,7 +32,7 @@ export class RefactorProxy extends vscode.Disposable { } this._process = undefined; } - private getOffsetAt(document: vscode.TextDocument, position: vscode.Position): number { + private getOffsetAt(document: TextDocument, position: Position): number { if (!IS_WINDOWS) { return document.offsetAt(position); } @@ -47,9 +46,9 @@ export class RefactorProxy extends vscode.Disposable { return offset - winEols; } - public rename(document: vscode.TextDocument, name: string, filePath: string, range: vscode.Range, options?: vscode.TextEditorOptions): Promise { + public rename(document: TextDocument, name: string, filePath: string, range: Range, options?: TextEditorOptions): Promise { if (!options) { - options = vscode.window.activeTextEditor!.options; + options = window.activeTextEditor!.options; } const command = { lookup: 'rename', @@ -62,9 +61,9 @@ export class RefactorProxy extends vscode.Disposable { return this.sendCommand(JSON.stringify(command)); } - public extractVariable(document: vscode.TextDocument, name: string, filePath: string, range: vscode.Range, options?: vscode.TextEditorOptions): Promise { + public extractVariable(document: TextDocument, name: string, filePath: string, range: Range, options?: TextEditorOptions): Promise { if (!options) { - options = vscode.window.activeTextEditor!.options; + options = window.activeTextEditor!.options; } const command = { lookup: 'extract_variable', @@ -77,9 +76,9 @@ export class RefactorProxy extends vscode.Disposable { }; return this.sendCommand(JSON.stringify(command)); } - public extractMethod(document: vscode.TextDocument, name: string, filePath: string, range: vscode.Range, options?: vscode.TextEditorOptions): Promise { + public extractMethod(document: TextDocument, name: string, filePath: string, range: Range, options?: TextEditorOptions): Promise { if (!options) { - options = vscode.window.activeTextEditor!.options; + options = window.activeTextEditor!.options; } // Ensure last line is an empty line if (!document.lineAt(document.lineCount - 1).isEmptyOrWhitespace && range.start.line === document.lineCount - 1) { @@ -131,7 +130,7 @@ export class RefactorProxy extends vscode.Disposable { // Possible there was an exception in parsing the data returned // So append the data then parse it let dataStr = this._previousStdErrData = this._previousStdErrData + data + ''; - let errorResponse: { message: string, traceback: string, type: string }[]; + let errorResponse: { message: string; traceback: string; type: string }[]; try { errorResponse = dataStr.split(/\r?\n/g).filter(line => line.length > 0).map(resp => JSON.parse(resp)); this._previousStdErrData = ''; diff --git a/src/client/sortImports.ts b/src/client/sortImports.ts index 53fadc8769a9..b32488ec1ed3 100644 --- a/src/client/sortImports.ts +++ b/src/client/sortImports.ts @@ -1,6 +1,5 @@ import * as os from 'os'; import * as vscode from 'vscode'; -import { IProcessService, IPythonExecutionFactory } from './common/process/types'; import { IServiceContainer } from './ioc/types'; import * as sortProvider from './providers/importSortProvider'; @@ -29,15 +28,14 @@ export function activate(context: vscode.ExtensionContext, outChannel: vscode.Ou }); } return emptyLineAdded.then(() => { - const processService = serviceContainer.get(IProcessService); - const pythonExecutionFactory = serviceContainer.get(IPythonExecutionFactory); - return new sortProvider.PythonImportSortProvider(pythonExecutionFactory, processService).sortImports(rootDir, activeEditor.document); + return new sortProvider.PythonImportSortProvider(serviceContainer).sortImports(rootDir, activeEditor.document); }).then(changes => { if (!changes || changes!.length === 0) { return; } - return new Promise((resolve, reject) => activeEditor.edit(builder => changes.forEach(change => builder.replace(change.range, change.newText))).then(resolve, reject)); + // tslint:disable-next-line:no-any + return new Promise((resolve, reject) => activeEditor.edit(builder => changes.forEach(change => builder.replace(change.range, change.newText))).then(resolve, reject)); }).catch(error => { const message = typeof error === 'string' ? error : (error.message ? error.message : error); outChannel.appendLine(error); diff --git a/src/client/terminals/codeExecution/helper.ts b/src/client/terminals/codeExecution/helper.ts index de31a8b94049..94380e8f247c 100644 --- a/src/client/terminals/codeExecution/helper.ts +++ b/src/client/terminals/codeExecution/helper.ts @@ -7,9 +7,8 @@ import { Range, TextEditor, Uri } from 'vscode'; import { IApplicationShell, IDocumentManager } from '../../common/application/types'; import { EXTENSION_ROOT_DIR, PYTHON_LANGUAGE } from '../../common/constants'; import '../../common/extensions'; -import { IProcessService } from '../../common/process/types'; +import { IProcessServiceFactory } from '../../common/process/types'; import { IConfigurationService } from '../../common/types'; -import { IEnvironmentVariablesProvider } from '../../common/variables/types'; import { IServiceContainer } from '../../ioc/types'; import { ICodeExecutionHelper } from '../types'; @@ -17,14 +16,12 @@ import { ICodeExecutionHelper } from '../types'; export class CodeExecutionHelper implements ICodeExecutionHelper { private readonly documentManager: IDocumentManager; private readonly applicationShell: IApplicationShell; - private readonly envVariablesProvider: IEnvironmentVariablesProvider; - private readonly processService: IProcessService; + private readonly processServiceFactory: IProcessServiceFactory; private readonly configurationService: IConfigurationService; constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { this.documentManager = serviceContainer.get(IDocumentManager); this.applicationShell = serviceContainer.get(IApplicationShell); - this.envVariablesProvider = serviceContainer.get(IEnvironmentVariablesProvider); - this.processService = serviceContainer.get(IProcessService); + this.processServiceFactory = serviceContainer.get(IProcessServiceFactory); this.configurationService = serviceContainer.get(IConfigurationService); } public async normalizeLines(code: string, resource?: Uri): Promise { @@ -32,10 +29,10 @@ export class CodeExecutionHelper implements ICodeExecutionHelper { if (code.trim().length === 0) { return ''; } - const env = await this.envVariablesProvider.getEnvironmentVariables(resource); const pythonPath = this.configurationService.getSettings(resource).pythonPath; const args = [path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'normalizeForInterpreter.py'), code]; - const proc = await this.processService.exec(pythonPath, args, { env, throwOnStdErr: true }); + const processService = await this.processServiceFactory.create(resource); + const proc = await processService.exec(pythonPath, args, { throwOnStdErr: true }); return proc.stdout; } catch (ex) { console.error(ex, 'Python: Failed to normalize code for execution in terminal'); diff --git a/src/client/workspaceSymbols/generator.ts b/src/client/workspaceSymbols/generator.ts index 9d4a6458374b..86ea33be0eef 100644 --- a/src/client/workspaceSymbols/generator.ts +++ b/src/client/workspaceSymbols/generator.ts @@ -1,15 +1,15 @@ import * as fs from 'fs'; import * as path from 'path'; -import * as vscode from 'vscode'; +import { Disposable, OutputChannel, Uri, window } from 'vscode'; import { PythonSettings } from '../common/configSettings'; -import { IProcessService } from '../common/process/types'; +import { IProcessServiceFactory } from '../common/process/types'; import { IPythonSettings } from '../common/types'; import { captureTelemetry } from '../telemetry'; import { WORKSPACE_SYMBOLS_BUILD } from '../telemetry/constants'; -export class Generator implements vscode.Disposable { +export class Generator implements Disposable { private optionsFile: string; - private disposables: vscode.Disposable[]; + private disposables: Disposable[]; private pythonSettings: IPythonSettings; public get tagFilePath(): string { return this.pythonSettings.workspaceSymbols.tagFilePath; @@ -17,8 +17,8 @@ export class Generator implements vscode.Disposable { public get enabled(): boolean { return this.pythonSettings.workspaceSymbols.enabled; } - constructor(public readonly workspaceFolder: vscode.Uri, private output: vscode.OutputChannel, - private processService: IProcessService) { + constructor(public readonly workspaceFolder: Uri, private readonly output: OutputChannel, + private readonly processServiceFactory: IProcessServiceFactory) { this.disposables = []; this.optionsFile = path.join(__dirname, '..', '..', '..', 'resources', 'ctagOptions'); this.pythonSettings = PythonSettings.getInstance(workspaceFolder); @@ -60,8 +60,9 @@ export class Generator implements vscode.Disposable { args.push('-o', outputFile, '.'); this.output.appendLine(`${'-'.repeat(10)}Generating Tags${'-'.repeat(10)}`); this.output.appendLine(`${cmd} ${args.join(' ')}`); - const promise = new Promise((resolve, reject) => { - const result = this.processService.execObservable(cmd, args, { cwd: source.directory }); + const promise = new Promise(async (resolve, reject) => { + const processService = await this.processServiceFactory.create(); + const result = processService.execObservable(cmd, args, { cwd: source.directory }); let errorMsg = ''; result.out.subscribe(output => { if (output.source === 'stderr') { @@ -79,7 +80,7 @@ export class Generator implements vscode.Disposable { }); }); - vscode.window.setStatusBarMessage('Generating Tags', promise); + window.setStatusBarMessage('Generating Tags', promise); return promise; } diff --git a/src/client/workspaceSymbols/main.ts b/src/client/workspaceSymbols/main.ts index 9077e33a09d1..59919555ba11 100644 --- a/src/client/workspaceSymbols/main.ts +++ b/src/client/workspaceSymbols/main.ts @@ -1,8 +1,7 @@ -import * as vscode from 'vscode'; -import { OutputChannel, workspace } from 'vscode'; +import { CancellationToken, commands, Disposable, languages, OutputChannel, workspace } from 'vscode'; import { Commands, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; import { isNotInstalledError } from '../common/helpers'; -import { IProcessService } from '../common/process/types'; +import { IProcessServiceFactory } from '../common/process/types'; import { IInstaller, InstallerResponse, IOutputChannel, Product } from '../common/types'; import { fsExistsAsync } from '../common/utils'; import { IServiceContainer } from '../ioc/types'; @@ -11,20 +10,18 @@ import { WorkspaceSymbolProvider } from './provider'; const MAX_NUMBER_OF_ATTEMPTS_TO_INSTALL_AND_BUILD = 2; -export class WorkspaceSymbols implements vscode.Disposable { - private disposables: vscode.Disposable[]; +export class WorkspaceSymbols implements Disposable { + private disposables: Disposable[]; private generators: Generator[] = []; private readonly outputChannel: OutputChannel; - // tslint:disable-next-line:no-any - private timeout: any; constructor(private serviceContainer: IServiceContainer) { this.outputChannel = this.serviceContainer.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); this.disposables = []; this.disposables.push(this.outputChannel); this.registerCommands(); this.initializeGenerators(); - vscode.languages.registerWorkspaceSymbolProvider(new WorkspaceSymbolProvider(this.generators, this.outputChannel)); - this.disposables.push(vscode.workspace.onDidChangeWorkspaceFolders(() => this.initializeGenerators())); + languages.registerWorkspaceSymbolProvider(new WorkspaceSymbolProvider(this.generators, this.outputChannel)); + this.disposables.push(workspace.onDidChangeWorkspaceFolders(() => this.initializeGenerators())); } public dispose() { this.disposables.forEach(d => d.dispose()); @@ -35,21 +32,21 @@ export class WorkspaceSymbols implements vscode.Disposable { generator.dispose(); } - if (Array.isArray(vscode.workspace.workspaceFolders)) { - vscode.workspace.workspaceFolders.forEach(wkSpc => { - const processService = this.serviceContainer.get(IProcessService); - this.generators.push(new Generator(wkSpc.uri, this.outputChannel, processService)); + if (Array.isArray(workspace.workspaceFolders)) { + workspace.workspaceFolders.forEach(wkSpc => { + const processServiceFactory = this.serviceContainer.get(IProcessServiceFactory); + this.generators.push(new Generator(wkSpc.uri, this.outputChannel, processServiceFactory)); }); } } private registerCommands() { - this.disposables.push(vscode.commands.registerCommand(Commands.Build_Workspace_Symbols, async (rebuild: boolean = true, token?: vscode.CancellationToken) => { + this.disposables.push(commands.registerCommand(Commands.Build_Workspace_Symbols, async (rebuild: boolean = true, token?: CancellationToken) => { const promises = this.buildWorkspaceSymbols(rebuild, token); return Promise.all(promises); })); } // tslint:disable-next-line:no-any - private buildWorkspaceSymbols(rebuild: boolean = true, token?: vscode.CancellationToken): Promise[] { + private buildWorkspaceSymbols(rebuild: boolean = true, token?: CancellationToken): Promise[] { if (token && token.isCancellationRequested) { return []; } diff --git a/src/test/common/installer.test.ts b/src/test/common/installer.test.ts index c491d93cd680..09b0385eaca2 100644 --- a/src/test/common/installer.test.ts +++ b/src/test/common/installer.test.ts @@ -12,10 +12,9 @@ import { Logger } from '../../client/common/logger'; import { PersistentStateFactory } from '../../client/common/persistentState'; import { PathUtils } from '../../client/common/platform/pathUtils'; import { CurrentProcess } from '../../client/common/process/currentProcess'; -import { IProcessService } from '../../client/common/process/types'; +import { IProcessServiceFactory } from '../../client/common/process/types'; import { IConfigurationService, ICurrentProcess, IInstaller, ILogger, IPathUtils, IPersistentStateFactory, IsWindows, ModuleNamePurpose, Product } from '../../client/common/types'; -import { rootWorkspaceUri } from '../common'; -import { updateSetting } from '../common'; +import { rootWorkspaceUri, updateSetting } from '../common'; import { MockModuleInstaller } from '../mocks/moduleInstaller'; import { MockProcessService } from '../mocks/proc'; import { UnitTestIocContainer } from '../unittests/serviceRegistry'; @@ -68,7 +67,7 @@ suite('Installer', () => { async function testCheckingIfProductIsInstalled(product: Product) { const installer = ioc.serviceContainer.get(IInstaller); - const processService = ioc.serviceContainer.get(IProcessService); + const processService = await ioc.serviceContainer.get(IProcessServiceFactory).create() as MockProcessService; const checkInstalledDef = createDeferred(); processService.onExec((file, args, options, callback) => { const moduleName = installer.translateProductToModuleName(product, ModuleNamePurpose.run); diff --git a/src/test/common/moduleInstaller.test.ts b/src/test/common/moduleInstaller.test.ts index 31ea81de250a..84835f18f7b2 100644 --- a/src/test/common/moduleInstaller.test.ts +++ b/src/test/common/moduleInstaller.test.ts @@ -16,7 +16,7 @@ import { PathUtils } from '../../client/common/platform/pathUtils'; import { PlatformService } from '../../client/common/platform/platformService'; import { Architecture, IFileSystem, IPlatformService } from '../../client/common/platform/types'; import { CurrentProcess } from '../../client/common/process/currentProcess'; -import { IProcessService, IPythonExecutionFactory } from '../../client/common/process/types'; +import { IProcessServiceFactory, IPythonExecutionFactory } from '../../client/common/process/types'; import { ITerminalService, ITerminalServiceFactory } from '../../client/common/terminal/types'; import { IConfigurationService, ICurrentProcess, IInstaller, ILogger, IPathUtils, IPersistentStateFactory, IPythonSettings, IsWindows } from '../../client/common/types'; import { ICondaService, IInterpreterLocatorService, IInterpreterService, INTERPRETER_LOCATOR_SERVICE, InterpreterType, PIPENV_SERVICE, PythonInterpreter } from '../../client/interpreter/contracts'; @@ -104,7 +104,7 @@ suite('Module Installer', () => { ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, mockInterpreterLocator.object, INTERPRETER_LOCATOR_SERVICE); ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, TypeMoq.Mock.ofType().object, PIPENV_SERVICE); - const processService = ioc.serviceContainer.get(IProcessService); + const processService = await ioc.serviceContainer.get(IProcessServiceFactory).create() as MockProcessService; processService.onExec((file, args, options, callback) => { if (args.length > 1 && args[0] === '-c' && args[1] === 'import pip') { callback({ stdout: '' }); @@ -137,7 +137,7 @@ suite('Module Installer', () => { ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, mockInterpreterLocator.object, INTERPRETER_LOCATOR_SERVICE); ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, TypeMoq.Mock.ofType().object, PIPENV_SERVICE); - const processService = ioc.serviceContainer.get(IProcessService); + const processService = await ioc.serviceContainer.get(IProcessServiceFactory).create() as MockProcessService; processService.onExec((file, args, options, callback) => { if (args.length > 1 && args[0] === '-c' && args[1] === 'import pip') { callback({ stdout: '' }); diff --git a/src/test/common/process/execFactory.test.ts b/src/test/common/process/execFactory.test.ts index 9a8aa3ce4322..2802a38cb749 100644 --- a/src/test/common/process/execFactory.test.ts +++ b/src/test/common/process/execFactory.test.ts @@ -1,32 +1,37 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +// tslint:disable:max-func-body-length no-any + import { expect } from 'chai'; import * as TypeMoq from 'typemoq'; import { Uri } from 'vscode'; import { IFileSystem } from '../../../client/common/platform/types'; -import { IProcessService } from '../../../client/common/process/types'; +import { IProcessService, IProcessServiceFactory } from '../../../client/common/process/types'; import { IConfigurationService, IPythonSettings } from '../../../client/common/types'; import { IEnvironmentVariablesProvider } from '../../../client/common/variables/types'; import { InterpreterVersionService } from '../../../client/interpreter/interpreterVersion'; import { IServiceContainer } from '../../../client/ioc/types'; -// tslint:disable-next-line:max-func-body-length suite('PythonExecutableService', () => { let serviceContainer: TypeMoq.IMock; let configService: TypeMoq.IMock; let procService: TypeMoq.IMock; + let procServiceFactory: TypeMoq.IMock; setup(() => { serviceContainer = TypeMoq.Mock.ofType(); const envVarsProvider = TypeMoq.Mock.ofType(); + procServiceFactory = TypeMoq.Mock.ofType(); procService = TypeMoq.Mock.ofType(); configService = TypeMoq.Mock.ofType(); const fileSystem = TypeMoq.Mock.ofType(); fileSystem.setup(f => f.fileExistsAsync(TypeMoq.It.isAny())).returns(() => Promise.resolve(false)); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IFileSystem))).returns(() => fileSystem.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IEnvironmentVariablesProvider))).returns(() => envVarsProvider.object); - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IProcessService))).returns(() => procService.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IProcessServiceFactory))).returns(() => procServiceFactory.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationService))).returns(() => configService.object); + procService.setup((x: any) => x.then).returns(() => undefined); + procServiceFactory.setup(p => p.create(TypeMoq.It.isAny())).returns(() => Promise.resolve(procService.object)); envVarsProvider.setup(v => v.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})); }); @@ -38,7 +43,7 @@ suite('PythonExecutableService', () => { configService.setup(c => c.getSettings(TypeMoq.It.isValue(undefined))).returns(() => pythonSettings.object); procService.setup(p => p.exec(TypeMoq.It.isValue(pythonPath), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: pythonVersion })); - const versionService = new InterpreterVersionService(procService.object); + const versionService = new InterpreterVersionService(procServiceFactory.object); const version = await versionService.getVersion(pythonPath, ''); expect(version).to.be.equal(pythonVersion); @@ -52,7 +57,7 @@ suite('PythonExecutableService', () => { configService.setup(c => c.getSettings(TypeMoq.It.isValue(resource))).returns(() => pythonSettings.object); procService.setup(p => p.exec(TypeMoq.It.isValue(pythonPath), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: pythonVersion })); - const versionService = new InterpreterVersionService(procService.object); + const versionService = new InterpreterVersionService(procServiceFactory.object); const version = await versionService.getVersion(pythonPath, ''); expect(version).to.be.equal(pythonVersion); diff --git a/src/test/common/terminals/activation.conda.test.ts b/src/test/common/terminals/activation.conda.test.ts index d1d868dcc44f..b4498ce8edeb 100644 --- a/src/test/common/terminals/activation.conda.test.ts +++ b/src/test/common/terminals/activation.conda.test.ts @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +// tslint:disable:max-func-body-length no-any + import { expect } from 'chai'; import * as path from 'path'; import * as TypeMoq from 'typemoq'; @@ -8,7 +10,7 @@ import { Disposable } from 'vscode'; import { EnumEx } from '../../../client/common/enumUtils'; import '../../../client/common/extensions'; import { IFileSystem, IPlatformService } from '../../../client/common/platform/types'; -import { IProcessService } from '../../../client/common/process/types'; +import { IProcessService, IProcessServiceFactory } from '../../../client/common/process/types'; import { CondaActivationCommandProvider } from '../../../client/common/terminal/environmentActivationProviders/condaActivationProvider'; import { TerminalHelper } from '../../../client/common/terminal/helper'; import { ITerminalActivationCommandProvider, TerminalShellType } from '../../../client/common/terminal/types'; @@ -16,7 +18,6 @@ import { IConfigurationService, IDisposableRegistry, IPythonSettings, ITerminalS import { ICondaService } from '../../../client/interpreter/contracts'; import { IServiceContainer } from '../../../client/ioc/types'; -// tslint:disable-next-line:max-func-body-length suite('Terminal Environment Activation conda', () => { let terminalHelper: TerminalHelper; let disposables: Disposable[] = []; @@ -26,6 +27,7 @@ suite('Terminal Environment Activation conda', () => { let pythonSettings: TypeMoq.IMock; let serviceContainer: TypeMoq.IMock; let processService: TypeMoq.IMock; + let procServiceFactory: TypeMoq.IMock; let condaService: TypeMoq.IMock; setup(() => { @@ -37,10 +39,13 @@ suite('Terminal Environment Activation conda', () => { platformService = TypeMoq.Mock.ofType(); processService = TypeMoq.Mock.ofType(); condaService = TypeMoq.Mock.ofType(); + processService.setup((x: any) => x.then).returns(() => undefined); + procServiceFactory = TypeMoq.Mock.ofType(); + procServiceFactory.setup(p => p.create(TypeMoq.It.isAny())).returns(() => Promise.resolve(processService.object)); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPlatformService), TypeMoq.It.isAny())).returns(() => platformService.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IFileSystem), TypeMoq.It.isAny())).returns(() => fileSystem.object); - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IProcessService), TypeMoq.It.isAny())).returns(() => processService.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IProcessServiceFactory), TypeMoq.It.isAny())).returns(() => procServiceFactory.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ICondaService), TypeMoq.It.isAny())).returns(() => condaService.object); const configService = TypeMoq.Mock.ofType(); diff --git a/src/test/format/extension.format.test.ts b/src/test/format/extension.format.test.ts index f50e678b3886..503de9c117c2 100644 --- a/src/test/format/extension.format.test.ts +++ b/src/test/format/extension.format.test.ts @@ -1,7 +1,8 @@ import * as fs from 'fs-extra'; import * as path from 'path'; import * as vscode from 'vscode'; -import { IProcessService, IPythonExecutionFactory } from '../../client/common/process/types'; +import { CancellationTokenSource, Position, Uri, window, workspace } from 'vscode'; +import { IProcessServiceFactory, IPythonExecutionFactory } from '../../client/common/process/types'; import { AutoPep8Formatter } from '../../client/formatters/autoPep8Formatter'; import { BlackFormatter } from '../../client/formatters/blackFormatter'; import { YapfFormatter } from '../../client/formatters/yapfFormatter'; @@ -10,7 +11,7 @@ import { MockProcessService } from '../mocks/proc'; import { compareFiles } from '../textUtils'; import { UnitTestIocContainer } from '../unittests/serviceRegistry'; -const ch = vscode.window.createOutputChannel('Tests'); +const ch = window.createOutputChannel('Tests'); const formatFilesPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'formatting'); const workspaceRootPath = path.join(__dirname, '..', '..', '..', 'src', 'test'); const originalUnformattedFile = path.join(formatFilesPath, 'fileToFormat.py'); @@ -85,8 +86,8 @@ suite('Formatting', () => { ioc.registerMockProcessTypes(); } - function injectFormatOutput(outputFileName: string) { - const procService = ioc.serviceContainer.get(IProcessService); + async function injectFormatOutput(outputFileName: string) { + const procService = await ioc.serviceContainer.get(IProcessServiceFactory).create() as MockProcessService; procService.onExecObservable((file, args, options, callback) => { if (args.indexOf('--diff') >= 0) { callback({ @@ -102,7 +103,7 @@ suite('Formatting', () => { const textEditor = await vscode.window.showTextDocument(textDocument); const options = { insertSpaces: textEditor.options.insertSpaces! as boolean, tabSize: textEditor.options.tabSize! as number }; - injectFormatOutput(outputFileName); + await injectFormatOutput(outputFileName); const edits = await formatter.formatDocument(textDocument, options, new vscode.CancellationTokenSource().token); await textEditor.edit(editBuilder => { @@ -137,11 +138,11 @@ suite('Formatting', () => { fs.copySync(path.join(sourceDir, originalName), fileToFormat, { overwrite: true }); fs.copySync(path.join(sourceDir, resultsName), formattedFile, { overwrite: true }); - const textDocument = await vscode.workspace.openTextDocument(fileToFormat); - const textEditor = await vscode.window.showTextDocument(textDocument); + const textDocument = await workspace.openTextDocument(fileToFormat); + const textEditor = await window.showTextDocument(textDocument); await textEditor.edit(builder => { // Make file dirty. Trailing blanks will be removed. - builder.insert(new vscode.Position(0, 0), '\n \n'); + builder.insert(new Position(0, 0), '\n \n'); }); const dir = path.dirname(fileToFormat); diff --git a/src/test/format/extension.sort.test.ts b/src/test/format/extension.sort.test.ts index af88891e6e91..884113bdb77b 100644 --- a/src/test/format/extension.sort.test.ts +++ b/src/test/format/extension.sort.test.ts @@ -3,7 +3,6 @@ import * as fs from 'fs'; import { EOL } from 'os'; import * as path from 'path'; import { commands, ConfigurationTarget, Position, Range, Uri, window, workspace } from 'vscode'; -import { IProcessService, IPythonExecutionFactory } from '../../client/common/process/types'; import { PythonImportSortProvider } from '../../client/providers/importSortProvider'; import { updateSetting } from '../common'; import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST } from '../initialize'; @@ -39,9 +38,7 @@ suite('Sorting', () => { fs.writeFileSync(fileToFormatWithConfig1, fs.readFileSync(originalFileToFormatWithConfig1)); await updateSetting('sortImports.args', [], Uri.file(sortingPath), configTarget); await closeActiveWindows(); - const pythonExecutionFactory = ioc.serviceContainer.get(IPythonExecutionFactory); - const processService = ioc.serviceContainer.get(IProcessService); - sorter = new PythonImportSortProvider(pythonExecutionFactory, processService); + sorter = new PythonImportSortProvider(ioc.serviceContainer); }); teardown(async () => { ioc.dispose(); diff --git a/src/test/interpreters/condaService.test.ts b/src/test/interpreters/condaService.test.ts index f6a16891e71a..21059a94e537 100644 --- a/src/test/interpreters/condaService.test.ts +++ b/src/test/interpreters/condaService.test.ts @@ -1,3 +1,4 @@ +// tslint:disable:no-require-imports no-var-requires no-any max-func-body-length import * as assert from 'assert'; import { expect } from 'chai'; import { EOL } from 'os'; @@ -5,19 +6,17 @@ import * as path from 'path'; import * as TypeMoq from 'typemoq'; import { FileSystem } from '../../client/common/platform/fileSystem'; import { IFileSystem, IPlatformService } from '../../client/common/platform/types'; -import { IProcessService } from '../../client/common/process/types'; +import { IProcessService, IProcessServiceFactory } from '../../client/common/process/types'; import { ILogger, IPersistentStateFactory } from '../../client/common/types'; import { IInterpreterLocatorService, InterpreterType, PythonInterpreter } from '../../client/interpreter/contracts'; import { CondaService, KNOWN_CONDA_LOCATIONS } from '../../client/interpreter/locators/services/condaService'; import { IServiceContainer } from '../../client/ioc/types'; import { MockState } from './mocks'; -// tslint:disable-next-line:no-require-imports no-var-requires const untildify: (value: string) => string = require('untildify'); const environmentsPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'environments'); -// tslint:disable-next-line:max-func-body-length suite('Interpreters Conda Service', () => { let processService: TypeMoq.IMock; let platformService: TypeMoq.IMock; @@ -25,14 +24,19 @@ suite('Interpreters Conda Service', () => { let fileSystem: TypeMoq.IMock; let registryInterpreterLocatorService: TypeMoq.IMock; let serviceContainer: TypeMoq.IMock; + let procServiceFactory: TypeMoq.IMock; setup(async () => { const logger = TypeMoq.Mock.ofType(); processService = TypeMoq.Mock.ofType(); platformService = TypeMoq.Mock.ofType(); registryInterpreterLocatorService = TypeMoq.Mock.ofType(); fileSystem = TypeMoq.Mock.ofType(); + procServiceFactory = TypeMoq.Mock.ofType(); + processService.setup((x: any) => x.then).returns(() => undefined); + procServiceFactory.setup(p => p.create(TypeMoq.It.isAny())).returns(() => Promise.resolve(processService.object)); + serviceContainer = TypeMoq.Mock.ofType(); - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IProcessService), TypeMoq.It.isAny())).returns(() => processService.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IProcessServiceFactory), TypeMoq.It.isAny())).returns(() => procServiceFactory.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPlatformService), TypeMoq.It.isAny())).returns(() => platformService.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ILogger), TypeMoq.It.isAny())).returns(() => logger.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IFileSystem), TypeMoq.It.isAny())).returns(() => fileSystem.object); diff --git a/src/test/interpreters/currentPathService.test.ts b/src/test/interpreters/currentPathService.test.ts index 4d136735edf9..19e44c6eca76 100644 --- a/src/test/interpreters/currentPathService.test.ts +++ b/src/test/interpreters/currentPathService.test.ts @@ -3,17 +3,18 @@ 'use strict'; +// tslint:disable:max-func-body-length no-any + import { expect } from 'chai'; import * as TypeMoq from 'typemoq'; import { IFileSystem } from '../../client/common/platform/types'; -import { IProcessService } from '../../client/common/process/types'; +import { IProcessService, IProcessServiceFactory } from '../../client/common/process/types'; import { IConfigurationService, IPersistentState, IPersistentStateFactory, IPythonSettings } from '../../client/common/types'; import { IInterpreterVersionService, InterpreterType, PythonInterpreter } from '../../client/interpreter/contracts'; import { CurrentPathService } from '../../client/interpreter/locators/services/currentPathService'; import { IVirtualEnvironmentManager } from '../../client/interpreter/virtualEnvs/types'; import { IServiceContainer } from '../../client/ioc/types'; -// tslint:disable-next-line:max-func-body-length suite('Interpreters CurrentPath Service', () => { let processService: TypeMoq.IMock; let fileSystem: TypeMoq.IMock; @@ -32,21 +33,22 @@ suite('Interpreters CurrentPath Service', () => { configurationService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); const persistentStateFactory = TypeMoq.Mock.ofType(); persistentState = TypeMoq.Mock.ofType>(); - // tslint:disable-next-line:no-any + processService.setup((x: any) => x.then).returns(() => undefined); persistentState.setup(p => p.value).returns(() => undefined as any); persistentState.setup(p => p.updateValue(TypeMoq.It.isAny())).returns(() => Promise.resolve()); fileSystem = TypeMoq.Mock.ofType(); persistentStateFactory.setup(p => p.createGlobalPersistentState(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => persistentState.object); + const procServiceFactory = TypeMoq.Mock.ofType(); + procServiceFactory.setup(p => p.create(TypeMoq.It.isAny())).returns(() => Promise.resolve(processService.object)); serviceContainer = TypeMoq.Mock.ofType(); - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IProcessService), TypeMoq.It.isAny())).returns(() => processService.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IVirtualEnvironmentManager), TypeMoq.It.isAny())).returns(() => virtualEnvironmentManager.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInterpreterVersionService), TypeMoq.It.isAny())).returns(() => interpreterVersionService.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IFileSystem), TypeMoq.It.isAny())).returns(() => fileSystem.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPersistentStateFactory), TypeMoq.It.isAny())).returns(() => persistentStateFactory.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationService), TypeMoq.It.isAny())).returns(() => configurationService.object); - currentPathService = new CurrentPathService(virtualEnvironmentManager.object, interpreterVersionService.object, processService.object, serviceContainer.object); + currentPathService = new CurrentPathService(virtualEnvironmentManager.object, interpreterVersionService.object, procServiceFactory.object, serviceContainer.object); }); test('Interpreters that do not exist on the file system are not excluded from the list', async () => { diff --git a/src/test/interpreters/interpreterVersion.test.ts b/src/test/interpreters/interpreterVersion.test.ts index 9e78d8ee5e66..6b9c9da81ad7 100644 --- a/src/test/interpreters/interpreterVersion.test.ts +++ b/src/test/interpreters/interpreterVersion.test.ts @@ -4,7 +4,7 @@ import { assert, expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import '../../client/common/extensions'; -import { IProcessService } from '../../client/common/process/types'; +import { IProcessServiceFactory } from '../../client/common/process/types'; import { IInterpreterVersionService } from '../../client/interpreter/contracts'; import { PIP_VERSION_REGEX } from '../../client/interpreter/interpreterVersion'; import { PYTHON_PATH } from '../common'; @@ -31,7 +31,7 @@ suite('Interpreters display version', () => { } test('Must return the Python Version', async () => { - const pythonProcess = ioc.serviceContainer.get(IProcessService); + const pythonProcess = await ioc.serviceContainer.get(IProcessServiceFactory).create(); const output = await pythonProcess.exec(PYTHON_PATH, ['--version'], { cwd: __dirname, mergeStdOutErr: true }); const version = output.stdout.splitLines()[0]; const interpreterVersion = ioc.serviceContainer.get(IInterpreterVersionService); @@ -44,7 +44,7 @@ suite('Interpreters display version', () => { assert.equal(pyVersion, 'DEFAULT_TEST_VALUE', 'Incorrect version'); }); test('Must return the pip Version', async () => { - const pythonProcess = ioc.serviceContainer.get(IProcessService); + const pythonProcess = await ioc.serviceContainer.get(IProcessServiceFactory).create(); const result = await pythonProcess.exec(PYTHON_PATH, ['-m', 'pip', '--version'], { cwd: __dirname, mergeStdOutErr: true }); const output = result.stdout.splitLines()[0]; // Take the second part, see below example. diff --git a/src/test/interpreters/pipEnvService.test.ts b/src/test/interpreters/pipEnvService.test.ts index 65545cfc7b53..4571dd14b939 100644 --- a/src/test/interpreters/pipEnvService.test.ts +++ b/src/test/interpreters/pipEnvService.test.ts @@ -12,7 +12,7 @@ import { Uri, WorkspaceFolder } from 'vscode'; import { IApplicationShell, IWorkspaceService } from '../../client/common/application/types'; import { EnumEx } from '../../client/common/enumUtils'; import { IFileSystem } from '../../client/common/platform/types'; -import { IProcessService } from '../../client/common/process/types'; +import { IProcessService, IProcessServiceFactory } from '../../client/common/process/types'; import { ICurrentProcess, IPersistentState, IPersistentStateFactory } from '../../client/common/types'; import { IEnvironmentVariablesProvider } from '../../client/common/variables/types'; import { IInterpreterLocatorService, IInterpreterVersionService } from '../../client/interpreter/contracts'; @@ -38,6 +38,7 @@ suite('Interpreters - PipEnv', () => { let appShell: TypeMoq.IMock; let persistentStateFactory: TypeMoq.IMock; let envVarsProvider: TypeMoq.IMock; + let procServiceFactory: TypeMoq.IMock; setup(() => { serviceContainer = TypeMoq.Mock.ofType(); const workspaceService = TypeMoq.Mock.ofType(); @@ -48,6 +49,9 @@ suite('Interpreters - PipEnv', () => { currentProcess = TypeMoq.Mock.ofType(); persistentStateFactory = TypeMoq.Mock.ofType(); envVarsProvider = TypeMoq.Mock.ofType(); + procServiceFactory = TypeMoq.Mock.ofType(); + processService.setup((x: any) => x.then).returns(() => undefined); + procServiceFactory.setup(p => p.create(TypeMoq.It.isAny())).returns(() => Promise.resolve(processService.object)); // tslint:disable-next-line:no-any const persistentState = TypeMoq.Mock.ofType>(); @@ -61,9 +65,9 @@ suite('Interpreters - PipEnv', () => { workspaceService.setup(w => w.getWorkspaceFolder(TypeMoq.It.isAny())).returns(() => workspaceFolder.object); workspaceService.setup(w => w.rootPath).returns(() => rootWorkspace); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IProcessServiceFactory), TypeMoq.It.isAny())).returns(() => procServiceFactory.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IWorkspaceService))).returns(() => workspaceService.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInterpreterVersionService))).returns(() => interpreterVersionService.object); - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IProcessService))).returns(() => processService.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ICurrentProcess))).returns(() => currentProcess.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IFileSystem))).returns(() => fileSystem.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IApplicationShell))).returns(() => appShell.object); @@ -101,7 +105,6 @@ suite('Interpreters - PipEnv', () => { }); test(`Should display warning message if there is a \'PipFile\' but \'pipenv --venv\' failes with stderr ${testSuffix}`, async () => { const env = {}; - envVarsProvider.setup(e => e.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})).verifiable(TypeMoq.Times.once()); currentProcess.setup(c => c.env).returns(() => env); processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stderr: 'PipEnv Failed', stdout: '' })); fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(true)); @@ -109,13 +112,11 @@ suite('Interpreters - PipEnv', () => { const environments = await pipEnvService.getInterpreters(resource); expect(environments).to.be.deep.equal([]); - envVarsProvider.verifyAll(); appShell.verifyAll(); }); test(`Should return interpreter information${testSuffix}`, async () => { const env = {}; const venvDir = 'one'; - envVarsProvider.setup(e => e.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})).verifiable(TypeMoq.Times.once()); currentProcess.setup(c => c.env).returns(() => env); processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: venvDir })); interpreterVersionService.setup(v => v.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('xyz')); @@ -125,7 +126,6 @@ suite('Interpreters - PipEnv', () => { expect(environments).to.be.lengthOf(1); fileSystem.verifyAll(); - envVarsProvider.verifyAll(); }); test(`Should return interpreter information using PipFile defined in Env variable${testSuffix}`, async () => { const envPipFile = 'XYZ'; @@ -133,7 +133,6 @@ suite('Interpreters - PipEnv', () => { PIPENV_PIPFILE: envPipFile }; const venvDir = 'one'; - envVarsProvider.setup(e => e.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})).verifiable(TypeMoq.Times.once()); currentProcess.setup(c => c.env).returns(() => env); processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: venvDir })); interpreterVersionService.setup(v => v.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('xyz')); @@ -144,7 +143,6 @@ suite('Interpreters - PipEnv', () => { expect(environments).to.be.lengthOf(1); fileSystem.verifyAll(); - envVarsProvider.verifyAll(); }); }); }); diff --git a/src/test/interpreters/virtualEnvManager.test.ts b/src/test/interpreters/virtualEnvManager.test.ts index 3d00204b9269..4db50c4ac344 100644 --- a/src/test/interpreters/virtualEnvManager.test.ts +++ b/src/test/interpreters/virtualEnvManager.test.ts @@ -1,12 +1,14 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +// tslint:disable:no-any + import { expect } from 'chai'; import { Container } from 'inversify'; import * as TypeMoq from 'typemoq'; import { BufferDecoder } from '../../client/common/process/decoder'; import { ProcessService } from '../../client/common/process/proc'; -import { IBufferDecoder, IProcessService } from '../../client/common/process/types'; +import { IBufferDecoder, IProcessService, IProcessServiceFactory } from '../../client/common/process/types'; import { VirtualEnvironmentManager } from '../../client/interpreter/virtualEnvs'; import { ServiceContainer } from '../../client/ioc/container'; import { ServiceManager } from '../../client/ioc/serviceManager'; @@ -15,7 +17,6 @@ import { PYTHON_PATH } from '../common'; suite('Virtual environment manager', () => { let serviceManager: ServiceManager; let serviceContainer: ServiceContainer; - let process: TypeMoq.IMock; setup(async () => { const cont = new Container(); @@ -28,7 +29,9 @@ suite('Virtual environment manager', () => { test('Virtualenv Python environment suffix', async () => testSuffix('virtualenv')); test('Run actual virtual env detection code', async () => { - serviceManager.addSingleton(IProcessService, ProcessService); + const processServiceFactory = TypeMoq.Mock.ofType(); + processServiceFactory.setup(f => f.create(TypeMoq.It.isAny())).returns(() => Promise.resolve(new ProcessService(new BufferDecoder(), process.env as any))); + serviceManager.addSingletonInstance(IProcessServiceFactory, processServiceFactory.object); serviceManager.addSingleton(IBufferDecoder, BufferDecoder); const venvManager = new VirtualEnvironmentManager(serviceContainer); const name = await venvManager.getEnvironmentName(PYTHON_PATH); @@ -37,11 +40,14 @@ suite('Virtual environment manager', () => { }); async function testSuffix(expectedName: string) { - process = TypeMoq.Mock.ofType(); - serviceManager.addSingletonInstance(IProcessService, process.object); + const processService = TypeMoq.Mock.ofType(); + const processServiceFactory = TypeMoq.Mock.ofType(); + processService.setup((x: any) => x.then).returns(() => undefined); + processServiceFactory.setup(f => f.create(TypeMoq.It.isAny())).returns(() => Promise.resolve(processService.object)); + serviceManager.addSingletonInstance(IProcessServiceFactory, processServiceFactory.object); const venvManager = new VirtualEnvironmentManager(serviceContainer); - process + processService .setup(x => x.exec(PYTHON_PATH, TypeMoq.It.isAny())) .returns(() => Promise.resolve({ stdout: expectedName, diff --git a/src/test/mocks/proc.ts b/src/test/mocks/proc.ts index e910d0da4720..dcf1d5388859 100644 --- a/src/test/mocks/proc.ts +++ b/src/test/mocks/proc.ts @@ -1,5 +1,4 @@ import { EventEmitter } from 'events'; -import { inject, injectable } from 'inversify'; import 'rxjs/add/observable/of'; import { Observable } from 'rxjs/Observable'; import { ExecutionResult, IProcessService, ObservableExecutionResult, Output, SpawnOptions } from '../../client/common/process/types'; @@ -9,9 +8,8 @@ type ExecCallback = (result: ExecutionResult) => void; export const IOriginalProcessService = Symbol('IProcessService'); -@injectable() export class MockProcessService extends EventEmitter implements IProcessService { - constructor( @inject(IOriginalProcessService) private procService: IProcessService) { + constructor(private procService: IProcessService) { super(); } public onExecObservable(handler: (file: string, args: string[], options: SpawnOptions, callback: ExecObservableCallback) => void) { diff --git a/src/test/serviceRegistry.ts b/src/test/serviceRegistry.ts index f38344a474bd..5ce82911dea3 100644 --- a/src/test/serviceRegistry.ts +++ b/src/test/serviceRegistry.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. import { Container } from 'inversify'; +import * as TypeMoq from 'typemoq'; import { Disposable, Memento, OutputChannel } from 'vscode'; import { STANDARD_OUTPUT_CHANNEL } from '../client/common/constants'; import { Logger } from '../client/common/logger'; @@ -16,7 +17,7 @@ import { ProcessService } from '../client/common/process/proc'; import { PythonExecutionFactory } from '../client/common/process/pythonExecutionFactory'; import { PythonToolExecutionService } from '../client/common/process/pythonToolService'; import { registerTypes as processRegisterTypes } from '../client/common/process/serviceRegistry'; -import { IBufferDecoder, IProcessService, IPythonExecutionFactory, IPythonToolExecutionService } from '../client/common/process/types'; +import { IBufferDecoder, IProcessServiceFactory, IPythonExecutionFactory, IPythonToolExecutionService } from '../client/common/process/types'; import { registerTypes as commonRegisterTypes } from '../client/common/serviceRegistry'; import { GLOBAL_MEMENTO, ICurrentProcess, IDisposableRegistry, ILogger, IMemento, IOutputChannel, IPathUtils, Is64Bit, IsWindows, WORKSPACE_MEMENTO } from '../client/common/types'; import { registerTypes as variableRegisterTypes } from '../client/common/variables/serviceRegistry'; @@ -30,7 +31,7 @@ import { TEST_OUTPUT_CHANNEL } from '../client/unittests/common/constants'; import { registerTypes as unittestsRegisterTypes } from '../client/unittests/serviceRegistry'; import { MockOutputChannel } from './mockClasses'; import { MockMemento } from './mocks/mementos'; -import { IOriginalProcessService, MockProcessService } from './mocks/proc'; +import { MockProcessService } from './mocks/proc'; import { MockProcess } from './mocks/process'; export class IocContainer { @@ -93,8 +94,11 @@ export class IocContainer { } public registerMockProcessTypes() { this.serviceManager.addSingleton(IBufferDecoder, BufferDecoder); - this.serviceManager.addSingleton(IOriginalProcessService, ProcessService); - this.serviceManager.addSingleton(IProcessService, MockProcessService); + const processServiceFactory = TypeMoq.Mock.ofType(); + // tslint:disable-next-line:no-any + const processService = new MockProcessService(new ProcessService(new BufferDecoder(), process.env as any)); + processServiceFactory.setup(f => f.create(TypeMoq.It.isAny())).returns(() => Promise.resolve(processService)); + this.serviceManager.addSingletonInstance(IProcessServiceFactory, processServiceFactory.object); this.serviceManager.addSingleton(IPythonExecutionFactory, PythonExecutionFactory); this.serviceManager.addSingleton(IPythonToolExecutionService, PythonToolExecutionService); } diff --git a/src/test/terminals/codeExecution/helper.test.ts b/src/test/terminals/codeExecution/helper.test.ts index 56db9e5657cd..62c00bbd0115 100644 --- a/src/test/terminals/codeExecution/helper.test.ts +++ b/src/test/terminals/codeExecution/helper.test.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -// tslint:disable:no-multiline-string no-trailing-whitespace +// tslint:disable:no-multiline-string no-trailing-whitespace max-func-body-length no-any import { expect } from 'chai'; import * as fs from 'fs-extra'; @@ -14,7 +14,7 @@ import { EXTENSION_ROOT_DIR, PYTHON_LANGUAGE } from '../../../client/common/cons import '../../../client/common/extensions'; import { BufferDecoder } from '../../../client/common/process/decoder'; import { ProcessService } from '../../../client/common/process/proc'; -import { IProcessService } from '../../../client/common/process/types'; +import { IProcessService, IProcessServiceFactory } from '../../../client/common/process/types'; import { IConfigurationService, IPythonSettings } from '../../../client/common/types'; import { IEnvironmentVariablesProvider } from '../../../client/common/variables/types'; import { IServiceContainer } from '../../../client/ioc/types'; @@ -24,7 +24,6 @@ import { PYTHON_PATH } from '../../common'; const TEST_FILES_PATH = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'terminalExec'); -// tslint:disable-next-line:max-func-body-length suite('Terminal - Code Execution Helper', () => { let documentManager: TypeMoq.IMock; let applicationShell: TypeMoq.IMock; @@ -42,12 +41,15 @@ suite('Terminal - Code Execution Helper', () => { configService = TypeMoq.Mock.ofType(); const pythonSettings = TypeMoq.Mock.ofType(); pythonSettings.setup(p => p.pythonPath).returns(() => PYTHON_PATH); + processService.setup((x: any) => x.then).returns(() => undefined); configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); envVariablesProvider.setup(e => e.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})); + const processServiceFactory = TypeMoq.Mock.ofType(); + processServiceFactory.setup(p => p.create(TypeMoq.It.isAny())).returns(() => Promise.resolve(processService.object)); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IProcessServiceFactory), TypeMoq.It.isAny())).returns(() => processServiceFactory.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IDocumentManager), TypeMoq.It.isAny())).returns(() => documentManager.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IApplicationShell), TypeMoq.It.isAny())).returns(() => applicationShell.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IEnvironmentVariablesProvider), TypeMoq.It.isAny())).returns(() => envVariablesProvider.object); - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IProcessService), TypeMoq.It.isAny())).returns(() => processService.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationService), TypeMoq.It.isAny())).returns(() => configService.object); helper = new CodeExecutionHelper(serviceContainer.object); diff --git a/src/test/unittests/nosetest.disovery.test.ts b/src/test/unittests/nosetest.disovery.test.ts index d1acbecf149b..51001b1ab568 100644 --- a/src/test/unittests/nosetest.disovery.test.ts +++ b/src/test/unittests/nosetest.disovery.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; -import { IProcessService } from '../../client/common/process/types'; +import { IProcessServiceFactory } from '../../client/common/process/types'; import { CommandSource } from '../../client/unittests/common/constants'; import { ITestManagerFactory } from '../../client/unittests/common/types'; import { rootWorkspaceUri, updateSetting } from '../common'; @@ -62,8 +62,8 @@ suite('Unit Tests - nose - discovery with mocked process output', () => { ioc.registerMockProcessTypes(); } - function injectTestDiscoveryOutput(outputFileName: string) { - const procService = ioc.serviceContainer.get(IProcessService); + async function injectTestDiscoveryOutput(outputFileName: string) { + const procService = await ioc.serviceContainer.get(IProcessServiceFactory).create() as MockProcessService; procService.onExecObservable((file, args, options, callback) => { if (args.indexOf('--collect-only') >= 0) { let out = fs.readFileSync(path.join(UNITTEST_TEST_FILES_PATH, outputFileName), 'utf8'); @@ -78,7 +78,7 @@ suite('Unit Tests - nose - discovery with mocked process output', () => { } test('Discover Tests (single test file)', async () => { - injectTestDiscoveryOutput('one.output'); + await injectTestDiscoveryOutput('one.output'); const factory = ioc.serviceContainer.get(ITestManagerFactory); const testManager = factory('nosetest', rootWorkspaceUri, UNITTEST_SINGLE_TEST_FILE_PATH); const tests = await testManager.discoverTests(CommandSource.ui, true, true); @@ -89,7 +89,7 @@ suite('Unit Tests - nose - discovery with mocked process output', () => { }); test('Check that nameToRun in testSuites has class name after : (single test file)', async () => { - injectTestDiscoveryOutput('two.output'); + await injectTestDiscoveryOutput('two.output'); const factory = ioc.serviceContainer.get(ITestManagerFactory); const testManager = factory('nosetest', rootWorkspaceUri, UNITTEST_SINGLE_TEST_FILE_PATH); const tests = await testManager.discoverTests(CommandSource.ui, true, true); @@ -99,7 +99,7 @@ suite('Unit Tests - nose - discovery with mocked process output', () => { assert.equal(tests.testSuites.every(t => t.testSuite.name === t.testSuite.nameToRun.split(':')[1]), true, 'Suite name does not match class name'); }); test('Discover Tests (-m=test)', async () => { - injectTestDiscoveryOutput('three.output'); + await injectTestDiscoveryOutput('three.output'); await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); const factory = ioc.serviceContainer.get(ITestManagerFactory); const testManager = factory('nosetest', rootWorkspaceUri, UNITTEST_TEST_FILES_PATH); @@ -115,7 +115,7 @@ suite('Unit Tests - nose - discovery with mocked process output', () => { }); test('Discover Tests (-w=specific -m=tst)', async () => { - injectTestDiscoveryOutput('four.output'); + await injectTestDiscoveryOutput('four.output'); await updateSetting('unitTest.nosetestArgs', ['-w', 'specific', '-m', 'tst'], rootWorkspaceUri, configTarget); const factory = ioc.serviceContainer.get(ITestManagerFactory); const testManager = factory('nosetest', rootWorkspaceUri, UNITTEST_TEST_FILES_PATH); @@ -128,7 +128,7 @@ suite('Unit Tests - nose - discovery with mocked process output', () => { }); test('Discover Tests (-m=test_)', async () => { - injectTestDiscoveryOutput('five.output'); + await injectTestDiscoveryOutput('five.output'); await updateSetting('unitTest.nosetestArgs', ['-m', 'test_'], rootWorkspaceUri, configTarget); const factory = ioc.serviceContainer.get(ITestManagerFactory); const testManager = factory('nosetest', rootWorkspaceUri, UNITTEST_TEST_FILES_PATH); diff --git a/src/test/unittests/nosetest.run.test.ts b/src/test/unittests/nosetest.run.test.ts index 1fc34a684c34..37d26716a31e 100644 --- a/src/test/unittests/nosetest.run.test.ts +++ b/src/test/unittests/nosetest.run.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; -import { IProcessService } from '../../client/common/process/types'; +import { IProcessServiceFactory } from '../../client/common/process/types'; import { CommandSource } from '../../client/unittests/common/constants'; import { ITestManagerFactory, TestsToRun } from '../../client/unittests/common/types'; import { rootWorkspaceUri, updateSetting } from '../common'; @@ -60,8 +60,8 @@ suite('Unit Tests - nose - run against actual python process', () => { ioc.registerMockProcessTypes(); } - function injectTestDiscoveryOutput(outputFileName: string) { - const procService = ioc.serviceContainer.get(IProcessService); + async function injectTestDiscoveryOutput(outputFileName: string) { + const procService = await ioc.serviceContainer.get(IProcessServiceFactory).create() as MockProcessService; procService.onExecObservable((file, args, options, callback) => { if (args.indexOf('--collect-only') >= 0) { callback({ @@ -72,8 +72,8 @@ suite('Unit Tests - nose - run against actual python process', () => { }); } - function injectTestRunOutput(outputFileName: string, failedOutput: boolean = false) { - const procService = ioc.serviceContainer.get(IProcessService); + async function injectTestRunOutput(outputFileName: string, failedOutput: boolean = false) { + const procService = await ioc.serviceContainer.get(IProcessServiceFactory).create() as MockProcessService; procService.onExecObservable((file, args, options, callback) => { if (failedOutput && args.indexOf('--failed') === -1) { return; @@ -90,8 +90,8 @@ suite('Unit Tests - nose - run against actual python process', () => { } test('Run Tests', async () => { - injectTestDiscoveryOutput('run.one.output'); - injectTestRunOutput('run.one.result'); + await injectTestDiscoveryOutput('run.one.output'); + await injectTestRunOutput('run.one.result'); await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); const factory = ioc.serviceContainer.get(ITestManagerFactory); const testManager = factory('nosetest', rootWorkspaceUri, UNITTEST_TEST_FILES_PATH); @@ -103,9 +103,9 @@ suite('Unit Tests - nose - run against actual python process', () => { }); test('Run Failed Tests', async () => { - injectTestDiscoveryOutput('run.two.output'); - injectTestRunOutput('run.two.result'); - injectTestRunOutput('run.two.again.result', true); + await injectTestDiscoveryOutput('run.two.output'); + await injectTestRunOutput('run.two.result'); + await injectTestRunOutput('run.two.again.result', true); await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); const factory = ioc.serviceContainer.get(ITestManagerFactory); const testManager = factory('nosetest', rootWorkspaceUri, UNITTEST_TEST_FILES_PATH); @@ -123,8 +123,8 @@ suite('Unit Tests - nose - run against actual python process', () => { }); test('Run Specific Test File', async () => { - injectTestDiscoveryOutput('run.three.output'); - injectTestRunOutput('run.three.result'); + await injectTestDiscoveryOutput('run.three.output'); + await injectTestRunOutput('run.three.result'); await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); const factory = ioc.serviceContainer.get(ITestManagerFactory); const testManager = factory('nosetest', rootWorkspaceUri, UNITTEST_TEST_FILES_PATH); @@ -141,8 +141,8 @@ suite('Unit Tests - nose - run against actual python process', () => { }); test('Run Specific Test Suite', async () => { - injectTestDiscoveryOutput('run.four.output'); - injectTestRunOutput('run.four.result'); + await injectTestDiscoveryOutput('run.four.output'); + await injectTestRunOutput('run.four.result'); await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); const factory = ioc.serviceContainer.get(ITestManagerFactory); const testManager = factory('nosetest', rootWorkspaceUri, UNITTEST_TEST_FILES_PATH); @@ -159,8 +159,8 @@ suite('Unit Tests - nose - run against actual python process', () => { }); test('Run Specific Test Function', async () => { - injectTestDiscoveryOutput('run.five.output'); - injectTestRunOutput('run.five.result'); + await injectTestDiscoveryOutput('run.five.output'); + await injectTestRunOutput('run.five.result'); await updateSetting('unitTest.nosetestArgs', ['-m', 'test'], rootWorkspaceUri, configTarget); const factory = ioc.serviceContainer.get(ITestManagerFactory); const testManager = factory('nosetest', rootWorkspaceUri, UNITTEST_TEST_FILES_PATH); diff --git a/src/test/unittests/pytest.discovery.test.ts b/src/test/unittests/pytest.discovery.test.ts index 9f073f8440bc..bdd6002ab81c 100644 --- a/src/test/unittests/pytest.discovery.test.ts +++ b/src/test/unittests/pytest.discovery.test.ts @@ -4,7 +4,7 @@ import * as assert from 'assert'; import * as path from 'path'; import * as vscode from 'vscode'; -import { IProcessService } from '../../client/common/process/types'; +import { IProcessServiceFactory } from '../../client/common/process/types'; import { CommandSource } from '../../client/unittests/common/constants'; import { ITestManagerFactory } from '../../client/unittests/common/types'; import { rootWorkspaceUri, updateSetting } from '../common'; @@ -44,8 +44,8 @@ suite('Unit Tests - pytest - discovery with mocked process output', () => { ioc.registerMockProcessTypes(); } - function injectTestDiscoveryOutput(output: string) { - const procService = ioc.serviceContainer.get(IProcessService); + async function injectTestDiscoveryOutput(output: string) { + const procService = await ioc.serviceContainer.get(IProcessServiceFactory).create() as MockProcessService; procService.onExecObservable((file, args, options, callback) => { if (args.indexOf('--collect-only') >= 0) { callback({ @@ -58,7 +58,7 @@ suite('Unit Tests - pytest - discovery with mocked process output', () => { test('Discover Tests (single test file)', async () => { // tslint:disable-next-line:no-multiline-string - injectTestDiscoveryOutput(` + await injectTestDiscoveryOutput(` ============================= test session starts ============================== platform darwin -- Python 3.6.2, pytest-3.3.0, py-1.5.2, pluggy-0.6.0 rootdir: /Users/donjayamanne/.vscode/extensions/pythonVSCode/src/test/pythonFiles/testFiles/single, inifile: @@ -89,7 +89,7 @@ suite('Unit Tests - pytest - discovery with mocked process output', () => { test('Discover Tests (pattern = test_)', async () => { // tslint:disable-next-line:no-multiline-string - injectTestDiscoveryOutput(` + await injectTestDiscoveryOutput(` ============================= test session starts ============================== platform darwin -- Python 3.6.2, pytest-3.3.0, py-1.5.2, pluggy-0.6.0 rootdir: /Users/donjayamanne/.vscode/extensions/pythonVSCode/src/test/pythonFiles/testFiles/standard, inifile: @@ -172,7 +172,7 @@ suite('Unit Tests - pytest - discovery with mocked process output', () => { test('Discover Tests (pattern = _test)', async () => { // tslint:disable-next-line:no-multiline-string - injectTestDiscoveryOutput(` + await injectTestDiscoveryOutput(` ============================= test session starts ============================== platform darwin -- Python 3.6.2, pytest-3.3.0, py-1.5.2, pluggy-0.6.0 rootdir: /Users/donjayamanne/.vscode/extensions/pythonVSCode/src/test/pythonFiles/testFiles/standard, inifile: @@ -198,7 +198,7 @@ suite('Unit Tests - pytest - discovery with mocked process output', () => { test('Discover Tests (with config)', async () => { // tslint:disable-next-line:no-multiline-string - injectTestDiscoveryOutput(` + await injectTestDiscoveryOutput(` ============================= test session starts ============================== platform darwin -- Python 3.6.2, pytest-3.3.0, py-1.5.2, pluggy-0.6.0 rootdir: /Users/donjayamanne/.vscode/extensions/pythonVSCode/src/test/pythonFiles/testFiles/unitestsWithConfigs, inifile: pytest.ini @@ -243,7 +243,7 @@ suite('Unit Tests - pytest - discovery with mocked process output', () => { test('Setting cwd should return tests', async () => { // tslint:disable-next-line:no-multiline-string - injectTestDiscoveryOutput(` + await injectTestDiscoveryOutput(` ============================= test session starts ============================== platform darwin -- Python 3.6.2, pytest-3.3.0, py-1.5.2, pluggy-0.6.0 rootdir: /Users/donjayamanne/.vscode/extensions/pythonVSCode/src/test/pythonFiles/testFiles/cwd/src, inifile: diff --git a/src/test/unittests/pytest.run.test.ts b/src/test/unittests/pytest.run.test.ts index b6695db63311..57cc69f7bf97 100644 --- a/src/test/unittests/pytest.run.test.ts +++ b/src/test/unittests/pytest.run.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; -import { IProcessService } from '../../client/common/process/types'; +import { IProcessServiceFactory } from '../../client/common/process/types'; import { CommandSource } from '../../client/unittests/common/constants'; import { ITestManagerFactory, TestFile, TestsToRun } from '../../client/unittests/common/types'; import { rootWorkspaceUri, updateSetting } from '../common'; @@ -43,8 +43,8 @@ suite('Unit Tests - pytest - run with mocked process output', () => { ioc.registerMockProcessTypes(); } - function injectTestDiscoveryOutput(outputFileName: string) { - const procService = ioc.serviceContainer.get(IProcessService); + async function injectTestDiscoveryOutput(outputFileName: string) { + const procService = await ioc.serviceContainer.get(IProcessServiceFactory).create() as MockProcessService; procService.onExecObservable((file, args, options, callback) => { if (args.indexOf('--collect-only') >= 0) { callback({ @@ -55,8 +55,8 @@ suite('Unit Tests - pytest - run with mocked process output', () => { }); } - function injectTestRunOutput(outputFileName: string, failedOutput: boolean = false) { - const procService = ioc.serviceContainer.get(IProcessService); + async function injectTestRunOutput(outputFileName: string, failedOutput: boolean = false) { + const procService = await ioc.serviceContainer.get(IProcessServiceFactory).create() as MockProcessService; procService.onExecObservable((file, args, options, callback) => { if (failedOutput && args.indexOf('--last-failed') === -1) { return; @@ -73,8 +73,8 @@ suite('Unit Tests - pytest - run with mocked process output', () => { } test('Run Tests', async () => { - injectTestDiscoveryOutput('one.output'); - injectTestRunOutput('one.xml'); + await injectTestDiscoveryOutput('one.output'); + await injectTestRunOutput('one.xml'); await updateSetting('unitTest.pyTestArgs', ['-k=test_'], rootWorkspaceUri, configTarget); const factory = ioc.serviceContainer.get(ITestManagerFactory); const testManager = factory('pytest', rootWorkspaceUri, UNITTEST_TEST_FILES_PATH); @@ -86,9 +86,9 @@ suite('Unit Tests - pytest - run with mocked process output', () => { }); test('Run Failed Tests', async () => { - injectTestDiscoveryOutput('two.output'); - injectTestRunOutput('two.xml'); - injectTestRunOutput('two.again.xml', true); + await injectTestDiscoveryOutput('two.output'); + await injectTestRunOutput('two.xml'); + await injectTestRunOutput('two.again.xml', true); await updateSetting('unitTest.pyTestArgs', ['-k=test_'], rootWorkspaceUri, configTarget); const factory = ioc.serviceContainer.get(ITestManagerFactory); const testManager = factory('pytest', rootWorkspaceUri, UNITTEST_TEST_FILES_PATH); @@ -106,8 +106,8 @@ suite('Unit Tests - pytest - run with mocked process output', () => { }); test('Run Specific Test File', async () => { - injectTestDiscoveryOutput('three.output'); - injectTestRunOutput('three.xml'); + await injectTestDiscoveryOutput('three.output'); + await injectTestRunOutput('three.xml'); await updateSetting('unitTest.pyTestArgs', ['-k=test_'], rootWorkspaceUri, configTarget); const factory = ioc.serviceContainer.get(ITestManagerFactory); const testManager = factory('pytest', rootWorkspaceUri, UNITTEST_TEST_FILES_PATH); @@ -130,8 +130,8 @@ suite('Unit Tests - pytest - run with mocked process output', () => { }); test('Run Specific Test Suite', async () => { - injectTestDiscoveryOutput('four.output'); - injectTestRunOutput('four.xml'); + await injectTestDiscoveryOutput('four.output'); + await injectTestRunOutput('four.xml'); await updateSetting('unitTest.pyTestArgs', ['-k=test_'], rootWorkspaceUri, configTarget); const factory = ioc.serviceContainer.get(ITestManagerFactory); const testManager = factory('pytest', rootWorkspaceUri, UNITTEST_TEST_FILES_PATH); @@ -145,8 +145,8 @@ suite('Unit Tests - pytest - run with mocked process output', () => { }); test('Run Specific Test Function', async () => { - injectTestDiscoveryOutput('five.output'); - injectTestRunOutput('five.xml'); + await injectTestDiscoveryOutput('five.output'); + await injectTestRunOutput('five.xml'); await updateSetting('unitTest.pyTestArgs', ['-k=test_'], rootWorkspaceUri, configTarget); const factory = ioc.serviceContainer.get(ITestManagerFactory); const testManager = factory('pytest', rootWorkspaceUri, UNITTEST_TEST_FILES_PATH); diff --git a/src/test/unittests/serviceRegistry.ts b/src/test/unittests/serviceRegistry.ts index 725dba422e81..f3a6d21d2315 100644 --- a/src/test/unittests/serviceRegistry.ts +++ b/src/test/unittests/serviceRegistry.ts @@ -13,6 +13,7 @@ import { TestFlatteningVisitor } from '../../client/unittests/common/testVisitor import { TestFolderGenerationVisitor } from '../../client/unittests/common/testVisitors/folderGenerationVisitor'; import { TestResultResetVisitor } from '../../client/unittests/common/testVisitors/resultResetVisitor'; import { ITestResultsService, ITestsHelper, ITestsParser, ITestVisitor, IUnitTestSocketServer, TestProvider } from '../../client/unittests/common/types'; +// tslint:disable-next-line:no-duplicate-imports import { ITestCollectionStorageService, ITestDiscoveryService, ITestManager, ITestManagerFactory, ITestManagerService, ITestManagerServiceFactory } from '../../client/unittests/common/types'; import { TestManager as NoseTestManager } from '../../client/unittests/nosetest/main'; import { TestDiscoveryService as NoseTestDiscoveryService } from '../../client/unittests/nosetest/services/discoveryService'; diff --git a/src/test/unittests/unittest.discovery.test.ts b/src/test/unittests/unittest.discovery.test.ts index b1f95c6ed978..e573726c2b4e 100644 --- a/src/test/unittests/unittest.discovery.test.ts +++ b/src/test/unittests/unittest.discovery.test.ts @@ -6,7 +6,7 @@ import * as fs from 'fs-extra'; import { EOL } from 'os'; import * as path from 'path'; import { ConfigurationTarget } from 'vscode'; -import { IProcessService } from '../../client/common/process/types'; +import { IProcessServiceFactory } from '../../client/common/process/types'; import { CommandSource } from '../../client/unittests/common/constants'; import { ITestManagerFactory } from '../../client/unittests/common/types'; import { rootWorkspaceUri, updateSetting } from '../common'; @@ -59,8 +59,8 @@ suite('Unit Tests - unittest - discovery with mocked process output', () => { ioc.registerMockProcessTypes(); } - function injectTestDiscoveryOutput(output: string) { - const procService = ioc.serviceContainer.get(IProcessService); + async function injectTestDiscoveryOutput(output: string) { + const procService = await ioc.serviceContainer.get(IProcessServiceFactory).create() as MockProcessService; procService.onExecObservable((file, args, options, callback) => { if (args.length > 1 && args[0] === '-c' && args[1].includes('import unittest') && args[1].includes('loader = unittest.TestLoader()')) { callback({ @@ -75,7 +75,7 @@ suite('Unit Tests - unittest - discovery with mocked process output', () => { test('Discover Tests (single test file)', async () => { await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=test_*.py'], rootWorkspaceUri, configTarget); // tslint:disable-next-line:no-multiline-string - injectTestDiscoveryOutput(`start + await injectTestDiscoveryOutput(`start test_one.Test_test1.test_A test_one.Test_test1.test_B test_one.Test_test1.test_c @@ -92,7 +92,7 @@ suite('Unit Tests - unittest - discovery with mocked process output', () => { test('Discover Tests', async () => { await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=test_*.py'], rootWorkspaceUri, configTarget); // tslint:disable-next-line:no-multiline-string - injectTestDiscoveryOutput(`start + await injectTestDiscoveryOutput(`start test_unittest_one.Test_test1.test_A test_unittest_one.Test_test1.test_B test_unittest_one.Test_test1.test_c @@ -116,7 +116,7 @@ suite('Unit Tests - unittest - discovery with mocked process output', () => { test('Discover Tests (pattern = *_test_*.py)', async () => { await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=*_test*.py'], rootWorkspaceUri, configTarget); // tslint:disable-next-line:no-multiline-string - injectTestDiscoveryOutput(`start + await injectTestDiscoveryOutput(`start unittest_three_test.Test_test3.test_A unittest_three_test.Test_test3.test_B `); @@ -132,7 +132,7 @@ suite('Unit Tests - unittest - discovery with mocked process output', () => { test('Setting cwd should return tests', async () => { await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=test_*.py'], rootWorkspaceUri, configTarget); // tslint:disable-next-line:no-multiline-string - injectTestDiscoveryOutput(`start + await injectTestDiscoveryOutput(`start test_cwd.Test_Current_Working_Directory.test_cwd `); const factory = ioc.serviceContainer.get(ITestManagerFactory); diff --git a/src/test/unittests/unittest.run.test.ts b/src/test/unittests/unittest.run.test.ts index f74ff2522acb..757ecf836199 100644 --- a/src/test/unittests/unittest.run.test.ts +++ b/src/test/unittests/unittest.run.test.ts @@ -6,7 +6,7 @@ import * as fs from 'fs-extra'; import { EOL } from 'os'; import * as path from 'path'; import { ConfigurationTarget } from 'vscode'; -import { IProcessService } from '../../client/common/process/types'; +import { IProcessServiceFactory } from '../../client/common/process/types'; import { CommandSource } from '../../client/unittests/common/constants'; import { ITestManagerFactory, IUnitTestSocketServer, TestsToRun } from '../../client/unittests/common/types'; import { rootWorkspaceUri, updateSetting } from '../common'; @@ -43,7 +43,7 @@ suite('Unit Tests - unittest - run with mocked process output', () => { } await initializeTest(); initializeDI(); - ignoreTestLauncher(); + await ignoreTestLauncher(); }); teardown(async () => { ioc.dispose(); @@ -70,8 +70,8 @@ suite('Unit Tests - unittest - run with mocked process output', () => { ioc.registerTestVisitors(); } - function ignoreTestLauncher() { - const procService = ioc.serviceContainer.get(IProcessService); + async function ignoreTestLauncher() { + const procService = await ioc.serviceContainer.get(IProcessServiceFactory).create() as MockProcessService; // When running the python test launcher, just return. procService.onExecObservable((file, args, options, callback) => { if (args.length > 1 && args[0].endsWith('visualstudio_py_testlauncher.py')) { @@ -79,8 +79,8 @@ suite('Unit Tests - unittest - run with mocked process output', () => { } }); } - function injectTestDiscoveryOutput(output: string) { - const procService = ioc.serviceContainer.get(IProcessService); + async function injectTestDiscoveryOutput(output: string) { + const procService = await ioc.serviceContainer.get(IProcessServiceFactory).create() as MockProcessService; procService.onExecObservable((file, args, options, callback) => { if (args.length > 1 && args[0] === '-c' && args[1].includes('import unittest') && args[1].includes('loader = unittest.TestLoader()')) { callback({ @@ -101,7 +101,7 @@ suite('Unit Tests - unittest - run with mocked process output', () => { test('Run Tests', async () => { await updateSetting('unitTest.unittestArgs', ['-v', '-s', './tests', '-p', 'test_unittest*.py'], rootWorkspaceUri, configTarget); // tslint:disable-next-line:no-multiline-string - injectTestDiscoveryOutput(`start + await injectTestDiscoveryOutput(`start test_unittest_one.Test_test1.test_A test_unittest_one.Test_test1.test_B test_unittest_one.Test_test1.test_c @@ -138,7 +138,7 @@ suite('Unit Tests - unittest - run with mocked process output', () => { test('Run Failed Tests', async () => { await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=test_unittest*.py'], rootWorkspaceUri, configTarget); // tslint:disable-next-line:no-multiline-string - injectTestDiscoveryOutput(`start + await injectTestDiscoveryOutput(`start test_unittest_one.Test_test1.test_A test_unittest_one.Test_test1.test_B test_unittest_one.Test_test1.test_c @@ -191,7 +191,7 @@ suite('Unit Tests - unittest - run with mocked process output', () => { await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=test_unittest*.py'], rootWorkspaceUri, configTarget); // tslint:disable-next-line:no-multiline-string - injectTestDiscoveryOutput(`start + await injectTestDiscoveryOutput(`start test_unittest_one.Test_test_one_1.test_1_1_1 test_unittest_one.Test_test_one_1.test_1_1_2 test_unittest_one.Test_test_one_1.test_1_1_3 @@ -228,7 +228,7 @@ suite('Unit Tests - unittest - run with mocked process output', () => { test('Run Specific Test Suite', async () => { await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=test_unittest*.py'], rootWorkspaceUri, configTarget); // tslint:disable-next-line:no-multiline-string - injectTestDiscoveryOutput(`start + await injectTestDiscoveryOutput(`start test_unittest_one.Test_test_one_1.test_1_1_1 test_unittest_one.Test_test_one_1.test_1_1_2 test_unittest_one.Test_test_one_1.test_1_1_3 @@ -265,7 +265,7 @@ suite('Unit Tests - unittest - run with mocked process output', () => { test('Run Specific Test Function', async () => { await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=test_unittest*.py'], rootWorkspaceUri, configTarget); // tslint:disable-next-line:no-multiline-string - injectTestDiscoveryOutput(`start + await injectTestDiscoveryOutput(`start test_unittest_one.Test_test1.test_A test_unittest_one.Test_test1.test_B test_unittest_one.Test_test1.test_c diff --git a/src/test/workspaceSymbols/multiroot.test.ts b/src/test/workspaceSymbols/multiroot.test.ts index ccbf64b3adcb..e6e0f45febe3 100644 --- a/src/test/workspaceSymbols/multiroot.test.ts +++ b/src/test/workspaceSymbols/multiroot.test.ts @@ -1,7 +1,7 @@ import * as assert from 'assert'; import * as path from 'path'; import { CancellationTokenSource, ConfigurationTarget, Uri } from 'vscode'; -import { IProcessService } from '../../client/common/process/types'; +import { IProcessServiceFactory } from '../../client/common/process/types'; import { Generator } from '../../client/workspaceSymbols/generator'; import { WorkspaceSymbolProvider } from '../../client/workspaceSymbols/provider'; import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST } from '../initialize'; @@ -13,7 +13,7 @@ const multirootPath = path.join(__dirname, '..', '..', '..', 'src', 'testMultiRo suite('Multiroot Workspace Symbols', () => { let ioc: UnitTestIocContainer; - let processService: IProcessService; + let processServiceFactory: IProcessServiceFactory; suiteSetup(function () { if (!IS_MULTI_ROOT_TEST) { // tslint:disable-next-line:no-invalid-this @@ -37,7 +37,7 @@ suite('Multiroot Workspace Symbols', () => { ioc.registerCommonTypes(); ioc.registerVariableTypes(); ioc.registerProcessTypes(); - processService = ioc.serviceContainer.get(IProcessService); + processServiceFactory = ioc.serviceContainer.get(IProcessServiceFactory); } test('symbols should be returned when enabeld and vice versa', async () => { @@ -46,7 +46,7 @@ suite('Multiroot Workspace Symbols', () => { await updateSetting('workspaceSymbols.enabled', false, childWorkspaceUri, ConfigurationTarget.WorkspaceFolder); - let generator = new Generator(childWorkspaceUri, outputChannel, processService); + let generator = new Generator(childWorkspaceUri, outputChannel, processServiceFactory); let provider = new WorkspaceSymbolProvider([generator], outputChannel); let symbols = await provider.provideWorkspaceSymbols('', new CancellationTokenSource().token); assert.equal(symbols.length, 0, 'Symbols returned even when workspace symbols are turned off'); @@ -54,7 +54,7 @@ suite('Multiroot Workspace Symbols', () => { await updateSetting('workspaceSymbols.enabled', true, childWorkspaceUri, ConfigurationTarget.WorkspaceFolder); - generator = new Generator(childWorkspaceUri, outputChannel, processService); + generator = new Generator(childWorkspaceUri, outputChannel, processServiceFactory); provider = new WorkspaceSymbolProvider([generator], outputChannel); symbols = await provider.provideWorkspaceSymbols('', new CancellationTokenSource().token); assert.notEqual(symbols.length, 0, 'Symbols should be returned when workspace symbols are turned on'); @@ -68,8 +68,8 @@ suite('Multiroot Workspace Symbols', () => { await updateSetting('workspaceSymbols.enabled', true, workspace2Uri, ConfigurationTarget.WorkspaceFolder); const generators = [ - new Generator(childWorkspaceUri, outputChannel, processService), - new Generator(workspace2Uri, outputChannel, processService)]; + new Generator(childWorkspaceUri, outputChannel, processServiceFactory), + new Generator(workspace2Uri, outputChannel, processServiceFactory)]; const provider = new WorkspaceSymbolProvider(generators, outputChannel); const symbols = await provider.provideWorkspaceSymbols('meth1Of', new CancellationTokenSource().token); diff --git a/src/test/workspaceSymbols/standard.test.ts b/src/test/workspaceSymbols/standard.test.ts index 5d264e7fd35a..46fdd9ba6102 100644 --- a/src/test/workspaceSymbols/standard.test.ts +++ b/src/test/workspaceSymbols/standard.test.ts @@ -2,7 +2,7 @@ import * as assert from 'assert'; import * as path from 'path'; import { CancellationTokenSource, ConfigurationTarget, Uri } from 'vscode'; import { PythonSettings } from '../../client/common/configSettings'; -import { IProcessService } from '../../client/common/process/types'; +import { IProcessServiceFactory } from '../../client/common/process/types'; import { Generator } from '../../client/workspaceSymbols/generator'; import { WorkspaceSymbolProvider } from '../../client/workspaceSymbols/provider'; import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST } from '../initialize'; @@ -15,7 +15,7 @@ const configUpdateTarget = IS_MULTI_ROOT_TEST ? ConfigurationTarget.WorkspaceFol suite('Workspace Symbols', () => { let ioc: UnitTestIocContainer; - let processService: IProcessService; + let processServiceFactory: IProcessServiceFactory; suiteSetup(initialize); suiteTeardown(closeActiveWindows); setup(async () => { @@ -32,7 +32,7 @@ suite('Workspace Symbols', () => { ioc.registerCommonTypes(); ioc.registerVariableTypes(); ioc.registerProcessTypes(); - processService = ioc.serviceContainer.get(IProcessService); + processServiceFactory = ioc.serviceContainer.get(IProcessServiceFactory); } test('symbols should be returned when enabeld and vice versa', async () => { @@ -42,9 +42,9 @@ suite('Workspace Symbols', () => { // The workspace will be in the output test folder // So lets modify the settings so it sees the source test folder let settings = PythonSettings.getInstance(workspaceUri); - settings.workspaceSymbols.tagFilePath = path.join(workspaceUri.fsPath, '.vscode', 'tags'); + settings.workspaceSymbols!.tagFilePath = path.join(workspaceUri.fsPath, '.vscode', 'tags'); - let generator = new Generator(workspaceUri, outputChannel, processService); + let generator = new Generator(workspaceUri, outputChannel, processServiceFactory); let provider = new WorkspaceSymbolProvider([generator], outputChannel); let symbols = await provider.provideWorkspaceSymbols('', new CancellationTokenSource().token); assert.equal(symbols.length, 0, 'Symbols returned even when workspace symbols are turned off'); @@ -55,9 +55,9 @@ suite('Workspace Symbols', () => { // The workspace will be in the output test folder // So lets modify the settings so it sees the source test folder settings = PythonSettings.getInstance(workspaceUri); - settings.workspaceSymbols.tagFilePath = path.join(workspaceUri.fsPath, '.vscode', 'tags'); + settings.workspaceSymbols!.tagFilePath = path.join(workspaceUri.fsPath, '.vscode', 'tags'); - generator = new Generator(workspaceUri, outputChannel, processService); + generator = new Generator(workspaceUri, outputChannel, processServiceFactory); provider = new WorkspaceSymbolProvider([generator], outputChannel); symbols = await provider.provideWorkspaceSymbols('', new CancellationTokenSource().token); assert.notEqual(symbols.length, 0, 'Symbols should be returned when workspace symbols are turned on'); @@ -70,9 +70,9 @@ suite('Workspace Symbols', () => { // The workspace will be in the output test folder // So lets modify the settings so it sees the source test folder const settings = PythonSettings.getInstance(workspaceUri); - settings.workspaceSymbols.tagFilePath = path.join(workspaceUri.fsPath, '.vscode', 'tags'); + settings.workspaceSymbols!.tagFilePath = path.join(workspaceUri.fsPath, '.vscode', 'tags'); - const generators = [new Generator(workspaceUri, outputChannel, processService)]; + const generators = [new Generator(workspaceUri, outputChannel, processServiceFactory)]; const provider = new WorkspaceSymbolProvider(generators, outputChannel); const symbols = await provider.provideWorkspaceSymbols('meth1Of', new CancellationTokenSource().token); From 8ccd663cb47198355e52430d6c288c80e5b44960 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 8 May 2018 16:08:07 -0700 Subject: [PATCH 241/433] Fix flask app variable in debug configuration and flask unit tests (#1641) * Fix flask app variable and tests --- news/2 Fixes/1634.md | 1 + news/3 Code Health/1640.md | 1 + package.json | 8 ++++---- src/test/debugger/web.framework.test.ts | 9 +++------ 4 files changed, 9 insertions(+), 10 deletions(-) create mode 100644 news/2 Fixes/1634.md create mode 100644 news/3 Code Health/1640.md diff --git a/news/2 Fixes/1634.md b/news/2 Fixes/1634.md new file mode 100644 index 000000000000..b9c8cbefe658 --- /dev/null +++ b/news/2 Fixes/1634.md @@ -0,0 +1 @@ +Modify the `FLASK_APP` environment variable in the flask debug configuration to include just the name of the application file. diff --git a/news/3 Code Health/1640.md b/news/3 Code Health/1640.md new file mode 100644 index 000000000000..329147d17bf9 --- /dev/null +++ b/news/3 Code Health/1640.md @@ -0,0 +1 @@ +Fix unit tests used to test flask template debugging on AppVeyor for the experimental debugger. diff --git a/package.json b/package.json index 3a8ffd2d11e1..aa2a85f44083 100644 --- a/package.json +++ b/package.json @@ -392,7 +392,7 @@ "module": "flask", "cwd": "^\"\\${workspaceFolder}\"", "env": { - "FLASK_APP": "^\"\\${workspaceFolder}/app.py\"" + "FLASK_APP": "app.py" }, "args": [ "run", @@ -685,7 +685,7 @@ "request": "launch", "module": "flask", "env": { - "FLASK_APP": "${workspaceFolder}/app.py" + "FLASK_APP": "app.py" }, "args": [ "run", @@ -808,7 +808,7 @@ "request": "launch", "module": "flask", "env": { - "FLASK_APP": "^\"\\${workspaceFolder}/app.py\"" + "FLASK_APP": "app.py" }, "args": [ "run", @@ -1107,7 +1107,7 @@ "request": "launch", "module": "flask", "env": { - "FLASK_APP": "${workspaceFolder}/app.py" + "FLASK_APP": "app.py" }, "args": [ "run", diff --git a/src/test/debugger/web.framework.test.ts b/src/test/debugger/web.framework.test.ts index f95dbedaca77..bb3be26a80b6 100644 --- a/src/test/debugger/web.framework.test.ts +++ b/src/test/debugger/web.framework.test.ts @@ -13,7 +13,7 @@ import { EXTENSION_ROOT_DIR } from '../../client/common/constants'; import { noop } from '../../client/common/core.utils'; import { DebugOptions, LaunchRequestArguments } from '../../client/debugger/Common/Contracts'; import { PYTHON_PATH, sleep } from '../common'; -import { IS_APPVEYOR, IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; +import { IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; import { DEBUGGER_TIMEOUT } from './common/constants'; import { continueDebugging, createDebugAdapter, ExpectedVariable, hitHttpBreakpoint, makeHttpRequest, validateVariablesInFrame } from './utils'; @@ -62,7 +62,7 @@ suite(`Django and Flask Debugging: ${debuggerType}`, () => { const port = await getFreePort({ host: 'localhost' }); const options = buildLaunchArgs(workspaceDirectory); - options.env!['FLASK_APP'] = path.join(workspaceDirectory, 'run.py'); + options.env!['FLASK_APP'] = 'run.py'; options.module = 'flask'; options.debugOptions = [DebugOptions.RedirectOutput, DebugOptions.Jinja]; options.args = [ @@ -129,10 +129,7 @@ suite(`Django and Flask Debugging: ${debuggerType}`, () => { expect(htmlResult).to.contain('Hello this_is_another_value_from_server'); } - test('Test Flask Route and Template debugging', async function () { - if (IS_APPVEYOR) { - return this.skip(); - } + test('Test Flask Route and Template debugging', async () => { const workspaceDirectory = path.join(EXTENSION_ROOT_DIR, 'src', 'testMultiRootWkspc', 'workspace5', 'flaskApp'); const { options, port } = await buildFlaskLaunchArgs(workspaceDirectory); From fe247a4c1d68d7fd696b5ef67b0ad250d911fe4c Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Tue, 8 May 2018 18:15:36 -0700 Subject: [PATCH 242/433] PTVS engine update + handling of the interpreter change (#1613) * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * #1096 The if statement is automatically formatted incorrectly * Merge fix * Add more tests * More tests * Typo * Test * Also better handle multiline arguments * Add a couple missing periods [skip ci] * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * Merge fix * #1257 On type formatting errors for args and kwargs * Handle f-strings * Stop importing from test code * #1308 Single line statements leading to an indentation on the next line * #726 editing python after inline if statement invalid indent * Undo change * Move constant * Harden LS startup error checks * #1364 Intellisense doesn't work after specific const string * Telemetry for the analysis enging * PR feedback * Fix typo * Test baseline update * Jedi 0.12 * Priority to goto_defition * News * Replace unzip * Linux flavors + test * Grammar check * Grammar test * Test baselines * Add news * Pin dependency [skip ci] * Specify markdown as preferable format * Improve function argument detection * Specify markdown * Pythia setting * Baseline updates * Baseline update * Improve startup * Handle missing interpreter better * Handle interpreter change * Delete old file * Fix LS startup time reporting * Remove Async suffix from IFileSystem --- package.json | 12 +++ src/client/activation/analysis.ts | 75 ++++++++++++++----- src/client/activation/downloader.ts | 64 +++++++++++----- src/client/activation/hashVerifier.ts | 4 +- .../activation/interpreterDataService.ts | 32 ++++---- src/client/common/configSettings.ts | 3 + src/client/common/platform/fileSystem.ts | 37 +++++++-- src/client/common/platform/types.ts | 14 ++-- src/client/common/process/pythonProcess.ts | 2 +- .../baseActivationProvider.ts | 6 +- src/client/common/types.ts | 1 + .../configurationProviderUtils.ts | 2 +- src/client/interpreter/display/index.ts | 2 +- .../services/baseVirtualEnvService.ts | 4 +- .../locators/services/condaEnvFileService.ts | 6 +- .../locators/services/condaEnvService.ts | 4 +- .../locators/services/condaService.ts | 6 +- .../locators/services/currentPathService.ts | 2 +- .../locators/services/pipEnvService.ts | 6 +- src/client/linters/lintingEngine.ts | 4 +- src/client/linters/pylint.ts | 14 ++-- .../terminals/codeExecution/djangoContext.ts | 4 +- src/test/common/process/execFactory.test.ts | 2 +- .../common/terminals/activation.bash.test.ts | 2 +- .../activation.commandPrompt.test.ts | 16 ++-- .../common/terminals/activation.conda.test.ts | 12 +-- .../configuration/interpreterSelector.test.ts | 2 +- .../debugger/configProvider/provider.test.ts | 2 +- src/test/definitions/hover.ptvs.test.ts | 28 +++++-- .../interpreters/condaEnvFileService.test.ts | 10 +-- src/test/interpreters/condaEnvService.test.ts | 16 ++-- src/test/interpreters/condaService.test.ts | 48 ++++++------ .../interpreters/currentPathService.test.ts | 8 +- src/test/interpreters/display.test.ts | 6 +- src/test/interpreters/pipEnvService.test.ts | 16 ++-- src/test/linters/lint.args.test.ts | 2 +- src/test/linters/lint.provider.test.ts | 2 +- src/test/linters/lintengine.test.ts | 2 +- src/test/linters/pylint.test.ts | 34 ++++----- src/test/signature/signature.ptvs.test.ts | 2 +- 40 files changed, 316 insertions(+), 198 deletions(-) diff --git a/package.json b/package.json index aa2a85f44083..b80589c88802 100644 --- a/package.json +++ b/package.json @@ -1232,6 +1232,18 @@ "description": "Whether to install Python modules globally when not using an environment.", "scope": "resource" }, + "python.pythiaEnabled": { + "type": "boolean", + "default": true, + "description": "Enables AI-driven additions to the completion list. Does not apply to Jedi.", + "scope": "resource" + }, + "python.jediEnabled": { + "type": "boolean", + "default": true, + "description": "Enables Jedi as IntelliSense engine instead of Microsoft Python Analysis Engine.", + "scope": "resource" + }, "python.jediMemoryLimit": { "type": "number", "default": 0, diff --git a/src/client/activation/analysis.ts b/src/client/activation/analysis.ts index 239dbef44edb..8418b2c20c77 100644 --- a/src/client/activation/analysis.ts +++ b/src/client/activation/analysis.ts @@ -13,6 +13,7 @@ import { IProcessServiceFactory } from '../common/process/types'; import { StopWatch } from '../common/stopWatch'; import { IConfigurationService, IOutputChannel, IPythonSettings } from '../common/types'; import { IEnvironmentVariablesProvider } from '../common/variables/types'; +import { IInterpreterService } from '../interpreter/contracts'; import { IServiceContainer } from '../ioc/types'; import { PYTHON_ANALYSIS_ENGINE_DOWNLOADED, @@ -35,11 +36,11 @@ class LanguageServerStartupErrorHandler implements ErrorHandler { constructor(private readonly deferred: Deferred) { } public error(error: Error, message: Message, count: number): ErrorAction { this.deferred.reject(error); - return ErrorAction.Shutdown; + return ErrorAction.Continue; } public closed(): CloseAction { this.deferred.reject(); - return CloseAction.DoNotRestart; + return CloseAction.Restart; } } @@ -50,7 +51,11 @@ export class AnalysisExtensionActivator implements IExtensionActivator { private readonly fs: IFileSystem; private readonly sw = new StopWatch(); private readonly platformData: PlatformData; + private readonly interpreterService: IInterpreterService; + private readonly disposables: Disposable[] = []; private languageClient: LanguageClient | undefined; + private context: ExtensionContext | undefined; + private interpreterHash: string = ''; constructor(private readonly services: IServiceContainer, pythonSettings: IPythonSettings) { this.configuration = this.services.get(IConfigurationService); @@ -58,13 +63,17 @@ export class AnalysisExtensionActivator implements IExtensionActivator { this.output = this.services.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); this.fs = this.services.get(IFileSystem); this.platformData = new PlatformData(services.get(IPlatformService), this.fs); + this.interpreterService = this.services.get(IInterpreterService); } public async activate(context: ExtensionContext): Promise { + this.sw.reset(); + this.context = context; const clientOptions = await this.getAnalysisOptions(context); if (!clientOptions) { return false; } + this.disposables.push(this.interpreterService.onDidChangeInterpreter(() => this.restartLanguageServer())); return this.startLanguageServer(context, clientOptions); } @@ -72,17 +81,36 @@ export class AnalysisExtensionActivator implements IExtensionActivator { if (this.languageClient) { await this.languageClient.stop(); } + for (const d of this.disposables) { + d.dispose(); + } + } + + private async restartLanguageServer(): Promise { + if (!this.context) { + return; + } + const ids = new InterpreterDataService(this.context, this.services); + const idata = await ids.getInterpreterData(); + if (!idata || idata.hash !== this.interpreterHash) { + this.interpreterHash = idata ? idata.hash : ''; + await this.deactivate(); + await this.activate(this.context); + } } private async startLanguageServer(context: ExtensionContext, clientOptions: LanguageClientOptions): Promise { // Determine if we are running MSIL/Universal via dotnet or self-contained app. const mscorlib = path.join(context.extensionPath, analysisEngineFolder, 'mscorlib.dll'); + const downloader = new AnalysisEngineDownloader(this.services, analysisEngineFolder); let downloadPackage = false; const reporter = getTelemetryReporter(); reporter.sendTelemetryEvent(PYTHON_ANALYSIS_ENGINE_ENABLED); - if (!await this.fs.fileExistsAsync(mscorlib)) { + await this.checkPythiaModel(context, downloader); + + if (!await this.fs.fileExists(mscorlib)) { // Depends on .NET Runtime or SDK this.languageClient = this.createSimpleLanguageClient(context, clientOptions); try { @@ -100,7 +128,7 @@ export class AnalysisExtensionActivator implements IExtensionActivator { } if (downloadPackage) { - const downloader = new AnalysisEngineDownloader(this.services, analysisEngineFolder); + this.appShell.showWarningMessage('.NET Runtime is not found, platform-specific Python Analysis Engine will be downloaded.'); await downloader.downloadAnalysisEngine(context); reporter.sendTelemetryEvent(PYTHON_ANALYSIS_ENGINE_DOWNLOADED); } @@ -128,7 +156,9 @@ export class AnalysisExtensionActivator implements IExtensionActivator { disposable = lc.start(); lc.onReady() .then(() => deferred.resolve()) - .catch(deferred.reject); + .catch((reason) => { + deferred.reject(reason); + }); await deferred.promise; this.output.appendLine(`Language server ready: ${this.sw.elapsedTime} ms`); @@ -172,20 +202,19 @@ export class AnalysisExtensionActivator implements IExtensionActivator { const interpreterData = await interpreterDataService.getInterpreterData(); if (!interpreterData) { const appShell = this.services.get(IApplicationShell); - appShell.showErrorMessage('Unable to determine path to Python interpreter.'); - return; + appShell.showWarningMessage('Unable to determine path to Python interpreter. IntelliSense will be limited.'); } - // tslint:disable-next-line:no-string-literal - properties['InterpreterPath'] = interpreterData.path; - // tslint:disable-next-line:no-string-literal - properties['Version'] = interpreterData.version; - // tslint:disable-next-line:no-string-literal - properties['PrefixPath'] = interpreterData.prefix; - // tslint:disable-next-line:no-string-literal - properties['DatabasePath'] = path.join(context.extensionPath, analysisEngineFolder); + if (interpreterData) { + // tslint:disable-next-line:no-string-literal + properties['InterpreterPath'] = interpreterData.path; + // tslint:disable-next-line:no-string-literal + properties['Version'] = interpreterData.version; + // tslint:disable-next-line:no-string-literal + properties['PrefixPath'] = interpreterData.prefix; + } - let searchPaths = interpreterData.searchPaths; + let searchPaths = interpreterData ? interpreterData.searchPaths : ''; const settings = this.configuration.getSettings(); if (settings.autoComplete) { const extraPaths = settings.autoComplete.extraPaths; @@ -194,12 +223,15 @@ export class AnalysisExtensionActivator implements IExtensionActivator { } } + // tslint:disable-next-line:no-string-literal + properties['DatabasePath'] = path.join(context.extensionPath, analysisEngineFolder); + const envProvider = this.services.get(IEnvironmentVariablesProvider); const pythonPath = (await envProvider.getEnvironmentVariables()).PYTHONPATH; + this.interpreterHash = interpreterData ? interpreterData.hash : ''; // tslint:disable-next-line:no-string-literal properties['SearchPaths'] = `${searchPaths};${pythonPath ? pythonPath : ''}`; - const selector: string[] = [PYTHON]; // Options to control the language client @@ -215,12 +247,14 @@ export class AnalysisExtensionActivator implements IExtensionActivator { properties }, displayOptions: { + preferredFormat: 1, // Markdown trimDocumentationLines: false, maxDocumentationLineLength: 0, trimDocumentationText: false, maxDocumentationTextLength: 0 }, asyncStartup: true, + pythiaEnabled: settings.pythiaEnabled, testEnvironment: isTestExecution() } }; @@ -231,4 +265,11 @@ export class AnalysisExtensionActivator implements IExtensionActivator { const result = await ps.exec('dotnet', ['--version']).catch(() => { return { stdout: '' }; }); return result.stdout.trim().startsWith('2.'); } + + private async checkPythiaModel(context: ExtensionContext, downloader: AnalysisEngineDownloader): Promise { + const settings = this.configuration.getSettings(); + if (settings.pythiaEnabled) { + await downloader.downloadPythiaModel(context); + } + } } diff --git a/src/client/activation/downloader.ts b/src/client/activation/downloader.ts index 98a2d2e1bfc2..52e9136a4951 100644 --- a/src/client/activation/downloader.ts +++ b/src/client/activation/downloader.ts @@ -1,13 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import * as fs from 'fs'; +import * as fileSystem from 'fs'; import * as path from 'path'; import * as request from 'request'; import * as requestProgress from 'request-progress'; import { ExtensionContext, OutputChannel, ProgressLocation, window } from 'vscode'; import { STANDARD_OUTPUT_CHANNEL } from '../common/constants'; -import { noop } from '../common/core.utils'; import { createDeferred, createTemporaryFile } from '../common/helpers'; import { IFileSystem, IPlatformService } from '../common/platform/types'; import { IOutputChannel } from '../common/types'; @@ -22,41 +21,71 @@ const downloadUriPrefix = 'https://pvsc.blob.core.windows.net/python-analysis'; const downloadBaseFileName = 'python-analysis-vscode'; const downloadVersion = '0.1.0'; const downloadFileExtension = '.nupkg'; +const pythiaModelName = 'model-sequence.json.gz'; export class AnalysisEngineDownloader { private readonly output: OutputChannel; private readonly platform: IPlatformService; private readonly platformData: PlatformData; + private readonly fs: IFileSystem; constructor(private readonly services: IServiceContainer, private engineFolder: string) { this.output = this.services.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); + this.fs = this.services.get(IFileSystem); this.platform = this.services.get(IPlatformService); - this.platformData = new PlatformData(this.platform, this.services.get(IFileSystem)); + this.platformData = new PlatformData(this.platform, this.fs); } public async downloadAnalysisEngine(context: ExtensionContext): Promise { - const localTempFilePath = await this.downloadFile(); + const platformString = await this.platformData.getPlatformName(); + const enginePackageFileName = `${downloadBaseFileName}-${platformString}.${downloadVersion}${downloadFileExtension}`; + + let localTempFilePath = ''; try { - await this.verifyDownload(localTempFilePath); + localTempFilePath = await this.downloadFile(downloadUriPrefix, enginePackageFileName, 'Downloading Python Analysis Engine... '); + await this.verifyDownload(localTempFilePath, platformString); await this.unpackArchive(context.extensionPath, localTempFilePath); } catch (err) { this.output.appendLine('failed.'); this.output.appendLine(err); throw new Error(err); } finally { - fs.unlink(localTempFilePath, noop); + if (localTempFilePath.length > 0) { + await this.fs.deleteFile(localTempFilePath); + } } } - private async downloadFile(): Promise { - const platformString = await this.platformData.getPlatformName(); - const remoteFileName = `${downloadBaseFileName}-${platformString}.${downloadVersion}${downloadFileExtension}`; - const uri = `${downloadUriPrefix}/${remoteFileName}`; + public async downloadPythiaModel(context: ExtensionContext): Promise { + const modelFolder = path.join(context.extensionPath, 'analysis', 'Pythia', 'model'); + const localPath = path.join(modelFolder, pythiaModelName); + if (await this.fs.fileExists(localPath)) { + return; + } + + let localTempFilePath = ''; + try { + localTempFilePath = await this.downloadFile(downloadUriPrefix, pythiaModelName, 'Downloading IntelliSense Model File... '); + await this.fs.createDirectory(modelFolder); + await this.fs.copyFile(localTempFilePath, localPath); + } catch (err) { + this.output.appendLine('failed.'); + this.output.appendLine(err); + throw new Error(err); + } finally { + if (localTempFilePath.length > 0) { + await this.fs.deleteFile(localTempFilePath); + } + } + } + + private async downloadFile(location: string, fileName: string, title: string): Promise { + const uri = `${location}/${fileName}`; this.output.append(`Downloading ${uri}... `); const tempFile = await createTemporaryFile(downloadFileExtension); const deferred = createDeferred(); - const fileStream = fs.createWriteStream(tempFile.filePath); + const fileStream = fileSystem.createWriteStream(tempFile.filePath); fileStream.on('finish', () => { fileStream.close(); }).on('error', (err) => { @@ -64,7 +93,6 @@ export class AnalysisEngineDownloader { deferred.reject(err); }); - const title = 'Downloading Python Analysis Engine... '; await window.withProgress({ location: ProgressLocation.Window, title @@ -94,11 +122,11 @@ export class AnalysisEngineDownloader { return tempFile.filePath; } - private async verifyDownload(filePath: string): Promise { + private async verifyDownload(filePath: string, platformString: string): Promise { this.output.appendLine(''); this.output.append('Verifying download... '); const verifier = new HashVerifier(); - if (!await verifier.verifyHash(filePath, await this.platformData.getExpectedHash())) { + if (!await verifier.verifyHash(filePath, platformString, await this.platformData.getExpectedHash())) { throw new Error('Hash of the downloaded file does not match.'); } this.output.append('valid.'); @@ -123,10 +151,10 @@ export class AnalysisEngineDownloader { let totalFiles = 0; let extractedFiles = 0; - zip.on('ready', () => { + zip.on('ready', async () => { totalFiles = zip.entriesCount; - if (!fs.existsSync(installFolder)) { - fs.mkdirSync(installFolder); + if (!await this.fs.directoryExists(installFolder)) { + await this.fs.createDirectory(installFolder); } zip.extract(null, installFolder, (err, count) => { if (err) { @@ -147,7 +175,7 @@ export class AnalysisEngineDownloader { // Set file to executable if (!this.platform.isWindows) { const executablePath = path.join(installFolder, this.platformData.getEngineExecutableName()); - fs.chmodSync(executablePath, '0764'); // -rwxrw-r-- + fileSystem.chmodSync(executablePath, '0764'); // -rwxrw-r-- } } } diff --git a/src/client/activation/hashVerifier.ts b/src/client/activation/hashVerifier.ts index 950f02d869f9..c62cb36484f7 100644 --- a/src/client/activation/hashVerifier.ts +++ b/src/client/activation/hashVerifier.ts @@ -6,7 +6,7 @@ import * as fs from 'fs'; import { createDeferred } from '../common/helpers'; export class HashVerifier { - public async verifyHash(filePath: string, expectedDigest: string): Promise { + public async verifyHash(filePath: string, platformString: string, expectedDigest: string): Promise { const readStream = fs.createReadStream(filePath); const deferred = createDeferred(); const hash = createHash('sha512'); @@ -23,6 +23,6 @@ export class HashVerifier { readStream.pipe(hash); await deferred.promise; const actual = hash.read(); - return expectedDigest === '' ? true : actual === expectedDigest; + return expectedDigest === platformString ? true : actual === expectedDigest; } } diff --git a/src/client/activation/interpreterDataService.ts b/src/client/activation/interpreterDataService.ts index 45cf9749e6cf..45c7e42006c4 100644 --- a/src/client/activation/interpreterDataService.ts +++ b/src/client/activation/interpreterDataService.ts @@ -64,6 +64,22 @@ export class InterpreterDataService { return interpreterData; } + public getInterpreterHash(interpreterPath: string): Promise { + const platform = this.serviceContainer.get(IPlatformService); + const pythonExecutable = path.join(path.dirname(interpreterPath), platform.isWindows ? 'python.exe' : 'python'); + // Hash mod time and creation time + const deferred = createDeferred(); + fs.lstat(pythonExecutable, (err, stats) => { + if (err) { + deferred.resolve(''); + } else { + const actual = createHash('sha512').update(`${stats.ctime}-${stats.mtime}`).digest('hex'); + deferred.resolve(actual); + } + }); + return deferred.promise; + } + private async getInterpreterDataFromPython(execService: IPythonExecutionService, interpreterPath: string): Promise { const result = await execService.exec(['-c', 'import sys; print(sys.version_info); print(sys.prefix)'], {}); // 2.7.14 (v2.7.14:84471935ed, Sep 16 2017, 20:19:30) <> @@ -87,22 +103,6 @@ export class InterpreterDataService { return new InterpreterData(DataVersion, interpreterPath, `${majorMatches[1]}.${minorMatches[1]}`, prefix, searchPaths, hash); } - private getInterpreterHash(interpreterPath: string): Promise { - const platform = this.serviceContainer.get(IPlatformService); - const pythonExecutable = path.join(path.dirname(interpreterPath), platform.isWindows ? 'python.exe' : 'python'); - // Hash mod time and creation time - const deferred = createDeferred(); - fs.lstat(pythonExecutable, (err, stats) => { - if (err) { - deferred.resolve(''); - } else { - const actual = createHash('sha512').update(`${stats.ctimeMs}-${stats.mtimeMs}`).digest('hex'); - deferred.resolve(actual); - } - }); - return deferred.promise; - } - private async getSearchPaths(execService: IPythonExecutionService): Promise { const result = await execService.exec(['-c', 'import sys; print(sys.path);'], {}); if (!result.stdout) { diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index 85a6ac61bc14..e659fe1f0a2f 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -25,6 +25,7 @@ export const IS_WINDOWS = /^win/.test(process.platform); // tslint:disable-next-line:completed-docs export class PythonSettings extends EventEmitter implements IPythonSettings { private static pythonSettings: Map = new Map(); + public pythiaEnabled = true; public jediEnabled = true; public jediPath = ''; public jediMemoryLimit = 1024; @@ -124,6 +125,8 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { this.jediPath = ''; } this.jediMemoryLimit = pythonSettings.get('jediMemoryLimit')!; + } else { + this.pythiaEnabled = systemVariables.resolveAny(pythonSettings.get('pythiaEnabled', true))!; } // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion diff --git a/src/client/common/platform/fileSystem.ts b/src/client/common/platform/fileSystem.ts index 463b1089b6fe..ecc9c564f446 100644 --- a/src/client/common/platform/fileSystem.ts +++ b/src/client/common/platform/fileSystem.ts @@ -5,6 +5,7 @@ import * as fs from 'fs-extra'; import { inject, injectable } from 'inversify'; import * as path from 'path'; +import { createDeferred } from '../helpers'; import { IFileSystem, IPlatformService } from './types'; @injectable() @@ -15,7 +16,7 @@ export class FileSystem implements IFileSystem { return path.sep; } - public objectExistsAsync(filePath: string, statCheck: (s: fs.Stats) => boolean): Promise { + public objectExists(filePath: string, statCheck: (s: fs.Stats) => boolean): Promise { return new Promise(resolve => { fs.stat(filePath, (error, stats) => { if (error) { @@ -26,8 +27,8 @@ export class FileSystem implements IFileSystem { }); } - public fileExistsAsync(filePath: string): Promise { - return this.objectExistsAsync(filePath, (stats) => stats.isFile()); + public fileExists(filePath: string): Promise { + return this.objectExists(filePath, (stats) => stats.isFile()); } public fileExistsSync(filePath: string): boolean { return fs.existsSync(filePath); @@ -42,15 +43,15 @@ export class FileSystem implements IFileSystem { return fs.readFile(filePath).then(buffer => buffer.toString()); } - public directoryExistsAsync(filePath: string): Promise { - return this.objectExistsAsync(filePath, (stats) => stats.isDirectory()); + public directoryExists(filePath: string): Promise { + return this.objectExists(filePath, (stats) => stats.isDirectory()); } - public createDirectoryAsync(directoryPath: string): Promise { + public createDirectory(directoryPath: string): Promise { return fs.mkdirp(directoryPath); } - public getSubDirectoriesAsync(rootDir: string): Promise { + public getSubDirectories(rootDir: string): Promise { return new Promise(resolve => { fs.readdir(rootDir, (error, files) => { if (error) { @@ -89,11 +90,31 @@ export class FileSystem implements IFileSystem { return fs.appendFileSync(filename, data, optionsOrEncoding); } - public getRealPathAsync(filePath: string): Promise { + public getRealPath(filePath: string): Promise { return new Promise(resolve => { fs.realpath(filePath, (err, realPath) => { resolve(err ? filePath : realPath); }); }); } + + public copyFile(src: string, dest: string): Promise { + const deferred = createDeferred(); + const rs = fs.createReadStream(src).on('error', (err) => { + deferred.reject(err); + }); + const ws = fs.createWriteStream(dest).on('error', (err) => { + deferred.reject(err); + }).on('close', () => { + deferred.resolve(); + }); + rs.pipe(ws); + return deferred.promise; + } + + public deleteFile(filename: string): Promise { + const deferred = createDeferred(); + fs.unlink(filename, err => err ? deferred.reject(err) : deferred.resolve()); + return deferred.promise; + } } diff --git a/src/client/common/platform/types.ts b/src/client/common/platform/types.ts index 6c40a7a6a068..ce3836eb59a2 100644 --- a/src/client/common/platform/types.ts +++ b/src/client/common/platform/types.ts @@ -31,17 +31,19 @@ export interface IPlatformService { export const IFileSystem = Symbol('IFileSystem'); export interface IFileSystem { directorySeparatorChar: string; - objectExistsAsync(path: string, statCheck: (s: fs.Stats) => boolean): Promise; - fileExistsAsync(path: string): Promise; + objectExists(path: string, statCheck: (s: fs.Stats) => boolean): Promise; + fileExists(path: string): Promise; fileExistsSync(path: string): boolean; - directoryExistsAsync(path: string): Promise; - createDirectoryAsync(path: string): Promise; - getSubDirectoriesAsync(rootDir: string): Promise; + directoryExists(path: string): Promise; + createDirectory(path: string): Promise; + getSubDirectories(rootDir: string): Promise; arePathsSame(path1: string, path2: string): boolean; readFile(filePath: string): Promise; appendFileSync(filename: string, data: {}, encoding: string): void; appendFileSync(filename: string, data: {}, options?: { encoding?: string; mode?: number; flag?: string }): void; // tslint:disable-next-line:unified-signatures appendFileSync(filename: string, data: {}, options?: { encoding?: string; mode?: string; flag?: string }): void; - getRealPathAsync(path: string): Promise; + getRealPath(path: string): Promise; + copyFile(src: string, dest: string): Promise; + deleteFile(filename: string): Promise; } diff --git a/src/client/common/process/pythonProcess.ts b/src/client/common/process/pythonProcess.ts index 419b5d88dcba..8a7894dc4f54 100644 --- a/src/client/common/process/pythonProcess.ts +++ b/src/client/common/process/pythonProcess.ts @@ -28,7 +28,7 @@ export class PythonExecutionService implements IPythonExecutionService { public async getExecutablePath(): Promise { // If we've passed the python file, then return the file. // This is because on mac if using the interpreter /usr/bin/python2.7 we can get a different value for the path - if (await this.fileSystem.fileExistsAsync(this.pythonPath)) { + if (await this.fileSystem.fileExists(this.pythonPath)) { return this.pythonPath; } return this.procService.exec(this.pythonPath, ['-c', 'import sys;print(sys.executable)'], { throwOnStdErr: true }) diff --git a/src/client/common/terminal/environmentActivationProviders/baseActivationProvider.ts b/src/client/common/terminal/environmentActivationProviders/baseActivationProvider.ts index 5ba858d624f4..ee45d5948c93 100644 --- a/src/client/common/terminal/environmentActivationProviders/baseActivationProvider.ts +++ b/src/client/common/terminal/environmentActivationProviders/baseActivationProvider.ts @@ -4,12 +4,10 @@ import { injectable } from 'inversify'; import * as path from 'path'; import { Uri } from 'vscode'; -import { PythonInterpreter } from '../../../interpreter/contracts'; import { IServiceContainer } from '../../../ioc/types'; import { IFileSystem } from '../../platform/types'; import { IConfigurationService } from '../../types'; -import { TerminalShellType } from '../types'; -import { ITerminalActivationCommandProvider } from '../types'; +import { ITerminalActivationCommandProvider, TerminalShellType } from '../types'; @injectable() export abstract class BaseActivationCommandProvider implements ITerminalActivationCommandProvider { @@ -25,7 +23,7 @@ export abstract class BaseActivationCommandProvider implements ITerminalActivati for (const scriptFileName of scriptFileNames) { // Generate scripts are found in the same directory as the interpreter. const scriptFile = path.join(path.dirname(pythonPath), scriptFileName); - const found = await fs.fileExistsAsync(scriptFile); + const found = await fs.fileExists(scriptFile); if (found) { return scriptFile; } diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 027437f169cc..11ff9b508d6b 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -99,6 +99,7 @@ export interface IPythonSettings { readonly pythonPath: string; readonly venvPath: string; readonly venvFolders: string[]; + readonly pythiaEnabled: boolean; readonly jediEnabled: boolean; readonly jediPath: string; readonly jediMemoryLimit: number; diff --git a/src/client/debugger/configProviders/configurationProviderUtils.ts b/src/client/debugger/configProviders/configurationProviderUtils.ts index e5ce625517f5..6bc13f0bf53f 100644 --- a/src/client/debugger/configProviders/configurationProviderUtils.ts +++ b/src/client/debugger/configProviders/configurationProviderUtils.ts @@ -27,7 +27,7 @@ export class ConfigurationProviderUtils implements IConfigurationProviderUtils { const executionService = await this.executionFactory.create(resource); const output = await executionService.exec(['-c', 'import pyramid;print(pyramid.__file__)'], { throwOnStdErr: true }); const pserveFilePath = path.join(path.dirname(output.stdout.trim()), 'scripts', PSERVE_SCRIPT_FILE_NAME); - return await this.fs.fileExistsAsync(pserveFilePath) ? pserveFilePath : undefined; + return await this.fs.fileExists(pserveFilePath) ? pserveFilePath : undefined; } catch (ex) { const message = 'Unable to locate \'pserve.py\' required for debugging of Pyramid applications.'; console.error(message, ex); diff --git a/src/client/interpreter/display/index.ts b/src/client/interpreter/display/index.ts index 1c19a75f2e70..e419691b31d2 100644 --- a/src/client/interpreter/display/index.ts +++ b/src/client/interpreter/display/index.ts @@ -65,7 +65,7 @@ export class InterpreterDisplay implements IInterpreterDisplay { } else { const defaultDisplayName = `${path.basename(pythonPath)} [Environment]`; await Promise.all([ - this.fileSystem.fileExistsAsync(pythonPath), + this.fileSystem.fileExists(pythonPath), this.versionProvider.getVersion(pythonPath, defaultDisplayName), this.getVirtualEnvironmentName(pythonPath).catch(() => '') ]) diff --git a/src/client/interpreter/locators/services/baseVirtualEnvService.ts b/src/client/interpreter/locators/services/baseVirtualEnvService.ts index 06b57b939436..4499a3a38acf 100644 --- a/src/client/interpreter/locators/services/baseVirtualEnvService.ts +++ b/src/client/interpreter/locators/services/baseVirtualEnvService.ts @@ -34,7 +34,7 @@ export class BaseVirtualEnvService extends CacheableLocatorService { .then(listOfInterpreters => _.flatten(listOfInterpreters)); } private async lookForInterpretersInVenvs(pathToCheck: string) { - return this.fileSystem.getSubDirectoriesAsync(pathToCheck) + return this.fileSystem.getSubDirectories(pathToCheck) .then(subDirs => Promise.all(this.getProspectiveDirectoriesForLookup(subDirs))) .then(dirs => dirs.filter(dir => dir.length > 0)) .then(dirs => Promise.all(dirs.map(lookForInterpretersInDirectory))) @@ -50,7 +50,7 @@ export class BaseVirtualEnvService extends CacheableLocatorService { const platform = this.serviceContainer.get(IPlatformService); const dirToLookFor = platform.virtualEnvBinName; return subDirs.map(subDir => - this.fileSystem.getSubDirectoriesAsync(subDir) + this.fileSystem.getSubDirectories(subDir) .then(dirs => { const scriptOrBinDirs = dirs.filter(dir => { const folderName = path.basename(dir); diff --git a/src/client/interpreter/locators/services/condaEnvFileService.ts b/src/client/interpreter/locators/services/condaEnvFileService.ts index 49cbc2eec935..2f9e681abb3f 100644 --- a/src/client/interpreter/locators/services/condaEnvFileService.ts +++ b/src/client/interpreter/locators/services/condaEnvFileService.ts @@ -15,7 +15,7 @@ import { AnacondaCompanyName, AnacondaCompanyNames, AnacondaDisplayName } from ' @injectable() export class CondaEnvFileService extends CacheableLocatorService { - constructor( @inject(IInterpreterVersionService) private versionService: IInterpreterVersionService, + constructor(@inject(IInterpreterVersionService) private versionService: IInterpreterVersionService, @inject(ICondaService) private condaService: ICondaService, @inject(IFileSystem) private fileSystem: IFileSystem, @inject(IServiceContainer) serviceContainer: IServiceContainer, @@ -31,7 +31,7 @@ export class CondaEnvFileService extends CacheableLocatorService { if (!this.condaService.condaEnvironmentsFile) { return []; } - return this.fileSystem.fileExistsAsync(this.condaService.condaEnvironmentsFile!) + return this.fileSystem.fileExists(this.condaService.condaEnvironmentsFile!) .then(exists => exists ? this.getEnvironmentsFromFile(this.condaService.condaEnvironmentsFile!) : Promise.resolve([])); } private async getEnvironmentsFromFile(envFile: string) { @@ -66,7 +66,7 @@ export class CondaEnvFileService extends CacheableLocatorService { } private async getInterpreterDetails(environmentPath: string): Promise { const interpreter = this.condaService.getInterpreterPath(environmentPath); - if (!interpreter || !await this.fileSystem.fileExistsAsync(interpreter)) { + if (!interpreter || !await this.fileSystem.fileExists(interpreter)) { return; } diff --git a/src/client/interpreter/locators/services/condaEnvService.ts b/src/client/interpreter/locators/services/condaEnvService.ts index 7e2d5c616fcc..781f3b286f75 100644 --- a/src/client/interpreter/locators/services/condaEnvService.ts +++ b/src/client/interpreter/locators/services/condaEnvService.ts @@ -14,7 +14,7 @@ import { CondaHelper } from './condaHelper'; @injectable() export class CondaEnvService extends CacheableLocatorService { private readonly condaHelper = new CondaHelper(); - constructor( @inject(ICondaService) private condaService: ICondaService, + constructor(@inject(ICondaService) private condaService: ICondaService, @inject(IInterpreterVersionService) private versionService: IInterpreterVersionService, @inject(ILogger) private logger: ILogger, @inject(IServiceContainer) serviceContainer: IServiceContainer, @@ -37,7 +37,7 @@ export class CondaEnvService extends CacheableLocatorService { .map(async envPath => { const pythonPath = this.condaService.getInterpreterPath(envPath); - const existsPromise = pythonPath ? this.fileSystem.fileExistsAsync(pythonPath) : Promise.resolve(false); + const existsPromise = pythonPath ? this.fileSystem.fileExists(pythonPath) : Promise.resolve(false); const versionPromise = this.versionService.getVersion(pythonPath, ''); const [exists, version] = await Promise.all([existsPromise, versionPromise]); diff --git a/src/client/interpreter/locators/services/condaService.ts b/src/client/interpreter/locators/services/condaService.ts index 2e990c30fcbe..969b5a1ca0ff 100644 --- a/src/client/interpreter/locators/services/condaService.ts +++ b/src/client/interpreter/locators/services/condaService.ts @@ -91,7 +91,7 @@ export class CondaService implements ICondaService { const dir = path.dirname(interpreterPath); const isWindows = this.serviceContainer.get(IPlatformService).isWindows; const condaMetaDirectory = isWindows ? path.join(dir, 'conda-meta') : path.join(dir, '..', 'conda-meta'); - return fs.directoryExistsAsync(condaMetaDirectory); + return fs.directoryExists(condaMetaDirectory); } public async getCondaEnvironment(interpreterPath: string): Promise<{ name: string; path: string } | undefined> { const isCondaEnv = await this.isCondaEnvironment(interpreterPath); @@ -175,7 +175,7 @@ export class CondaService implements ICondaService { return condaInterpreter ? path.join(path.dirname(condaInterpreter.path), 'conda.exe') : 'conda'; }) .then(async condaPath => { - return this.fileSystem.fileExistsAsync(condaPath).then(exists => exists ? condaPath : 'conda'); + return this.fileSystem.fileExists(condaPath).then(exists => exists ? condaPath : 'conda'); }); } return this.getCondaFileFromKnownLocations(); @@ -183,7 +183,7 @@ export class CondaService implements ICondaService { private async getCondaFileFromKnownLocations(): Promise { const condaFiles = await Promise.all(KNOWN_CONDA_LOCATIONS .map(untildify) - .map(async (condaPath: string) => this.fileSystem.fileExistsAsync(condaPath).then(exists => exists ? condaPath : ''))); + .map(async (condaPath: string) => this.fileSystem.fileExists(condaPath).then(exists => exists ? condaPath : ''))); const validCondaFiles = condaFiles.filter(condaPath => condaPath.length > 0); return validCondaFiles.length === 0 ? 'conda' : validCondaFiles[0]; diff --git a/src/client/interpreter/locators/services/currentPathService.ts b/src/client/interpreter/locators/services/currentPathService.ts index cf2f5dc8d321..5d8c6fc1b636 100644 --- a/src/client/interpreter/locators/services/currentPathService.ts +++ b/src/client/interpreter/locators/services/currentPathService.ts @@ -58,7 +58,7 @@ export class CurrentPathService extends CacheableLocatorService { return processService.exec(pythonPath, ['-c', 'import sys;print(sys.executable)'], {}) .then(output => output.stdout.trim()) .then(async value => { - if (value.length > 0 && await this.fs.fileExistsAsync(value)) { + if (value.length > 0 && await this.fs.fileExists(value)) { return value; } return defaultValue; diff --git a/src/client/interpreter/locators/services/pipEnvService.ts b/src/client/interpreter/locators/services/pipEnvService.ts index 10d4d451a44a..46985a70c81d 100644 --- a/src/client/interpreter/locators/services/pipEnvService.ts +++ b/src/client/interpreter/locators/services/pipEnvService.ts @@ -78,15 +78,15 @@ export class PipEnvService extends CacheableLocatorService { return; } const venvFolder = await this.invokePipenv('--venv', cwd); - return venvFolder && await this.fs.directoryExistsAsync(venvFolder) ? venvFolder : undefined; + return venvFolder && await this.fs.directoryExists(venvFolder) ? venvFolder : undefined; } private async checkIfPipFileExists(cwd: string): Promise { const currentProcess = this.serviceContainer.get(ICurrentProcess); const pipFileName = currentProcess.env[pipEnvFileNameVariable]; - if (typeof pipFileName === 'string' && await this.fs.fileExistsAsync(path.join(cwd, pipFileName))) { + if (typeof pipFileName === 'string' && await this.fs.fileExists(path.join(cwd, pipFileName))) { return true; } - if (await this.fs.fileExistsAsync(path.join(cwd, 'Pipfile'))) { + if (await this.fs.fileExists(path.join(cwd, 'Pipfile'))) { return true; } return false; diff --git a/src/client/linters/lintingEngine.ts b/src/client/linters/lintingEngine.ts index d5323c697c43..93b424780278 100644 --- a/src/client/linters/lintingEngine.ts +++ b/src/client/linters/lintingEngine.ts @@ -66,7 +66,7 @@ export class LintingEngine implements ILintingEngine { public async lintOpenPythonFiles(): Promise { this.diagnosticCollection.clear(); - const promises = this.documents.textDocuments.map(async document => await this.lintDocument(document, 'auto')); + const promises = this.documents.textDocuments.map(async document => this.lintDocument(document, 'auto')); await Promise.all(promises); return this.diagnosticCollection; } @@ -197,6 +197,6 @@ export class LintingEngine implements ILintingEngine { if (document.uri.scheme !== 'file' || !document.uri.fsPath) { return false; } - return await this.fileSystem.fileExistsAsync(document.uri.fsPath); + return this.fileSystem.fileExists(document.uri.fsPath); } } diff --git a/src/client/linters/pylint.ts b/src/client/linters/pylint.ts index 1e830283127d..ef015a260c46 100644 --- a/src/client/linters/pylint.ts +++ b/src/client/linters/pylint.ts @@ -99,17 +99,17 @@ export class Pylint extends BaseLinter { return true; } - if (await fs.fileExistsAsync(path.join(folder, pylintrc)) || await fs.fileExistsAsync(path.join(folder, dotPylintrc))) { + if (await fs.fileExists(path.join(folder, pylintrc)) || await fs.fileExists(path.join(folder, dotPylintrc))) { return true; } let current = folder; let above = path.dirname(folder); do { - if (!await fs.fileExistsAsync(path.join(current, '__init__.py'))) { + if (!await fs.fileExists(path.join(current, '__init__.py'))) { break; } - if (await fs.fileExistsAsync(path.join(current, pylintrc)) || await fs.fileExistsAsync(path.join(current, dotPylintrc))) { + if (await fs.fileExists(path.join(current, pylintrc)) || await fs.fileExists(path.join(current, dotPylintrc))) { return true; } current = above; @@ -117,15 +117,15 @@ export class Pylint extends BaseLinter { } while (!fs.arePathsSame(current, above)); const home = os.homedir(); - if (await fs.fileExistsAsync(path.join(home, dotPylintrc))) { + if (await fs.fileExists(path.join(home, dotPylintrc))) { return true; } - if (await fs.fileExistsAsync(path.join(home, '.config', pylintrc))) { + if (await fs.fileExists(path.join(home, '.config', pylintrc))) { return true; } if (!platformService.isWindows) { - if (await fs.fileExistsAsync(path.join('/etc', pylintrc))) { + if (await fs.fileExists(path.join('/etc', pylintrc))) { return true; } } @@ -138,7 +138,7 @@ export class Pylint extends BaseLinter { let current = folder; let above = path.dirname(current); do { - if (await fs.fileExistsAsync(path.join(current, pylintrc)) || await fs.fileExistsAsync(path.join(current, dotPylintrc))) { + if (await fs.fileExists(path.join(current, pylintrc)) || await fs.fileExists(path.join(current, dotPylintrc))) { return true; } current = above; diff --git a/src/client/terminals/codeExecution/djangoContext.ts b/src/client/terminals/codeExecution/djangoContext.ts index 00a5dd8e437b..d6f8755d447b 100644 --- a/src/client/terminals/codeExecution/djangoContext.ts +++ b/src/client/terminals/codeExecution/djangoContext.ts @@ -54,7 +54,7 @@ export class DjangoContextInitializer implements Disposable { private async ensureContextStateIsSet(): Promise { const activeWorkspace = this.getActiveWorkspace(); if (!activeWorkspace) { - return await this.isDjangoProject.set(false); + return this.isDjangoProject.set(false); } if (this.lastCheckedWorkspace === activeWorkspace) { return; @@ -62,7 +62,7 @@ export class DjangoContextInitializer implements Disposable { if (this.workspaceContextKeyValues.has(activeWorkspace)) { await this.isDjangoProject.set(this.workspaceContextKeyValues.get(activeWorkspace)!); } else { - const exists = await this.fileSystem.fileExistsAsync(path.join(activeWorkspace, 'manage.py')); + const exists = await this.fileSystem.fileExists(path.join(activeWorkspace, 'manage.py')); await this.isDjangoProject.set(exists); this.workspaceContextKeyValues.set(activeWorkspace, exists); this.lastCheckedWorkspace = activeWorkspace; diff --git a/src/test/common/process/execFactory.test.ts b/src/test/common/process/execFactory.test.ts index 2802a38cb749..21eea8523d86 100644 --- a/src/test/common/process/execFactory.test.ts +++ b/src/test/common/process/execFactory.test.ts @@ -25,7 +25,7 @@ suite('PythonExecutableService', () => { procService = TypeMoq.Mock.ofType(); configService = TypeMoq.Mock.ofType(); const fileSystem = TypeMoq.Mock.ofType(); - fileSystem.setup(f => f.fileExistsAsync(TypeMoq.It.isAny())).returns(() => Promise.resolve(false)); + fileSystem.setup(f => f.fileExists(TypeMoq.It.isAny())).returns(() => Promise.resolve(false)); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IFileSystem))).returns(() => fileSystem.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IEnvironmentVariablesProvider))).returns(() => envVarsProvider.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IProcessServiceFactory))).returns(() => procServiceFactory.object); diff --git a/src/test/common/terminals/activation.bash.test.ts b/src/test/common/terminals/activation.bash.test.ts index c321528140ea..88f677f5dc21 100644 --- a/src/test/common/terminals/activation.bash.test.ts +++ b/src/test/common/terminals/activation.bash.test.ts @@ -75,7 +75,7 @@ suite('Terminal Environment Activation (bash)', () => { } const pathToScriptFile = path.join(path.dirname(pythonPath), scriptFileName); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(pathToScriptFile))).returns(() => Promise.resolve(true)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pathToScriptFile))).returns(() => Promise.resolve(true)); const command = await bash.getActivationCommands(undefined, shellType.value); if (isScriptFileSupported) { diff --git a/src/test/common/terminals/activation.commandPrompt.test.ts b/src/test/common/terminals/activation.commandPrompt.test.ts index ce1942bc3b88..8f6f743b6063 100644 --- a/src/test/common/terminals/activation.commandPrompt.test.ts +++ b/src/test/common/terminals/activation.commandPrompt.test.ts @@ -85,7 +85,7 @@ suite('Terminal Environment Activation (cmd/powershell)', () => { const bash = new CommandPromptAndPowerShell(serviceContainer.object); const pathToScriptFile = path.join(path.dirname(pythonPath), 'activate.bat'); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(pathToScriptFile))).returns(() => Promise.resolve(true)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pathToScriptFile))).returns(() => Promise.resolve(true)); const commands = await bash.getActivationCommands(resource, TerminalShellType.commandPrompt); // Ensure the script file is of the following form: @@ -101,7 +101,7 @@ suite('Terminal Environment Activation (cmd/powershell)', () => { platform.setup(p => p.isWindows).returns(() => true); const pathToScriptFile = path.join(path.dirname(pythonPath), 'activate.bat'); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(pathToScriptFile))).returns(() => Promise.resolve(true)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pathToScriptFile))).returns(() => Promise.resolve(true)); const command = await batch.getActivationCommands(resource, TerminalShellType.powershell); // Executing batch files from powershell requires going back to cmd, then into powershell @@ -116,7 +116,7 @@ suite('Terminal Environment Activation (cmd/powershell)', () => { platform.setup(p => p.isWindows).returns(() => true); const pathToScriptFile = path.join(path.dirname(pythonPath), 'activate.bat'); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(pathToScriptFile))).returns(() => Promise.resolve(true)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pathToScriptFile))).returns(() => Promise.resolve(true)); const command = await bash.getActivationCommands(resource, TerminalShellType.powershellCore); // Executing batch files from powershell requires going back to cmd, then into powershell @@ -131,7 +131,7 @@ suite('Terminal Environment Activation (cmd/powershell)', () => { platform.setup(p => p.isWindows).returns(() => false); const pathToScriptFile = path.join(path.dirname(pythonPath), 'activate.bat'); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(pathToScriptFile))).returns(() => Promise.resolve(true)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pathToScriptFile))).returns(() => Promise.resolve(true)); const command = await bash.getActivationCommands(resource, TerminalShellType.powershell); expect(command).to.be.equal(undefined, 'Invalid command'); @@ -142,7 +142,7 @@ suite('Terminal Environment Activation (cmd/powershell)', () => { platform.setup(p => p.isWindows).returns(() => false); const pathToScriptFile = path.join(path.dirname(pythonPath), 'activate.bat'); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(pathToScriptFile))).returns(() => Promise.resolve(true)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pathToScriptFile))).returns(() => Promise.resolve(true)); const command = await bash.getActivationCommands(resource, TerminalShellType.powershellCore); expect(command).to.be.equal(undefined, 'Invalid command'); @@ -172,7 +172,7 @@ suite('Terminal Environment Activation (cmd/powershell)', () => { platform.setup(p => p.isWindows).returns(() => true); const pathToScriptFile = path.join(path.dirname(pythonPath), 'activate.ps1'); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(pathToScriptFile))).returns(() => Promise.resolve(true)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pathToScriptFile))).returns(() => Promise.resolve(true)); const command = await bash.getActivationCommands(resource, TerminalShellType.commandPrompt); expect(command).to.be.deep.equal([], 'Invalid command (running powershell files are not supported on command prompt)'); @@ -183,7 +183,7 @@ suite('Terminal Environment Activation (cmd/powershell)', () => { platform.setup(p => p.isWindows).returns(() => true); const pathToScriptFile = path.join(path.dirname(pythonPath), 'activate.ps1'); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(pathToScriptFile))).returns(() => Promise.resolve(true)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pathToScriptFile))).returns(() => Promise.resolve(true)); const command = await bash.getActivationCommands(resource, TerminalShellType.powershell); expect(command).to.be.deep.equal([`& ${pathToScriptFile.fileToCommandArgument()}`.trim()], 'Invalid command'); @@ -194,7 +194,7 @@ suite('Terminal Environment Activation (cmd/powershell)', () => { platform.setup(p => p.isWindows).returns(() => true); const pathToScriptFile = path.join(path.dirname(pythonPath), 'activate.ps1'); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(pathToScriptFile))).returns(() => Promise.resolve(true)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pathToScriptFile))).returns(() => Promise.resolve(true)); const command = await bash.getActivationCommands(resource, TerminalShellType.powershellCore); expect(command).to.be.deep.equal([`& ${pathToScriptFile.fileToCommandArgument()}`.trim()], 'Invalid command'); diff --git a/src/test/common/terminals/activation.conda.test.ts b/src/test/common/terminals/activation.conda.test.ts index b4498ce8edeb..87d10d7c2b38 100644 --- a/src/test/common/terminals/activation.conda.test.ts +++ b/src/test/common/terminals/activation.conda.test.ts @@ -152,19 +152,19 @@ suite('Terminal Environment Activation conda', () => { test('If environment is a conda environment, ensure conda activation command is sent (windows)', async () => { const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'enva', 'python.exe'); - fileSystem.setup(f => f.directoryExistsAsync(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))).returns(() => Promise.resolve(true)); + fileSystem.setup(f => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))).returns(() => Promise.resolve(true)); await expectCondaActivationCommand(true, false, false, pythonPath); }); test('If environment is a conda environment, ensure conda activation command is sent (linux)', async () => { const pythonPath = path.join('users', 'xyz', '.conda', 'envs', 'enva', 'bin', 'python'); - fileSystem.setup(f => f.directoryExistsAsync(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta')))).returns(() => Promise.resolve(true)); + fileSystem.setup(f => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta')))).returns(() => Promise.resolve(true)); await expectCondaActivationCommand(false, false, true, pythonPath); }); test('If environment is a conda environment, ensure conda activation command is sent (osx)', async () => { const pythonPath = path.join('users', 'xyz', '.conda', 'envs', 'enva', 'bin', 'python'); - fileSystem.setup(f => f.directoryExistsAsync(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta')))).returns(() => Promise.resolve(true)); + fileSystem.setup(f => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta')))).returns(() => Promise.resolve(true)); await expectCondaActivationCommand(false, true, false, pythonPath); }); @@ -205,21 +205,21 @@ suite('Terminal Environment Activation conda', () => { test('If environment is a conda environment and environment detection fails, ensure activatino of script is sent (windows)', async () => { const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'enva', 'python.exe'); const condaEnvDir = path.join('c', 'users', 'xyz', '.conda', 'envs'); - fileSystem.setup(f => f.directoryExistsAsync(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))).returns(() => Promise.resolve(true)); + fileSystem.setup(f => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))).returns(() => Promise.resolve(true)); await expectActivationCommandIfCondaDetectionFails(true, false, false, pythonPath, condaEnvDir); }); test('If environment is a conda environment and environment detection fails, ensure activatino of script is sent (osx)', async () => { const pythonPath = path.join('users', 'xyz', '.conda', 'envs', 'enva', 'python'); const condaEnvDir = path.join('users', 'xyz', '.conda', 'envs'); - fileSystem.setup(f => f.directoryExistsAsync(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta')))).returns(() => Promise.resolve(true)); + fileSystem.setup(f => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta')))).returns(() => Promise.resolve(true)); await expectActivationCommandIfCondaDetectionFails(false, true, false, pythonPath, condaEnvDir); }); test('If environment is a conda environment and environment detection fails, ensure activatino of script is sent (linux)', async () => { const pythonPath = path.join('users', 'xyz', '.conda', 'envs', 'enva', 'python'); const condaEnvDir = path.join('users', 'xyz', '.conda', 'envs'); - fileSystem.setup(f => f.directoryExistsAsync(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta')))).returns(() => Promise.resolve(true)); + fileSystem.setup(f => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta')))).returns(() => Promise.resolve(true)); await expectActivationCommandIfCondaDetectionFails(false, false, true, pythonPath, condaEnvDir); }); diff --git a/src/test/configuration/interpreterSelector.test.ts b/src/test/configuration/interpreterSelector.test.ts index 5dbb7f7bd70d..98ea1fd27cc9 100644 --- a/src/test/configuration/interpreterSelector.test.ts +++ b/src/test/configuration/interpreterSelector.test.ts @@ -54,7 +54,7 @@ suite('Interpreters - selector', () => { .setup(x => x.arePathsSame(TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString())) .returns((a: string, b: string) => a === b); fileSystem - .setup(x => x.getRealPathAsync(TypeMoq.It.isAnyString())) + .setup(x => x.getRealPath(TypeMoq.It.isAnyString())) .returns((a: string) => new Promise(resolve => resolve(a))); serviceManager.addSingletonInstance(IFileSystem, fileSystem.object); diff --git a/src/test/debugger/configProvider/provider.test.ts b/src/test/debugger/configProvider/provider.test.ts index 94ca215e76db..fbc51cd3c762 100644 --- a/src/test/debugger/configProvider/provider.test.ts +++ b/src/test/debugger/configProvider/provider.test.ts @@ -345,7 +345,7 @@ import { IServiceContainer } from '../../../client/ioc/types'; pythonExecutionService.setup(e => e.exec(TypeMoq.It.isValue(args), TypeMoq.It.isAny())) .returns(() => execOutput) .verifiable(TypeMoq.Times.exactly(addPyramidDebugOption ? 1 : 0)); - fileSystem.setup(f => f.fileExistsAsync(TypeMoq.It.isValue(pserveFilePath))) + fileSystem.setup(f => f.fileExists(TypeMoq.It.isValue(pserveFilePath))) .returns(() => Promise.resolve(pyramidExists)) .verifiable(TypeMoq.Times.exactly(pyramidExists && addPyramidDebugOption ? 1 : 0)); appShell.setup(a => a.showErrorMessage(TypeMoq.It.isAny())) diff --git a/src/test/definitions/hover.ptvs.test.ts b/src/test/definitions/hover.ptvs.test.ts index 4f0f014c7bff..089245836090 100644 --- a/src/test/definitions/hover.ptvs.test.ts +++ b/src/test/definitions/hover.ptvs.test.ts @@ -52,7 +52,9 @@ suite('Hover Definition (Analysis Engine)', () => { const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); const expected = [ 'obj.method1:', + '```python', 'method method1 of one.Class1 objects', + '```', 'This is method1' ]; verifySignatureLines(actual, expected); @@ -67,7 +69,9 @@ suite('Hover Definition (Analysis Engine)', () => { const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); const expected = [ 'two.ct().fun:', + '```python', 'method fun of two.ct objects', + '```', 'This is fun' ]; verifySignatureLines(actual, expected); @@ -81,12 +85,13 @@ suite('Hover Definition (Analysis Engine)', () => { const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); const expected = [ - 'Foo.bar:', + '```python', 'four.Foo.bar() -> bool', + 'declared in Foo', + '```', '说明 - keep this line, it works', 'delete following line, it works', - '如果存在需要等待审批或正在执行的任务,将不刷新页面', - 'declared in Foo' + '如果存在需要等待审批或正在执行的任务,将不刷新页面' ]; verifySignatureLines(actual, expected); }); @@ -99,8 +104,9 @@ suite('Hover Definition (Analysis Engine)', () => { const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); const expected = [ - 'four.showMessage:', + '```python', 'four.showMessage()', + '```', 'Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи.', 'Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.' ]; @@ -131,8 +137,9 @@ suite('Hover Definition (Analysis Engine)', () => { const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); const expected = [ - 'misc.Random:', + '```python', 'class misc.Random(_random.Random)', + '```', 'Random number generator base class used by bound module functions.', 'Used to instantiate instances of Random to get generators that don\'t', 'share state.', @@ -154,7 +161,9 @@ suite('Hover Definition (Analysis Engine)', () => { const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); const expected = [ 'rnd2.randint:', + '```python', 'method randint of misc.Random objects -> int', + '```', 'Return random integer in range [a, b], including both end points.' ]; verifySignatureLines(actual, expected); @@ -168,8 +177,9 @@ suite('Hover Definition (Analysis Engine)', () => { const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); const expected = [ - 'math.acos:', - 'built-in function acos(x)', + '```python', + 'acos(x)', + '```', 'acos(x)', 'Return the arc cosine (measured in radians) of x.' ]; @@ -184,8 +194,9 @@ suite('Hover Definition (Analysis Engine)', () => { const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); const expected = [ - 'misc.Thread:', + '```python', 'class misc.Thread(_Verbose)', + '```', 'A class that represents a thread of control.', 'This class can be safely subclassed in a limited fashion.' ]; @@ -222,6 +233,7 @@ suite('Hover Definition (Analysis Engine)', () => { function verifySignatureLines(actual: string[], expected: string[]) { assert.equal(actual.length, expected.length, 'incorrect number of lines'); for (let i = 0; i < actual.length; i += 1) { + actual[i] = actual[i].replace(new RegExp(' ', 'g'), ' '); assert.equal(actual[i].trim(), expected[i], `signature line ${i + 1} is incorrect`); } } diff --git a/src/test/interpreters/condaEnvFileService.test.ts b/src/test/interpreters/condaEnvFileService.test.ts index 2207be82732a..92783ddf3bc9 100644 --- a/src/test/interpreters/condaEnvFileService.test.ts +++ b/src/test/interpreters/condaEnvFileService.test.ts @@ -44,7 +44,7 @@ suite('Interpreters from Conda Environments Text File', () => { }); test('Must return an empty list for an empty file', async () => { condaService.setup(c => c.condaEnvironmentsFile).returns(() => environmentsFilePath); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(environmentsFilePath))).returns(() => Promise.resolve(true)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(environmentsFilePath))).returns(() => Promise.resolve(true)); fileSystem.setup(fs => fs.readFile(TypeMoq.It.isValue(environmentsFilePath))).returns(() => Promise.resolve('')); interpreterVersion.setup(i => i.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('Mock Name')); const interpreters = await condaFileProvider.getInterpreters(); @@ -73,11 +73,11 @@ suite('Interpreters from Conda Environments Text File', () => { }); return Promise.resolve(condaEnvironments); }); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(environmentsFilePath))).returns(() => Promise.resolve(true)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(environmentsFilePath))).returns(() => Promise.resolve(true)); fileSystem.setup(fs => fs.arePathsSame(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((p1: string, p2: string) => isWindows ? p1 === p2 : p1.toUpperCase() === p2.toUpperCase()); validPaths.forEach(validPath => { const pythonPath = isWindows ? path.join(validPath, 'python.exe') : path.join(validPath, 'bin', 'python'); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); }); fileSystem.setup(fs => fs.readFile(TypeMoq.It.isValue(environmentsFilePath))).returns(() => Promise.resolve(interpreterPaths.join(EOL))); @@ -107,8 +107,8 @@ suite('Interpreters from Conda Environments Text File', () => { const pythonPath = path.join(interpreterPaths[0], 'pythonPath'); condaService.setup(c => c.condaEnvironmentsFile).returns(() => environmentsFilePath); condaService.setup(c => c.getInterpreterPath(TypeMoq.It.isAny())).returns(() => pythonPath); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(environmentsFilePath))).returns(() => Promise.resolve(true)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(environmentsFilePath))).returns(() => Promise.resolve(true)); fileSystem.setup(fs => fs.readFile(TypeMoq.It.isValue(environmentsFilePath))).returns(() => Promise.resolve(interpreterPaths.join(EOL))); for (const companyName of AnacondaCompanyNames) { diff --git a/src/test/interpreters/condaEnvService.test.ts b/src/test/interpreters/condaEnvService.test.ts index a3c7b93c7acc..82da233fb9e9 100644 --- a/src/test/interpreters/condaEnvService.test.ts +++ b/src/test/interpreters/condaEnvService.test.ts @@ -63,7 +63,7 @@ suite('Interpreters from Conda Environments', () => { }); info.envs.forEach(validPath => { const pythonPath = isWindows ? path.join(validPath, 'python.exe') : path.join(validPath, 'bin', 'python'); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); }); interpreterVersion.setup(i => i.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((_p, defaultValue) => Promise.resolve(defaultValue)); @@ -100,7 +100,7 @@ suite('Interpreters from Conda Environments', () => { }); info.envs.forEach(validPath => { const pythonPath = isWindows ? path.join(validPath, 'python.exe') : path.join(validPath, 'bin', 'python'); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); }); interpreterVersion.setup(i => i.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((_p, defaultValue) => Promise.resolve(defaultValue)); condaService.setup(c => c.getCondaFile()).returns(() => Promise.resolve('conda')); @@ -145,7 +145,7 @@ suite('Interpreters from Conda Environments', () => { }); info.envs.forEach(validPath => { const pythonPath = isWindows ? path.join(validPath, 'python.exe') : path.join(validPath, 'bin', 'python'); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); }); interpreterVersion.setup(i => i.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((_p, defaultValue) => Promise.resolve(defaultValue)); @@ -183,7 +183,7 @@ suite('Interpreters from Conda Environments', () => { }); info.envs.forEach(validPath => { const pythonPath = isWindows ? path.join(validPath, 'python.exe') : path.join(validPath, 'bin', 'python'); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); }); interpreterVersion.setup(i => i.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((_p, defaultValue) => Promise.resolve(defaultValue)); fileSystem.setup(fs => fs.arePathsSame(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((p1: string, p2: string) => isWindows ? p1 === p2 : p1.toUpperCase() === p2.toUpperCase()); @@ -213,7 +213,7 @@ suite('Interpreters from Conda Environments', () => { }); info.envs.forEach(validPath => { const pythonPath = isWindows ? path.join(validPath, 'python.exe') : path.join(validPath, 'bin', 'python'); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); }); interpreterVersion.setup(i => i.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((_p, defaultValue) => Promise.resolve(defaultValue)); @@ -243,7 +243,7 @@ suite('Interpreters from Conda Environments', () => { }); info.envs.forEach(validPath => { const pythonPath = isWindows ? path.join(validPath, 'python.exe') : path.join(validPath, 'bin', 'python'); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); }); interpreterVersion.setup(i => i.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((_p, defaultValue) => Promise.resolve(defaultValue)); condaService.setup(c => c.getCondaFile()).returns(() => Promise.resolve('conda')); @@ -279,7 +279,7 @@ suite('Interpreters from Conda Environments', () => { return isWindows ? path.join(environmentPath, 'python.exe') : path.join(environmentPath, 'bin', 'python'); }); const pythonPath = isWindows ? path.join(info.default_prefix, 'python.exe') : path.join(info.default_prefix, 'bin', 'python'); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); interpreterVersion.setup(i => i.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((_p, defaultValue) => Promise.resolve(defaultValue)); const interpreters = await condaProvider.parseCondaInfo(info); @@ -314,7 +314,7 @@ suite('Interpreters from Conda Environments', () => { return isWindows ? path.join(environmentPath, 'python.exe') : path.join(environmentPath, 'bin', 'python'); }); const pythonPath = isWindows ? path.join(envPath, 'python.exe') : path.join(envPath, 'bin', 'python'); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); }); const interpreters = await condaProvider.parseCondaInfo(info); diff --git a/src/test/interpreters/condaService.test.ts b/src/test/interpreters/condaService.test.ts index 21059a94e537..cbcdcf5642da 100644 --- a/src/test/interpreters/condaService.test.ts +++ b/src/test/interpreters/condaService.test.ts @@ -58,19 +58,19 @@ suite('Interpreters Conda Service', () => { test('Correctly identifies a python path as a conda environment (windows)', async () => { const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'enva', 'python.exe'); - fileSystem.setup(f => f.directoryExistsAsync(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))).returns(() => Promise.resolve(true)); + fileSystem.setup(f => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))).returns(() => Promise.resolve(true)); await identifyPythonPathAsCondaEnvironment(true, false, false, pythonPath); }); test('Correctly identifies a python path as a conda environment (linux)', async () => { const pythonPath = path.join('users', 'xyz', '.conda', 'envs', 'enva', 'bin', 'python'); - fileSystem.setup(f => f.directoryExistsAsync(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta')))).returns(() => Promise.resolve(true)); + fileSystem.setup(f => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta')))).returns(() => Promise.resolve(true)); await identifyPythonPathAsCondaEnvironment(false, false, true, pythonPath); }); test('Correctly identifies a python path as a conda environment (osx)', async () => { const pythonPath = path.join('users', 'xyz', '.conda', 'envs', 'enva', 'bin', 'python'); - fileSystem.setup(f => f.directoryExistsAsync(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta')))).returns(() => Promise.resolve(true)); + fileSystem.setup(f => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta')))).returns(() => Promise.resolve(true)); await identifyPythonPathAsCondaEnvironment(false, true, false, pythonPath); }); @@ -79,8 +79,8 @@ suite('Interpreters Conda Service', () => { platformService.setup(p => p.isWindows).returns(() => isWindows); platformService.setup(p => p.isMac).returns(() => isOsx); - fileSystem.setup(f => f.directoryExistsAsync(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))).returns(() => Promise.resolve(false)); - fileSystem.setup(f => f.directoryExistsAsync(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta')))).returns(() => Promise.resolve(false)); + fileSystem.setup(f => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))).returns(() => Promise.resolve(false)); + fileSystem.setup(f => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta')))).returns(() => Promise.resolve(false)); const isCondaEnv = await condaService.isCondaEnvironment(pythonPath); expect(isCondaEnv).to.be.equal(false, 'Path incorrectly identified as a conda path'); @@ -127,7 +127,7 @@ suite('Interpreters Conda Service', () => { const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'one', 'python.exe'); const condaEnvDir = path.join('c', 'users', 'xyz', '.conda', 'envs'); - fileSystem.setup(f => f.directoryExistsAsync(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))).returns(() => Promise.resolve(true)); + fileSystem.setup(f => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))).returns(() => Promise.resolve(true)); await checkCondaNameAndPathForCondaEnvironments(true, false, false, pythonPath, condaEnvDir, { name: 'One', path: path.dirname(pythonPath) }); }); @@ -135,7 +135,7 @@ suite('Interpreters Conda Service', () => { const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'eight 8', 'python.exe'); const condaEnvDir = path.join('c', 'users', 'xyz', '.conda', 'envs'); - fileSystem.setup(f => f.directoryExistsAsync(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))).returns(() => Promise.resolve(true)); + fileSystem.setup(f => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))).returns(() => Promise.resolve(true)); await checkCondaNameAndPathForCondaEnvironments(true, false, false, pythonPath, condaEnvDir, { name: 'Eight', path: path.dirname(pythonPath) }); }); @@ -143,7 +143,7 @@ suite('Interpreters Conda Service', () => { const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'one', 'bin', 'python'); const condaEnvDir = path.join('c', 'users', 'xyz', '.conda', 'envs'); - fileSystem.setup(f => f.directoryExistsAsync(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta')))).returns(() => Promise.resolve(true)); + fileSystem.setup(f => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta')))).returns(() => Promise.resolve(true)); await checkCondaNameAndPathForCondaEnvironments(false, true, false, pythonPath, condaEnvDir, { name: 'One', path: path.join(path.dirname(pythonPath), '..') }); }); @@ -151,7 +151,7 @@ suite('Interpreters Conda Service', () => { const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'Eight 8', 'bin', 'python'); const condaEnvDir = path.join('c', 'users', 'xyz', '.conda', 'envs'); - fileSystem.setup(f => f.directoryExistsAsync(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta')))).returns(() => Promise.resolve(true)); + fileSystem.setup(f => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta')))).returns(() => Promise.resolve(true)); await checkCondaNameAndPathForCondaEnvironments(false, true, false, pythonPath, condaEnvDir, { name: 'Eight', path: path.join(path.dirname(pythonPath), '..') }); }); @@ -159,7 +159,7 @@ suite('Interpreters Conda Service', () => { const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'one', 'bin', 'python'); const condaEnvDir = path.join('c', 'users', 'xyz', '.conda', 'envs'); - fileSystem.setup(f => f.directoryExistsAsync(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta')))).returns(() => Promise.resolve(true)); + fileSystem.setup(f => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta')))).returns(() => Promise.resolve(true)); await checkCondaNameAndPathForCondaEnvironments(false, false, true, pythonPath, condaEnvDir, { name: 'One', path: path.join(path.dirname(pythonPath), '..') }); }); @@ -167,7 +167,7 @@ suite('Interpreters Conda Service', () => { const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'Eight 8', 'bin', 'python'); const condaEnvDir = path.join('c', 'users', 'xyz', '.conda', 'envs'); - fileSystem.setup(f => f.directoryExistsAsync(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta')))).returns(() => Promise.resolve(true)); + fileSystem.setup(f => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), '..', 'conda-meta')))).returns(() => Promise.resolve(true)); await checkCondaNameAndPathForCondaEnvironments(false, false, true, pythonPath, condaEnvDir, { name: 'Eight', path: path.join(path.dirname(pythonPath), '..') }); }); @@ -187,7 +187,7 @@ suite('Interpreters Conda Service', () => { platformService.setup(p => p.isWindows).returns(() => true); platformService.setup(p => p.isMac).returns(() => false); - fileSystem.setup(f => f.directoryExistsAsync(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))).returns(() => Promise.resolve(true)); + fileSystem.setup(f => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))).returns(() => Promise.resolve(true)); const stateFactory = TypeMoq.Mock.ofType(); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPersistentStateFactory))).returns(() => stateFactory.object); const state = new MockState({ data: condaEnvironments }); @@ -228,7 +228,7 @@ suite('Interpreters Conda Service', () => { platformService.setup(p => p.isWindows).returns(() => true); platformService.setup(p => p.isMac).returns(() => false); - fileSystem.setup(f => f.directoryExistsAsync(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))).returns(() => Promise.resolve(true)); + fileSystem.setup(f => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))).returns(() => Promise.resolve(true)); const stateFactory = TypeMoq.Mock.ofType(); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPersistentStateFactory))).returns(() => stateFactory.object); const state = new MockState({ data: condaEnvironments }); @@ -265,7 +265,7 @@ suite('Interpreters Conda Service', () => { platformService.setup(p => p.isWindows).returns(() => true); processService.setup(p => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())).returns(() => Promise.reject(new Error('Not Found'))); registryInterpreterLocatorService.setup(r => r.getInterpreters(TypeMoq.It.isAny())).returns(() => Promise.resolve(registryInterpreters)); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isAny())).returns((file: string) => Promise.resolve(file === expectedCodnaPath)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isAny())).returns((file: string) => Promise.resolve(file === expectedCodnaPath)); const condaExe = await condaService.getCondaFile(); assert.equal(condaExe, expectedCodnaPath, 'Failed to identify conda.exe'); @@ -287,7 +287,7 @@ suite('Interpreters Conda Service', () => { platformService.setup(p => p.isWindows).returns(() => true); processService.setup(p => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())).returns(() => Promise.reject(new Error('Not Found'))); registryInterpreterLocatorService.setup(r => r.getInterpreters(TypeMoq.It.isAny())).returns(() => Promise.resolve(registryInterpreters)); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isAny())).returns((file: string) => Promise.resolve(file === expectedCodnaPath)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isAny())).returns((file: string) => Promise.resolve(file === expectedCodnaPath)); const condaExe = await condaService.getCondaFile(); assert.equal(condaExe, expectedCodnaPath, 'Failed to identify conda.exe'); @@ -307,7 +307,7 @@ suite('Interpreters Conda Service', () => { platformService.setup(p => p.isWindows).returns(() => true); processService.setup(p => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())).returns(() => Promise.reject(new Error('Not Found'))); registryInterpreterLocatorService.setup(r => r.getInterpreters(TypeMoq.It.isAny())).returns(() => Promise.resolve(registryInterpreters)); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isAny())).returns((file: string) => Promise.resolve(false)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isAny())).returns((file: string) => Promise.resolve(false)); const condaExe = await condaService.getCondaFile(); assert.equal(condaExe, 'conda', 'Failed to identify conda.exe'); @@ -344,7 +344,7 @@ suite('Interpreters Conda Service', () => { const expectedCondaLocation = untildify(knownLocation); platformService.setup(p => p.isWindows).returns(() => false); processService.setup(p => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())).returns(() => Promise.reject(new Error('Not Found'))); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isAny())).returns((file: string) => Promise.resolve(file === expectedCondaLocation)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isAny())).returns((file: string) => Promise.resolve(file === expectedCondaLocation)); const condaExe = await condaService.getCondaFile(); assert.equal(condaExe, expectedCondaLocation, 'Failed to identify'); @@ -354,7 +354,7 @@ suite('Interpreters Conda Service', () => { test('Must return \'conda\' if conda could not be found in known locations', async () => { platformService.setup(p => p.isWindows).returns(() => false); processService.setup(p => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())).returns(() => Promise.reject(new Error('Not Found'))); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isAny())).returns((file: string) => Promise.resolve(false)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isAny())).returns((file: string) => Promise.resolve(false)); const condaExe = await condaService.getCondaFile(); assert.equal(condaExe, 'conda', 'Failed to identify'); @@ -470,7 +470,7 @@ suite('Interpreters Conda Service', () => { platformService.setup(p => p.isWindows).returns(() => true); processService.setup(p => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())).returns(() => Promise.reject(new Error('Not Found'))); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(expectedCodaExe))).returns(() => Promise.resolve(true)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(expectedCodaExe))).returns(() => Promise.resolve(true)); registryInterpreterLocatorService.setup(r => r.getInterpreters(TypeMoq.It.isAny())).returns(() => Promise.resolve(registryInterpreters)); const condaExe = await condaService.getCondaFile(); @@ -485,7 +485,7 @@ suite('Interpreters Conda Service', () => { test('isAvailable will return false if conda is not available', async () => { processService.setup(p => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())).returns(() => Promise.reject(new Error('not found'))); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isAny())).returns(() => Promise.resolve(false)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isAny())).returns(() => Promise.resolve(false)); platformService.setup(p => p.isWindows).returns(() => false); const isAvailable = await condaService.isCondaAvailable(); @@ -508,7 +508,7 @@ suite('Interpreters Conda Service', () => { test('isCondaInCurrentPath will return false if conda is not available', async () => { processService.setup(p => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())).returns(() => Promise.reject(new Error('not found'))); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isAny())).returns(() => Promise.resolve(false)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isAny())).returns(() => Promise.resolve(false)); platformService.setup(p => p.isWindows).returns(() => false); const isAvailable = await condaService.isCondaInCurrentPath(); @@ -531,17 +531,17 @@ suite('Interpreters Conda Service', () => { } test('Fails to identify an environment as a conda env (windows)', async () => { const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'one', 'python.exe'); - fileSystem.setup(f => f.directoryExistsAsync(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))).returns(() => Promise.resolve(true)); + fileSystem.setup(f => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))).returns(() => Promise.resolve(true)); await testFailureOfGettingCondaEnvironments(true, false, false, pythonPath); }); test('Fails to identify an environment as a conda env (linux)', async () => { const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'one', 'python'); - fileSystem.setup(f => f.directoryExistsAsync(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))).returns(() => Promise.resolve(true)); + fileSystem.setup(f => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))).returns(() => Promise.resolve(true)); await testFailureOfGettingCondaEnvironments(false, false, true, pythonPath); }); test('Fails to identify an environment as a conda env (osx)', async () => { const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'one', 'python'); - fileSystem.setup(f => f.directoryExistsAsync(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))).returns(() => Promise.resolve(true)); + fileSystem.setup(f => f.directoryExists(TypeMoq.It.isValue(path.join(path.dirname(pythonPath), 'conda-meta')))).returns(() => Promise.resolve(true)); await testFailureOfGettingCondaEnvironments(false, true, false, pythonPath); }); }); diff --git a/src/test/interpreters/currentPathService.test.ts b/src/test/interpreters/currentPathService.test.ts index 19e44c6eca76..dec8298fb205 100644 --- a/src/test/interpreters/currentPathService.test.ts +++ b/src/test/interpreters/currentPathService.test.ts @@ -65,10 +65,10 @@ suite('Interpreters CurrentPath Service', () => { processService.setup(p => p.exec(TypeMoq.It.isValue('python2'), TypeMoq.It.isValue(execArgs), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: 'c:/python2' })).verifiable(TypeMoq.Times.once()); processService.setup(p => p.exec(TypeMoq.It.isValue('python3'), TypeMoq.It.isValue(execArgs), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: 'c:/python3' })).verifiable(TypeMoq.Times.once()); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue('c:/root:python'))).returns(() => Promise.resolve(true)).verifiable(TypeMoq.Times.once()); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue('c:/python1'))).returns(() => Promise.resolve(false)).verifiable(TypeMoq.Times.once()); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue('c:/python2'))).returns(() => Promise.resolve(false)).verifiable(TypeMoq.Times.once()); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue('c:/python3'))).returns(() => Promise.resolve(true)).verifiable(TypeMoq.Times.once()); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue('c:/root:python'))).returns(() => Promise.resolve(true)).verifiable(TypeMoq.Times.once()); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue('c:/python1'))).returns(() => Promise.resolve(false)).verifiable(TypeMoq.Times.once()); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue('c:/python2'))).returns(() => Promise.resolve(false)).verifiable(TypeMoq.Times.once()); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue('c:/python3'))).returns(() => Promise.resolve(true)).verifiable(TypeMoq.Times.once()); const interpreters = await currentPathService.getInterpreters(); processService.verifyAll(); diff --git a/src/test/interpreters/display.test.ts b/src/test/interpreters/display.test.ts index fad85341c2a4..2d9d212d41a5 100644 --- a/src/test/interpreters/display.test.ts +++ b/src/test/interpreters/display.test.ts @@ -149,7 +149,7 @@ suite('Interpreters Display', () => { interpreterService.setup(i => i.getActiveInterpreter(TypeMoq.It.isValue(workspaceFolder))).returns(() => Promise.resolve(undefined)); configurationService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); pythonSettings.setup(p => p.pythonPath).returns(() => pythonPath); - fileSystem.setup(f => f.fileExistsAsync(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(false)); + fileSystem.setup(f => f.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(false)); const defaultDisplayName = `${path.basename(pythonPath)} [Environment]`; versionProvider.setup(v => v.getVersion(TypeMoq.It.isValue(pythonPath), TypeMoq.It.isAny())).returns(() => Promise.resolve(defaultDisplayName)); virtualEnvMgr.setup(v => v.getEnvironmentName(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve('')); @@ -169,7 +169,7 @@ suite('Interpreters Display', () => { interpreterService.setup(i => i.getActiveInterpreter(TypeMoq.It.isValue(workspaceFolder))).returns(() => Promise.resolve(undefined)); configurationService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); pythonSettings.setup(p => p.pythonPath).returns(() => pythonPath); - fileSystem.setup(f => f.fileExistsAsync(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); + fileSystem.setup(f => f.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); const defaultDisplayName = `${path.basename(pythonPath)} [Environment]`; versionProvider.setup(v => v.getVersion(TypeMoq.It.isValue(pythonPath), TypeMoq.It.isAny())).returns(() => Promise.resolve(defaultDisplayName)); // tslint:disable-next-line:no-any @@ -190,7 +190,7 @@ suite('Interpreters Display', () => { interpreterService.setup(i => i.getActiveInterpreter(TypeMoq.It.isValue(workspaceFolder))).returns(() => Promise.resolve(undefined)); configurationService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); pythonSettings.setup(p => p.pythonPath).returns(() => pythonPath); - fileSystem.setup(f => f.fileExistsAsync(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); + fileSystem.setup(f => f.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); const displayName = 'Version from Interperter'; versionProvider.setup(v => v.getVersion(TypeMoq.It.isValue(pythonPath), TypeMoq.It.isAny())).returns(() => Promise.resolve(displayName)); // tslint:disable-next-line:no-any diff --git a/src/test/interpreters/pipEnvService.test.ts b/src/test/interpreters/pipEnvService.test.ts index 4571dd14b939..b32b6c4c74a3 100644 --- a/src/test/interpreters/pipEnvService.test.ts +++ b/src/test/interpreters/pipEnvService.test.ts @@ -85,7 +85,7 @@ suite('Interpreters - PipEnv', () => { const env = {}; envVarsProvider.setup(e => e.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})).verifiable(TypeMoq.Times.once()); currentProcess.setup(c => c.env).returns(() => env); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(false)).verifiable(TypeMoq.Times.once()); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(false)).verifiable(TypeMoq.Times.once()); const environments = await pipEnvService.getInterpreters(resource); expect(environments).to.be.deep.equal([]); @@ -95,7 +95,7 @@ suite('Interpreters - PipEnv', () => { const env = {}; currentProcess.setup(c => c.env).returns(() => env); processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.reject('')); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(true)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(true)); appShell.setup(a => a.showWarningMessage(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('')).verifiable(TypeMoq.Times.once()); const environments = await pipEnvService.getInterpreters(resource); @@ -107,7 +107,7 @@ suite('Interpreters - PipEnv', () => { const env = {}; currentProcess.setup(c => c.env).returns(() => env); processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stderr: 'PipEnv Failed', stdout: '' })); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(true)); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(true)); appShell.setup(a => a.showWarningMessage(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('')).verifiable(TypeMoq.Times.once()); const environments = await pipEnvService.getInterpreters(resource); @@ -120,8 +120,8 @@ suite('Interpreters - PipEnv', () => { currentProcess.setup(c => c.env).returns(() => env); processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: venvDir })); interpreterVersionService.setup(v => v.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('xyz')); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(true)).verifiable(); - fileSystem.setup(fs => fs.directoryExistsAsync(TypeMoq.It.isValue(venvDir))).returns(() => Promise.resolve(true)).verifiable(); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(true)).verifiable(); + fileSystem.setup(fs => fs.directoryExists(TypeMoq.It.isValue(venvDir))).returns(() => Promise.resolve(true)).verifiable(); const environments = await pipEnvService.getInterpreters(resource); expect(environments).to.be.lengthOf(1); @@ -136,9 +136,9 @@ suite('Interpreters - PipEnv', () => { currentProcess.setup(c => c.env).returns(() => env); processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: venvDir })); interpreterVersionService.setup(v => v.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('xyz')); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(false)).verifiable(TypeMoq.Times.never()); - fileSystem.setup(fs => fs.fileExistsAsync(TypeMoq.It.isValue(path.join(rootWorkspace, envPipFile)))).returns(() => Promise.resolve(true)).verifiable(TypeMoq.Times.once()); - fileSystem.setup(fs => fs.directoryExistsAsync(TypeMoq.It.isValue(venvDir))).returns(() => Promise.resolve(true)).verifiable(); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(false)).verifiable(TypeMoq.Times.never()); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(path.join(rootWorkspace, envPipFile)))).returns(() => Promise.resolve(true)).verifiable(TypeMoq.Times.once()); + fileSystem.setup(fs => fs.directoryExists(TypeMoq.It.isValue(venvDir))).returns(() => Promise.resolve(true)).verifiable(); const environments = await pipEnvService.getInterpreters(resource); expect(environments).to.be.lengthOf(1); diff --git a/src/test/linters/lint.args.test.ts b/src/test/linters/lint.args.test.ts index 780aefb2fe61..4475ef2d94bf 100644 --- a/src/test/linters/lint.args.test.ts +++ b/src/test/linters/lint.args.test.ts @@ -52,7 +52,7 @@ suite('Linting - Arguments', () => { outputChannel = TypeMoq.Mock.ofType(); const fs = TypeMoq.Mock.ofType(); - fs.setup(x => x.fileExistsAsync(TypeMoq.It.isAny())).returns(() => new Promise((resolve, reject) => resolve(true))); + fs.setup(x => x.fileExists(TypeMoq.It.isAny())).returns(() => new Promise((resolve, reject) => resolve(true))); fs.setup(x => x.arePathsSame(TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString())).returns(() => true); serviceManager.addSingletonInstance(IFileSystem, fs.object); diff --git a/src/test/linters/lint.provider.test.ts b/src/test/linters/lint.provider.test.ts index 023ee86223be..ee893b73db8f 100644 --- a/src/test/linters/lint.provider.test.ts +++ b/src/test/linters/lint.provider.test.ts @@ -39,7 +39,7 @@ suite('Linting - Provider', () => { context = TypeMoq.Mock.ofType(); fs = TypeMoq.Mock.ofType(); - fs.setup(x => x.fileExistsAsync(TypeMoq.It.isAny())).returns(() => new Promise((resolve, reject) => resolve(true))); + fs.setup(x => x.fileExists(TypeMoq.It.isAny())).returns(() => new Promise((resolve, reject) => resolve(true))); fs.setup(x => x.arePathsSame(TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString())).returns(() => true); serviceManager.addSingletonInstance(IFileSystem, fs.object); diff --git a/src/test/linters/lintengine.test.ts b/src/test/linters/lintengine.test.ts index e11c2dcdc5c4..92bb25d17ba4 100644 --- a/src/test/linters/lintengine.test.ts +++ b/src/test/linters/lintengine.test.ts @@ -106,7 +106,7 @@ suite('Linting - LintingEngine', () => { }); function mockTextDocument(fileName: string, language: string, exists: boolean, ignorePattern: string[] = [], scheme?: string): TextDocument { - fileSystem.setup(x => x.fileExistsAsync(TypeMoq.It.isAnyString())).returns(() => Promise.resolve(exists)); + fileSystem.setup(x => x.fileExists(TypeMoq.It.isAnyString())).returns(() => Promise.resolve(exists)); lintSettings.setup(l => l.ignorePatterns).returns(() => ignorePattern); settings.setup(x => x.linting).returns(() => lintSettings.object); diff --git a/src/test/linters/pylint.test.ts b/src/test/linters/pylint.test.ts index ca51e7150834..66bc7fdb956d 100644 --- a/src/test/linters/pylint.test.ts +++ b/src/test/linters/pylint.test.ts @@ -63,11 +63,11 @@ suite('Linting - Pylint', () => { }); test('pylintrc in the file folder', async () => { - fileSystem.setup(x => x.fileExistsAsync(path.join(basePath, pylintrc))).returns(() => Promise.resolve(true)); + fileSystem.setup(x => x.fileExists(path.join(basePath, pylintrc))).returns(() => Promise.resolve(true)); let result = await Pylint.hasConfigurationFile(fileSystem.object, basePath, platformService.object); expect(result).to.be.equal(true, `'${pylintrc}' not detected in the file folder.`); - fileSystem.setup(x => x.fileExistsAsync(path.join(basePath, dotPylintrc))).returns(() => Promise.resolve(true)); + fileSystem.setup(x => x.fileExists(path.join(basePath, dotPylintrc))).returns(() => Promise.resolve(true)); result = await Pylint.hasConfigurationFile(fileSystem.object, basePath, platformService.object); expect(result).to.be.equal(true, `'${dotPylintrc}' not detected in the file folder.`); }); @@ -77,10 +77,10 @@ suite('Linting - Pylint', () => { const module3 = path.join('/user/a/b', '__init__.py'); const rc = path.join('/user/a/b/c', pylintrc); - fileSystem.setup(x => x.fileExistsAsync(module1)).returns(() => Promise.resolve(true)); - fileSystem.setup(x => x.fileExistsAsync(module2)).returns(() => Promise.resolve(true)); - fileSystem.setup(x => x.fileExistsAsync(module3)).returns(() => Promise.resolve(true)); - fileSystem.setup(x => x.fileExistsAsync(rc)).returns(() => Promise.resolve(true)); + fileSystem.setup(x => x.fileExists(module1)).returns(() => Promise.resolve(true)); + fileSystem.setup(x => x.fileExists(module2)).returns(() => Promise.resolve(true)); + fileSystem.setup(x => x.fileExists(module3)).returns(() => Promise.resolve(true)); + fileSystem.setup(x => x.fileExists(rc)).returns(() => Promise.resolve(true)); const result = await Pylint.hasConfigurationFile(fileSystem.object, basePath, platformService.object); expect(result).to.be.equal(true, `'${pylintrc}' not detected in the module tree.`); @@ -92,10 +92,10 @@ suite('Linting - Pylint', () => { const module3 = path.join('/user/a/b', '__init__.py'); const rc = path.join('/user/a/b/c', pylintrc); - fileSystem.setup(x => x.fileExistsAsync(module1)).returns(() => Promise.resolve(true)); - fileSystem.setup(x => x.fileExistsAsync(module2)).returns(() => Promise.resolve(true)); - fileSystem.setup(x => x.fileExistsAsync(module3)).returns(() => Promise.resolve(true)); - fileSystem.setup(x => x.fileExistsAsync(rc)).returns(() => Promise.resolve(true)); + fileSystem.setup(x => x.fileExists(module1)).returns(() => Promise.resolve(true)); + fileSystem.setup(x => x.fileExists(module2)).returns(() => Promise.resolve(true)); + fileSystem.setup(x => x.fileExists(module3)).returns(() => Promise.resolve(true)); + fileSystem.setup(x => x.fileExists(rc)).returns(() => Promise.resolve(true)); const result = await Pylint.hasConfigurationFile(fileSystem.object, basePath, platformService.object); expect(result).to.be.equal(true, `'${dotPylintrc}' not detected in the module tree.`); @@ -103,7 +103,7 @@ suite('Linting - Pylint', () => { test('.pylintrc up the ~ folder', async () => { const home = os.homedir(); const rc = path.join(home, dotPylintrc); - fileSystem.setup(x => x.fileExistsAsync(rc)).returns(() => Promise.resolve(true)); + fileSystem.setup(x => x.fileExists(rc)).returns(() => Promise.resolve(true)); const result = await Pylint.hasConfigurationFile(fileSystem.object, basePath, platformService.object); expect(result).to.be.equal(true, `'${dotPylintrc}' not detected in the ~ folder.`); @@ -111,14 +111,14 @@ suite('Linting - Pylint', () => { test('pylintrc up the ~/.config folder', async () => { const home = os.homedir(); const rc = path.join(home, '.config', pylintrc); - fileSystem.setup(x => x.fileExistsAsync(rc)).returns(() => Promise.resolve(true)); + fileSystem.setup(x => x.fileExists(rc)).returns(() => Promise.resolve(true)); const result = await Pylint.hasConfigurationFile(fileSystem.object, basePath, platformService.object); expect(result).to.be.equal(true, `'${pylintrc}' not detected in the ~/.config folder.`); }); test('pylintrc in the /etc folder', async () => { const rc = path.join('/etc', pylintrc); - fileSystem.setup(x => x.fileExistsAsync(rc)).returns(() => Promise.resolve(true)); + fileSystem.setup(x => x.fileExists(rc)).returns(() => Promise.resolve(true)); const result = await Pylint.hasConfigurationFile(fileSystem.object, basePath, platformService.object); expect(result).to.be.equal(true, `'${pylintrc}' not detected in the /etc folder.`); @@ -127,7 +127,7 @@ suite('Linting - Pylint', () => { const root = '/user/a'; const midFolder = '/user/a/b'; fileSystem - .setup(x => x.fileExistsAsync(path.join(midFolder, pylintrc))) + .setup(x => x.fileExists(path.join(midFolder, pylintrc))) .returns(() => Promise.resolve(true)); const result = await Pylint.hasConfigrationFileInWorkspace(fileSystem.object, basePath, root); @@ -136,7 +136,7 @@ suite('Linting - Pylint', () => { test('minArgs - pylintrc between the file and the workspace root', async () => { fileSystem - .setup(x => x.fileExistsAsync(path.join('/user/a/b', pylintrc))) + .setup(x => x.fileExists(path.join('/user/a/b', pylintrc))) .returns(() => Promise.resolve(true)); await testPylintArguments('/user/a/b/c', '/user/a', false); @@ -149,7 +149,7 @@ suite('Linting - Pylint', () => { test('minArgs - pylintrc next to the file', async () => { const fileFolder = '/user/a/b/c'; fileSystem - .setup(x => x.fileExistsAsync(path.join(fileFolder, pylintrc))) + .setup(x => x.fileExists(path.join(fileFolder, pylintrc))) .returns(() => Promise.resolve(true)); await testPylintArguments(fileFolder, '/user/a', false); @@ -158,7 +158,7 @@ suite('Linting - Pylint', () => { test('minArgs - pylintrc at the workspace root', async () => { const root = '/user/a'; fileSystem - .setup(x => x.fileExistsAsync(path.join(root, pylintrc))) + .setup(x => x.fileExists(path.join(root, pylintrc))) .returns(() => Promise.resolve(true)); await testPylintArguments('/user/a/b/c', root, false); diff --git a/src/test/signature/signature.ptvs.test.ts b/src/test/signature/signature.ptvs.test.ts index 8e2f630756d4..823433b50093 100644 --- a/src/test/signature/signature.ptvs.test.ts +++ b/src/test/signature/signature.ptvs.test.ts @@ -95,7 +95,7 @@ suite('Signatures (Analysis Engine)', () => { return; } const expected = [ - new SignatureHelpResult(0, 5, 0, 0, null), + new SignatureHelpResult(0, 5, 1, -1, null), new SignatureHelpResult(0, 6, 1, 0, 'value'), new SignatureHelpResult(0, 7, 1, 0, 'value'), new SignatureHelpResult(0, 8, 1, 1, '...'), From cc3932e46d7765f75efe8a2ce7800bf7a7f4e527 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 10 May 2018 06:57:00 -0700 Subject: [PATCH 243/433] Create a new API to retrieve interpreter details with the ability to cache the details (#1567) Fixes #1569 --- news/3 Code Health/1569.md | 1 + pythonFiles/interpreterInfo.py | 13 ++++ .../activation/interpreterDataService.ts | 2 +- src/client/common/installer/channelManager.ts | 6 +- src/client/common/installer/pipInstaller.ts | 2 +- .../common/installer/productInstaller.ts | 2 +- src/client/common/persistentState.ts | 29 +++++--- src/client/common/platform/fileSystem.ts | 13 ++++ src/client/common/platform/types.ts | 1 + .../common/process/pythonExecutionFactory.ts | 12 ++-- src/client/common/process/pythonProcess.ts | 41 +++++++---- .../common/process/pythonToolService.ts | 4 +- src/client/common/process/types.ts | 21 +++++- src/client/common/types.ts | 4 +- .../configurationProviderUtils.ts | 2 +- .../configuration/pythonPathUpdaterService.ts | 19 +++--- src/client/interpreter/contracts.ts | 21 +++--- src/client/interpreter/display/index.ts | 16 ++--- src/client/interpreter/helpers.ts | 38 ++++++++++- src/client/interpreter/interpreterService.ts | 33 ++++++--- src/client/interpreter/locators/index.ts | 5 ++ .../locators/services/KnownPathsService.ts | 28 ++++---- .../services/baseVirtualEnvService.ts | 30 ++++---- .../locators/services/condaEnvFileService.ts | 14 ++-- .../locators/services/condaEnvService.ts | 21 +++--- .../locators/services/currentPathService.ts | 25 ++++--- .../locators/services/pipEnvService.ts | 49 +++++++++---- .../services/windowsRegistryService.ts | 34 ++++++---- src/client/interpreter/serviceRegistry.ts | 2 + src/client/interpreter/virtualEnvs/index.ts | 68 +++++++++++++++++-- src/client/interpreter/virtualEnvs/types.ts | 4 ++ .../linters/errorHandlers/notInstalled.ts | 4 +- src/client/providers/importSortProvider.ts | 2 +- src/client/providers/jediProxy.ts | 4 +- src/client/refactor/proxy.ts | 2 +- src/client/unittests/common/runner.ts | 7 +- src/test/common/moduleInstaller.test.ts | 24 +++++-- .../pythonProc.simple.multiroot.test.ts | 10 +-- .../configuration/interpreterSelector.test.ts | 17 ++++- src/test/format/extension.format.test.ts | 4 +- .../install/channelManager.channels.test.ts | 15 ++++ .../install/channelManager.messages.test.ts | 16 ++++- src/test/install/pythonInstallation.test.ts | 20 +++++- .../interpreters/condaEnvFileService.test.ts | 16 ++--- src/test/interpreters/condaEnvService.test.ts | 29 ++++---- src/test/interpreters/condaService.test.ts | 36 +++++++--- .../interpreters/currentPathService.test.ts | 16 +++-- src/test/interpreters/display.test.ts | 35 +++++++--- src/test/interpreters/helper.test.ts | 2 +- .../interpreters/interpreterService.test.ts | 19 +++++- src/test/interpreters/pipEnvService.test.ts | 26 +++---- .../interpreters/virtualEnvManager.test.ts | 12 ++++ .../windowsRegistryService.test.ts | 9 ++- src/test/unittests/serviceRegistry.ts | 2 +- 54 files changed, 631 insertions(+), 256 deletions(-) create mode 100644 news/3 Code Health/1569.md create mode 100644 pythonFiles/interpreterInfo.py diff --git a/news/3 Code Health/1569.md b/news/3 Code Health/1569.md new file mode 100644 index 000000000000..46eca178ee8a --- /dev/null +++ b/news/3 Code Health/1569.md @@ -0,0 +1 @@ +Create a new API to retrieve interpreter details with the ability to cache the details. diff --git a/pythonFiles/interpreterInfo.py b/pythonFiles/interpreterInfo.py new file mode 100644 index 000000000000..4822594bd046 --- /dev/null +++ b/pythonFiles/interpreterInfo.py @@ -0,0 +1,13 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import json +import sys + +obj = {} +obj["versionInfo"] = sys.version_info[:4] +obj["sysPrefix"] = sys.prefix +obj["version"] = sys.version +obj["is64Bit"] = sys.maxsize > 2**32 + +print(json.dumps(obj)) diff --git a/src/client/activation/interpreterDataService.ts b/src/client/activation/interpreterDataService.ts index 45c7e42006c4..f923758b36bf 100644 --- a/src/client/activation/interpreterDataService.ts +++ b/src/client/activation/interpreterDataService.ts @@ -33,7 +33,7 @@ export class InterpreterDataService { public async getInterpreterData(resource?: Uri): Promise { const executionFactory = this.serviceContainer.get(IPythonExecutionFactory); - const execService = await executionFactory.create(resource); + const execService = await executionFactory.create({ resource }); const interpreterPath = await execService.getExecutablePath(); if (interpreterPath.length === 0) { diff --git a/src/client/common/installer/channelManager.ts b/src/client/common/installer/channelManager.ts index 3e3747f3d412..7c5780d9152e 100644 --- a/src/client/common/installer/channelManager.ts +++ b/src/client/common/installer/channelManager.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. import { inject, injectable } from 'inversify'; -import { QuickPickItem, Uri } from 'vscode'; +import { Uri } from 'vscode'; import { IInterpreterService, InterpreterType } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; import { IApplicationShell } from '../application/types'; @@ -34,9 +34,9 @@ export class InstallationChannelManager implements IInstallationChannelManager { label: `Install using ${installer.displayName}`, description: '', installer - } as QuickPickItem & { installer: IModuleInstaller }; + }; }); - const selection = await appShell.showQuickPick(options, { matchOnDescription: true, matchOnDetail: true, placeHolder }); + const selection = await appShell.showQuickPick(options, { matchOnDescription: true, matchOnDetail: true, placeHolder }); return selection ? selection.installer : undefined; } diff --git a/src/client/common/installer/pipInstaller.ts b/src/client/common/installer/pipInstaller.ts index bfc7ff4deadd..90a471b382b1 100644 --- a/src/client/common/installer/pipInstaller.ts +++ b/src/client/common/installer/pipInstaller.ts @@ -38,7 +38,7 @@ export class PipInstaller extends ModuleInstaller implements IModuleInstaller { } private isPipAvailable(resource?: Uri): Promise { const pythonExecutionFactory = this.serviceContainer.get(IPythonExecutionFactory); - return pythonExecutionFactory.create(resource) + return pythonExecutionFactory.create({ resource }) .then(proc => proc.isModuleInstalled('pip')) .catch(() => false); } diff --git a/src/client/common/installer/productInstaller.ts b/src/client/common/installer/productInstaller.ts index d214653c6f43..6c56fdc4da7d 100644 --- a/src/client/common/installer/productInstaller.ts +++ b/src/client/common/installer/productInstaller.ts @@ -74,7 +74,7 @@ abstract class BaseInstaller { const isModule = typeof moduleName === 'string' && moduleName.length > 0 && path.basename(executableName) === executableName; if (isModule) { - const pythonProcess = await this.serviceContainer.get(IPythonExecutionFactory).create(resource); + const pythonProcess = await this.serviceContainer.get(IPythonExecutionFactory).create({ resource }); return pythonProcess.isModuleInstalled(executableName); } else { const process = await this.serviceContainer.get(IProcessServiceFactory).create(resource); diff --git a/src/client/common/persistentState.ts b/src/client/common/persistentState.ts index a882db7cbc1f..8042c7f52d2a 100644 --- a/src/client/common/persistentState.ts +++ b/src/client/common/persistentState.ts @@ -8,25 +8,38 @@ import { Memento } from 'vscode'; import { GLOBAL_MEMENTO, IMemento, IPersistentState, IPersistentStateFactory, WORKSPACE_MEMENTO } from './types'; class PersistentState implements IPersistentState{ - constructor(private storage: Memento, private key: string, private defaultValue: T) { } + constructor(private storage: Memento, private key: string, private defaultValue?: T, private expiryDurationMs?: number) { } public get value(): T { - return this.storage.get(this.key, this.defaultValue); + if (this.expiryDurationMs) { + const cachedData = this.storage.get<{ data?: T; expiry?: number }>(this.key, { data: this.defaultValue! }); + if (!cachedData || !cachedData.expiry || cachedData.expiry < Date.now()) { + return this.defaultValue!; + } else { + return cachedData.data!; + } + } else { + return this.storage.get(this.key, this.defaultValue!); + } } public async updateValue(newValue: T): Promise { - await this.storage.update(this.key, newValue); + if (this.expiryDurationMs) { + await this.storage.update(this.key, { data: newValue, expiry: Date.now() + this.expiryDurationMs }); + } else { + await this.storage.update(this.key, newValue); + } } } @injectable() export class PersistentStateFactory implements IPersistentStateFactory { - constructor( @inject(IMemento) @named(GLOBAL_MEMENTO) private globalState: Memento, + constructor(@inject(IMemento) @named(GLOBAL_MEMENTO) private globalState: Memento, @inject(IMemento) @named(WORKSPACE_MEMENTO) private workspaceState: Memento) { } - public createGlobalPersistentState(key: string, defaultValue: T): IPersistentState { - return new PersistentState(this.globalState, key, defaultValue); + public createGlobalPersistentState(key: string, defaultValue?: T, expiryDurationMs?: number): IPersistentState { + return new PersistentState(this.globalState, key, defaultValue, expiryDurationMs); } - public createWorkspacePersistentState(key: string, defaultValue: T): IPersistentState { - return new PersistentState(this.workspaceState, key, defaultValue); + public createWorkspacePersistentState(key: string, defaultValue?: T, expiryDurationMs?: number): IPersistentState { + return new PersistentState(this.workspaceState, key, defaultValue, expiryDurationMs); } } diff --git a/src/client/common/platform/fileSystem.ts b/src/client/common/platform/fileSystem.ts index ecc9c564f446..fd409584e00d 100644 --- a/src/client/common/platform/fileSystem.ts +++ b/src/client/common/platform/fileSystem.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. 'use strict'; +import { createHash } from 'crypto'; import * as fs from 'fs-extra'; import { inject, injectable } from 'inversify'; import * as path from 'path'; @@ -117,4 +118,16 @@ export class FileSystem implements IFileSystem { fs.unlink(filename, err => err ? deferred.reject(err) : deferred.resolve()); return deferred.promise; } + public getFileHash(filePath: string): Promise { + return new Promise(resolve => { + fs.lstat(filePath, (err, stats) => { + if (err) { + resolve(); + } else { + const actual = createHash('sha512').update(`${stats.ctimeMs}-${stats.mtimeMs}`).digest('hex'); + resolve(actual); + } + }); + }); + } } diff --git a/src/client/common/platform/types.ts b/src/client/common/platform/types.ts index ce3836eb59a2..d3a7fe118505 100644 --- a/src/client/common/platform/types.ts +++ b/src/client/common/platform/types.ts @@ -46,4 +46,5 @@ export interface IFileSystem { getRealPath(path: string): Promise; copyFile(src: string, dest: string): Promise; deleteFile(filename: string): Promise; + getFileHash(filePath: string): Promise; } diff --git a/src/client/common/process/pythonExecutionFactory.ts b/src/client/common/process/pythonExecutionFactory.ts index ceafb3aa827b..3e9551905cec 100644 --- a/src/client/common/process/pythonExecutionFactory.ts +++ b/src/client/common/process/pythonExecutionFactory.ts @@ -4,17 +4,21 @@ import { inject, injectable } from 'inversify'; import { Uri } from 'vscode'; import { IServiceContainer } from '../../ioc/types'; +import { IConfigurationService } from '../types'; import { PythonExecutionService } from './pythonProcess'; -import { IProcessServiceFactory, IPythonExecutionFactory, IPythonExecutionService } from './types'; +import { ExecutionFactoryCreationOptions, IProcessServiceFactory, IPythonExecutionFactory, IPythonExecutionService } from './types'; @injectable() export class PythonExecutionFactory implements IPythonExecutionFactory { + private readonly configService: IConfigurationService; private processServiceFactory: IProcessServiceFactory; constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { this.processServiceFactory = serviceContainer.get(IProcessServiceFactory); + this.configService = serviceContainer.get(IConfigurationService); } - public async create(resource?: Uri): Promise { - const processService = await this.processServiceFactory.create(resource); - return new PythonExecutionService(this.serviceContainer, processService, resource); + public async create(options: ExecutionFactoryCreationOptions): Promise { + const pythonPath = options.pythonPath ? options.pythonPath : this.configService.getSettings(options.resource).pythonPath; + const processService = await this.processServiceFactory.create(options.resource); + return new PythonExecutionService(this.serviceContainer, processService, pythonPath); } } diff --git a/src/client/common/process/pythonProcess.ts b/src/client/common/process/pythonProcess.ts index 8a7894dc4f54..625a02f34e5b 100644 --- a/src/client/common/process/pythonProcess.ts +++ b/src/client/common/process/pythonProcess.ts @@ -2,28 +2,46 @@ // Licensed under the MIT License. import { injectable } from 'inversify'; +import * as path from 'path'; import { Uri } from 'vscode'; -import { IInterpreterVersionService } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; +import { EXTENSION_ROOT_DIR } from '../constants'; import { ErrorUtils } from '../errors/errorUtils'; import { ModuleNotInstalledError } from '../errors/moduleNotInstalledError'; -import { IFileSystem } from '../platform/types'; -import { IConfigurationService } from '../types'; -import { ExecutionResult, IProcessService, IPythonExecutionService, ObservableExecutionResult, SpawnOptions } from './types'; +import { Architecture, IFileSystem } from '../platform/types'; +import { EnvironmentVariables } from '../variables/types'; +import { ExecutionResult, InterpreterInfomation, IProcessService, IPythonExecutionService, ObservableExecutionResult, PythonVersionInfo, SpawnOptions } from './types'; @injectable() export class PythonExecutionService implements IPythonExecutionService { - private readonly configService: IConfigurationService; private readonly fileSystem: IFileSystem; - constructor(private serviceContainer: IServiceContainer, private readonly procService: IProcessService, private resource?: Uri) { - this.configService = serviceContainer.get(IConfigurationService); + constructor(private serviceContainer: IServiceContainer, private readonly procService: IProcessService, private readonly pythonPath: string) { this.fileSystem = serviceContainer.get(IFileSystem); } - public async getVersion(): Promise { - const versionService = this.serviceContainer.get(IInterpreterVersionService); - return versionService.getVersion(this.pythonPath, ''); + public async getInterpreterInformation(): Promise { + const file = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'interpreterInfo.py'); + try { + const [version, jsonValue] = await Promise.all([ + this.procService.exec(this.pythonPath, ['--version'], { mergeStdOutErr: true }) + .then(output => output.stdout.trim()), + this.procService.exec(this.pythonPath, [file], { mergeStdOutErr: true }) + .then(output => output.stdout.trim()) + ]); + + const json = JSON.parse(jsonValue) as { versionInfo: PythonVersionInfo; sysPrefix: string; sysVersion: string; is64Bit: boolean }; + return { + architecture: json.is64Bit ? Architecture.x64 : Architecture.x86, + path: this.pythonPath, + version, + sysVersion: json.sysVersion, + version_info: json.versionInfo, + sysPrefix: json.sysPrefix + }; + } catch (ex) { + console.error(`Failed to get interpreter information for '${this.pythonPath}'`, ex); + } } public async getExecutablePath(): Promise { // If we've passed the python file, then return the file. @@ -65,7 +83,4 @@ export class PythonExecutionService implements IPythonExecutionService { return result; } - private get pythonPath(): string { - return this.configService.getSettings(this.resource).pythonPath; - } } diff --git a/src/client/common/process/pythonToolService.ts b/src/client/common/process/pythonToolService.ts index 3369c35ac6b6..d4b2ccaaa8bb 100644 --- a/src/client/common/process/pythonToolService.ts +++ b/src/client/common/process/pythonToolService.ts @@ -15,7 +15,7 @@ export class PythonToolExecutionService implements IPythonToolExecutionService { throw new Error('Environment variables are not supported'); } if (executionInfo.moduleName && executionInfo.moduleName.length > 0) { - const pythonExecutionService = await this.serviceContainer.get(IPythonExecutionFactory).create(resource); + const pythonExecutionService = await this.serviceContainer.get(IPythonExecutionFactory).create({ resource }); return pythonExecutionService.execModuleObservable(executionInfo.moduleName, executionInfo.args, options); } else { const processService = await this.serviceContainer.get(IProcessServiceFactory).create(resource); @@ -27,7 +27,7 @@ export class PythonToolExecutionService implements IPythonToolExecutionService { throw new Error('Environment variables are not supported'); } if (executionInfo.moduleName && executionInfo.moduleName.length > 0) { - const pythonExecutionService = await this.serviceContainer.get(IPythonExecutionFactory).create(resource); + const pythonExecutionService = await this.serviceContainer.get(IPythonExecutionFactory).create({ resource }); return pythonExecutionService.execModule(executionInfo.moduleName!, executionInfo.args, options); } else { const processService = await this.serviceContainer.get(IProcessServiceFactory).create(resource); diff --git a/src/client/common/process/types.ts b/src/client/common/process/types.ts index 22fb6965be55..96862c8ca26d 100644 --- a/src/client/common/process/types.ts +++ b/src/client/common/process/types.ts @@ -4,6 +4,7 @@ import { ChildProcess, SpawnOptions as ChildProcessSpawnOptions } from 'child_process'; import { Observable } from 'rxjs/Observable'; import { CancellationToken, Uri } from 'vscode'; +import { Architecture } from '../platform/types'; import { ExecutionInfo } from '../types'; import { EnvironmentVariables } from '../variables/types'; @@ -46,14 +47,28 @@ export interface IProcessServiceFactory { } export const IPythonExecutionFactory = Symbol('IPythonExecutionFactory'); - +export type ExecutionFactoryCreationOptions = { + resource?: Uri; + pythonPath?: string; +}; export interface IPythonExecutionFactory { - create(resource?: Uri): Promise; + create(options: ExecutionFactoryCreationOptions): Promise; } - +export type ReleaseLevel = 'alpha' | 'beta' | 'candidate' | 'final'; +// tslint:disable-next-line:interface-name +export type PythonVersionInfo = [number, number, number, ReleaseLevel]; +export type InterpreterInfomation = { + path: string; + version: string; + sysVersion: string; + architecture: Architecture; + version_info: PythonVersionInfo; + sysPrefix: string; +}; export const IPythonExecutionService = Symbol('IPythonExecutionService'); export interface IPythonExecutionService { + getInterpreterInformation(): Promise; getExecutablePath(): Promise; isModuleInstalled(moduleName: string): Promise; diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 11ff9b508d6b..2bec7bb18256 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -23,8 +23,8 @@ export interface IPersistentState { export const IPersistentStateFactory = Symbol('IPersistentStateFactory'); export interface IPersistentStateFactory { - createGlobalPersistentState(key: string, defaultValue: T): IPersistentState; - createWorkspacePersistentState(key: string, defaultValue: T): IPersistentState; + createGlobalPersistentState(key: string, defaultValue?: T, expiryDurationMs?: number): IPersistentState; + createWorkspacePersistentState(key: string, defaultValue?: T, expiryDurationMs?: number): IPersistentState; } export type ExecutionInfo = { diff --git a/src/client/debugger/configProviders/configurationProviderUtils.ts b/src/client/debugger/configProviders/configurationProviderUtils.ts index 6bc13f0bf53f..108426f9b979 100644 --- a/src/client/debugger/configProviders/configurationProviderUtils.ts +++ b/src/client/debugger/configProviders/configurationProviderUtils.ts @@ -24,7 +24,7 @@ export class ConfigurationProviderUtils implements IConfigurationProviderUtils { } public async getPyramidStartupScriptFilePath(resource?: Uri): Promise { try { - const executionService = await this.executionFactory.create(resource); + const executionService = await this.executionFactory.create({ resource }); const output = await executionService.exec(['-c', 'import pyramid;print(pyramid.__file__)'], { throwOnStdErr: true }); const pserveFilePath = path.join(path.dirname(output.stdout.trim()), 'scripts', PSERVE_SCRIPT_FILE_NAME); return await this.fs.fileExists(pserveFilePath) ? pserveFilePath : undefined; diff --git a/src/client/interpreter/configuration/pythonPathUpdaterService.ts b/src/client/interpreter/configuration/pythonPathUpdaterService.ts index 812fcd48b6de..e9579a565177 100644 --- a/src/client/interpreter/configuration/pythonPathUpdaterService.ts +++ b/src/client/interpreter/configuration/pythonPathUpdaterService.ts @@ -1,6 +1,7 @@ import { inject, injectable } from 'inversify'; import * as path from 'path'; import { ConfigurationTarget, Uri, window } from 'vscode'; +import { InterpreterInfomation, IPythonExecutionFactory } from '../../common/process/types'; import { StopWatch } from '../../common/stopWatch'; import { IServiceContainer } from '../../ioc/types'; import { sendTelemetryEvent } from '../../telemetry'; @@ -13,9 +14,11 @@ import { IPythonPathUpdaterServiceFactory, IPythonPathUpdaterServiceManager } fr export class PythonPathUpdaterService implements IPythonPathUpdaterServiceManager { private readonly pythonPathSettingsUpdaterFactory: IPythonPathUpdaterServiceFactory; private readonly interpreterVersionService: IInterpreterVersionService; + private readonly executionFactory: IPythonExecutionFactory; constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { this.pythonPathSettingsUpdaterFactory = serviceContainer.get(IPythonPathUpdaterServiceFactory); this.interpreterVersionService = serviceContainer.get(IInterpreterVersionService); + this.executionFactory = serviceContainer.get(IPythonExecutionFactory); } public async updatePythonPath(pythonPath: string, configTarget: ConfigurationTarget, trigger: 'ui' | 'shebang' | 'load', wkspace?: Uri): Promise { const stopWatch = new StopWatch(); @@ -39,17 +42,17 @@ export class PythonPathUpdaterService implements IPythonPathUpdaterServiceManage failed, trigger }; if (!failed) { - const pyVersionPromise = this.interpreterVersionService.getVersion(pythonPath, '') - .then(pyVersion => pyVersion.length === 0 ? undefined : pyVersion); + const processService = await this.executionFactory.create({ pythonPath }); + const infoPromise = processService.getInterpreterInformation().catch(() => undefined); const pipVersionPromise = this.interpreterVersionService.getPipVersion(pythonPath) .then(value => value.length === 0 ? undefined : value) - .catch(() => undefined); - const versions = await Promise.all([pyVersionPromise, pipVersionPromise]); - if (versions[0]) { - telemtryProperties.version = versions[0] as string; + .catch(() => undefined); + const [info, pipVersion] = await Promise.all([infoPromise, pipVersionPromise]); + if (info) { + telemtryProperties.version = info.version; } - if (versions[1]) { - telemtryProperties.pipVersion = versions[1] as string; + if (pipVersion) { + telemtryProperties.pipVersion = pipVersion; } } sendTelemetryEvent(PYTHON_INTERPRETER, duration, telemtryProperties); diff --git a/src/client/interpreter/contracts.ts b/src/client/interpreter/contracts.ts index 9029a05dbb4e..1b3b793230bf 100644 --- a/src/client/interpreter/contracts.ts +++ b/src/client/interpreter/contracts.ts @@ -1,5 +1,5 @@ import { CodeLensProvider, ConfigurationTarget, Disposable, Event, TextDocument, Uri } from 'vscode'; -import { Architecture } from '../common/platform/types'; +import { InterpreterInfomation } from '../common/process/types'; export const INTERPRETER_LOCATOR_SERVICE = 'IInterpreterLocatorService'; export const WINDOWS_REGISTRY_SERVICE = 'WindowsRegistryService'; @@ -10,7 +10,6 @@ export const KNOWN_PATH_SERVICE = 'KnownPathsService'; export const GLOBAL_VIRTUAL_ENV_SERVICE = 'VirtualEnvService'; export const WORKSPACE_VIRTUAL_ENV_SERVICE = 'WorkspaceVirtualEnvService'; export const PIPENV_SERVICE = 'PipEnvService'; - export const IInterpreterVersionService = Symbol('IInterpreterVersionService'); export interface IInterpreterVersionService { getVersion(pythonPath: string, defaultValue: string): Promise; @@ -54,15 +53,14 @@ export interface ICondaService { export enum InterpreterType { Unknown = 1, Conda = 2, - VirtualEnv = 4 + VirtualEnv = 4, + PipEnv = 8, + Pyenv = 16, + Venv = 32 } - -export type PythonInterpreter = { - path: string; +export type PythonInterpreter = InterpreterInfomation & { companyDisplayName?: string; displayName?: string; - version?: string; - architecture?: Architecture; type: InterpreterType; envName?: string; envPath?: string; @@ -80,6 +78,7 @@ export interface IInterpreterService { getInterpreters(resource?: Uri): Promise; autoSetInterpreter(): Promise; getActiveInterpreter(resource?: Uri): Promise; + getInterpreterDetails(pythonPath: string): Promise>; refresh(): Promise; initialize(): void; } @@ -97,4 +96,10 @@ export interface IShebangCodeLensProvider extends CodeLensProvider { export const IInterpreterHelper = Symbol('IInterpreterHelper'); export interface IInterpreterHelper { getActiveWorkspaceUri(): WorkspacePythonPath | undefined; + getInterpreterInformation(pythonPath: string): Promise>; +} + +export const IPipEnvService = Symbol('IPipEnvService'); +export interface IPipEnvService { + isRelatedPipEnvironment(dir: string, pythonPath: string): Promise; } diff --git a/src/client/interpreter/display/index.ts b/src/client/interpreter/display/index.ts index e419691b31d2..14291ea76468 100644 --- a/src/client/interpreter/display/index.ts +++ b/src/client/interpreter/display/index.ts @@ -6,7 +6,7 @@ import { IApplicationShell, IWorkspaceService } from '../../common/application/t import { IFileSystem } from '../../common/platform/types'; import { IConfigurationService, IDisposableRegistry } from '../../common/types'; import { IServiceContainer } from '../../ioc/types'; -import { IInterpreterDisplay, IInterpreterHelper, IInterpreterService, IInterpreterVersionService } from '../contracts'; +import { IInterpreterDisplay, IInterpreterHelper, IInterpreterService, PythonInterpreter } from '../contracts'; import { IVirtualEnvironmentManager } from '../virtualEnvs/types'; // tslint:disable-next-line:completed-docs @@ -15,7 +15,6 @@ export class InterpreterDisplay implements IInterpreterDisplay { private readonly statusBar: StatusBarItem; private readonly interpreterService: IInterpreterService; private readonly virtualEnvMgr: IVirtualEnvironmentManager; - private readonly versionProvider: IInterpreterVersionService; private readonly fileSystem: IFileSystem; private readonly configurationService: IConfigurationService; private readonly helper: IInterpreterHelper; @@ -24,7 +23,6 @@ export class InterpreterDisplay implements IInterpreterDisplay { constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { this.interpreterService = serviceContainer.get(IInterpreterService); this.virtualEnvMgr = serviceContainer.get(IVirtualEnvironmentManager); - this.versionProvider = serviceContainer.get(IInterpreterVersionService); this.fileSystem = serviceContainer.get(IFileSystem); this.configurationService = serviceContainer.get(IConfigurationService); this.helper = serviceContainer.get(IInterpreterHelper); @@ -63,17 +61,17 @@ export class InterpreterDisplay implements IInterpreterDisplay { this.statusBar.tooltip += toolTipSuffix; } } else { - const defaultDisplayName = `${path.basename(pythonPath)} [Environment]`; await Promise.all([ this.fileSystem.fileExists(pythonPath), - this.versionProvider.getVersion(pythonPath, defaultDisplayName), - this.getVirtualEnvironmentName(pythonPath).catch(() => '') + this.helper.getInterpreterInformation(pythonPath).catch>(() => undefined), + this.getVirtualEnvironmentName(pythonPath).catch(() => '') ]) - .then(([interpreterExists, displayName, virtualEnvName]) => { + .then(([interpreterExists, details, virtualEnvName]) => { + const defaultDisplayName = `${path.basename(pythonPath)} [Environment]`; const dislayNameSuffix = virtualEnvName.length > 0 ? ` (${virtualEnvName})` : ''; - this.statusBar.text = `${displayName}${dislayNameSuffix}`; + this.statusBar.text = `${details ? details.version : defaultDisplayName}${dislayNameSuffix}`; - if (!interpreterExists && displayName === defaultDisplayName && interpreters.length > 0) { + if (!interpreterExists && !details && interpreters.length > 0) { this.statusBar.color = 'yellow'; this.statusBar.text = '$(alert) Select Python Environment'; } diff --git a/src/client/interpreter/helpers.ts b/src/client/interpreter/helpers.ts index ba272607a147..00535634a590 100644 --- a/src/client/interpreter/helpers.ts +++ b/src/client/interpreter/helpers.ts @@ -1,8 +1,14 @@ import { inject, injectable } from 'inversify'; import { ConfigurationTarget } from 'vscode'; import { IDocumentManager, IWorkspaceService } from '../common/application/types'; +import { IFileSystem } from '../common/platform/types'; +import { IPythonExecutionFactory } from '../common/process/types'; +import { IPersistentStateFactory } from '../common/types'; import { IServiceContainer } from '../ioc/types'; -import { IInterpreterHelper, WorkspacePythonPath } from './contracts'; +import { IInterpreterHelper, PythonInterpreter, WorkspacePythonPath } from './contracts'; + +const EXPITY_DURATION = 24 * 60 * 60 * 1000; +type CachedPythonInterpreter = Partial & { fileHash: string }; export function getFirstNonEmptyLineFromMultilineString(stdout: string) { if (!stdout) { @@ -14,13 +20,17 @@ export function getFirstNonEmptyLineFromMultilineString(stdout: string) { @injectable() export class InterpreterHelper implements IInterpreterHelper { + private readonly fs: IFileSystem; + private readonly persistentFactory: IPersistentStateFactory; constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { + this.persistentFactory = this.serviceContainer.get(IPersistentStateFactory); + this.fs = this.serviceContainer.get(IFileSystem); } public getActiveWorkspaceUri(): WorkspacePythonPath | undefined { const workspaceService = this.serviceContainer.get(IWorkspaceService); const documentManager = this.serviceContainer.get(IDocumentManager); - if (!Array.isArray(workspaceService.workspaceFolders) || workspaceService.workspaceFolders.length === 0) { + if (!workspaceService.hasWorkspaceFolders) { return; } if (workspaceService.workspaceFolders.length === 1) { @@ -33,4 +43,28 @@ export class InterpreterHelper implements IInterpreterHelper { } } } + public async getInterpreterInformation(pythonPath: string): Promise> { + const fileHash = await this.fs.getFileHash(pythonPath).catch(() => ''); + const store = this.persistentFactory.createGlobalPersistentState(pythonPath, undefined, EXPITY_DURATION); + if (store.value && store.value.fileHash === fileHash) { + return store.value; + } + const processService = await this.serviceContainer.get(IPythonExecutionFactory).create({ pythonPath }); + + try { + const info = await processService.getInterpreterInformation().catch(() => undefined); + if (!info) { + return; + } + const details = { + ...(info), + fileHash + }; + await store.updateValue(details); + return details; + } catch (ex) { + console.error(`Failed to get interpreter information for '${pythonPath}'`, ex); + return {}; + } + } } diff --git a/src/client/interpreter/interpreterService.ts b/src/client/interpreter/interpreterService.ts index 3dee82a4ceda..b0b69910241a 100644 --- a/src/client/interpreter/interpreterService.ts +++ b/src/client/interpreter/interpreterService.ts @@ -10,8 +10,8 @@ import { IServiceContainer } from '../ioc/types'; import { IPythonPathUpdaterServiceManager } from './configuration/types'; import { IInterpreterDisplay, IInterpreterHelper, IInterpreterLocatorService, - IInterpreterService, IInterpreterVersionService, INTERPRETER_LOCATOR_SERVICE, - InterpreterType, PIPENV_SERVICE, PythonInterpreter, WORKSPACE_VIRTUAL_ENV_SERVICE + IInterpreterService, INTERPRETER_LOCATOR_SERVICE, + PIPENV_SERVICE, PythonInterpreter, WORKSPACE_VIRTUAL_ENV_SERVICE } from './contracts'; import { IVirtualEnvironmentManager } from './virtualEnvs/types'; @@ -93,29 +93,40 @@ export class InterpreterService implements Disposable, IInterpreterService { public async getActiveInterpreter(resource?: Uri): Promise { const pythonExecutionFactory = this.serviceContainer.get(IPythonExecutionFactory); - const pythonExecutionService = await pythonExecutionFactory.create(resource); + const pythonExecutionService = await pythonExecutionFactory.create({ resource }); const fullyQualifiedPath = await pythonExecutionService.getExecutablePath().catch(() => undefined); // Python path is invalid or python isn't installed. if (!fullyQualifiedPath) { return; } + + return this.getInterpreterDetails(fullyQualifiedPath, resource); + } + public async getInterpreterDetails(pythonPath: string, resource?: Uri): Promise { const interpreters = await this.getInterpreters(resource); - const interpreter = interpreters.find(i => utils.arePathsSame(i.path, fullyQualifiedPath)); + const interpreter = interpreters.find(i => utils.arePathsSame(i.path, pythonPath)); if (interpreter) { return interpreter; } - const pythonExecutableName = path.basename(fullyQualifiedPath); - const versionInfo = await this.serviceContainer.get(IInterpreterVersionService).getVersion(fullyQualifiedPath, pythonExecutableName); + const interpreterHelper = this.serviceContainer.get(IInterpreterHelper); const virtualEnvManager = this.serviceContainer.get(IVirtualEnvironmentManager); - const virtualEnvName = await virtualEnvManager.getEnvironmentName(fullyQualifiedPath); + const [details, virtualEnvName, type] = await Promise.all([ + interpreterHelper.getInterpreterInformation(pythonPath), + virtualEnvManager.getEnvironmentName(pythonPath), + virtualEnvManager.getEnvironmentType(pythonPath) + ]); + if (details) { + return; + } const dislayNameSuffix = virtualEnvName.length > 0 ? ` (${virtualEnvName})` : ''; - const displayName = `${versionInfo}${dislayNameSuffix}`; + const displayName = `${details.version!}${dislayNameSuffix}`; return { + ...(details as PythonInterpreter), displayName, - path: fullyQualifiedPath, - type: virtualEnvName.length > 0 ? InterpreterType.VirtualEnv : InterpreterType.Unknown, - version: versionInfo + path: pythonPath, + envName: virtualEnvName, + type: type }; } private async shouldAutoSetInterpreter(): Promise { diff --git a/src/client/interpreter/locators/index.ts b/src/client/interpreter/locators/index.ts index f40667b70b44..e5e05ca49d8a 100644 --- a/src/client/interpreter/locators/index.ts +++ b/src/client/interpreter/locators/index.ts @@ -49,6 +49,8 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi // tslint:disable-next-line:underscore-consistent-invocation return _.flatten(listOfInterpreters) + .filter(item => !!item) + .map(item => item!) .map(fixInterpreterDisplayName) .map(item => { item.path = path.normalize(item.path); return item; }) .reduce((accumulator, current) => { @@ -70,6 +72,9 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi private getLocators(): IInterpreterLocatorService[] { const locators: IInterpreterLocatorService[] = []; // The order of the services is important. + // The order is important because the data sources at the bottom of the list do not contain all, + // the information about the interpreters (e.g. type, environment name, etc). + // This way, the items returned from the top of the list will win, when we combine the items returned. if (this.platform.isWindows) { locators.push(this.serviceContainer.get(IInterpreterLocatorService, WINDOWS_REGISTRY_SERVICE)); } diff --git a/src/client/interpreter/locators/services/KnownPathsService.ts b/src/client/interpreter/locators/services/KnownPathsService.ts index 1fb6b99f57fa..348bcf3d96a9 100644 --- a/src/client/interpreter/locators/services/KnownPathsService.ts +++ b/src/client/interpreter/locators/services/KnownPathsService.ts @@ -4,7 +4,7 @@ import * as path from 'path'; import { Uri } from 'vscode'; import { fsExistsAsync, IS_WINDOWS } from '../../../common/utils'; import { IServiceContainer } from '../../../ioc/types'; -import { IInterpreterVersionService, IKnownSearchPathsForInterpreters, InterpreterType, PythonInterpreter } from '../../contracts'; +import { IInterpreterHelper, IKnownSearchPathsForInterpreters, InterpreterType, PythonInterpreter } from '../../contracts'; import { lookForInterpretersInDirectory } from '../helpers'; import { CacheableLocatorService } from './cacheableLocatorService'; @@ -13,8 +13,8 @@ const untildify = require('untildify'); @injectable() export class KnownPathsService extends CacheableLocatorService { - public constructor( @inject(IKnownSearchPathsForInterpreters) private knownSearchPaths: string[], - @inject(IInterpreterVersionService) private versionProvider: IInterpreterVersionService, + public constructor(@inject(IKnownSearchPathsForInterpreters) private knownSearchPaths: string[], + @inject(IInterpreterHelper) private helper: IInterpreterHelper, @inject(IServiceContainer) serviceContainer: IServiceContainer) { super('KnownPathsService', serviceContainer); } @@ -29,17 +29,19 @@ export class KnownPathsService extends CacheableLocatorService { // tslint:disable-next-line:underscore-consistent-invocation .then(listOfInterpreters => _.flatten(listOfInterpreters)) .then(interpreters => interpreters.filter(item => item.length > 0)) - .then(interpreters => Promise.all(interpreters.map(interpreter => this.getInterpreterDetails(interpreter)))); + .then(interpreters => Promise.all(interpreters.map(interpreter => this.getInterpreterDetails(interpreter)))) + .then(interpreters => interpreters.filter(interpreter => !!interpreter).map(interpreter => interpreter!)); } - private getInterpreterDetails(interpreter: string) { - return this.versionProvider.getVersion(interpreter, path.basename(interpreter)) - .then(displayName => { - return { - displayName, - path: interpreter, - type: InterpreterType.Unknown - }; - }); + private async getInterpreterDetails(interpreter: string) { + const details = await this.helper.getInterpreterInformation(interpreter); + if (!details) { + return; + } + return { + ...(details as PythonInterpreter), + path: interpreter, + type: InterpreterType.Unknown + }; } private getInterpretersInDirectory(dir: string) { return fsExistsAsync(dir) diff --git a/src/client/interpreter/locators/services/baseVirtualEnvService.ts b/src/client/interpreter/locators/services/baseVirtualEnvService.ts index 4499a3a38acf..8c48d470d9bc 100644 --- a/src/client/interpreter/locators/services/baseVirtualEnvService.ts +++ b/src/client/interpreter/locators/services/baseVirtualEnvService.ts @@ -4,7 +4,7 @@ import * as path from 'path'; import { Uri } from 'vscode'; import { IFileSystem, IPlatformService } from '../../../common/platform/types'; import { IServiceContainer } from '../../../ioc/types'; -import { IInterpreterVersionService, InterpreterType, IVirtualEnvironmentsSearchPathProvider, PythonInterpreter } from '../../contracts'; +import { IInterpreterHelper, IVirtualEnvironmentsSearchPathProvider, PythonInterpreter } from '../../contracts'; import { IVirtualEnvironmentManager } from '../../virtualEnvs/types'; import { lookForInterpretersInDirectory } from '../helpers'; import { CacheableLocatorService } from './cacheableLocatorService'; @@ -12,7 +12,7 @@ import { CacheableLocatorService } from './cacheableLocatorService'; @injectable() export class BaseVirtualEnvService extends CacheableLocatorService { private readonly virtualEnvMgr: IVirtualEnvironmentManager; - private readonly versionProvider: IInterpreterVersionService; + private readonly helper: IInterpreterHelper; private readonly fileSystem: IFileSystem; public constructor(@unmanaged() private searchPathsProvider: IVirtualEnvironmentsSearchPathProvider, @unmanaged() serviceContainer: IServiceContainer, @@ -20,7 +20,7 @@ export class BaseVirtualEnvService extends CacheableLocatorService { @unmanaged() cachePerWorkspace: boolean = false) { super(name, serviceContainer, cachePerWorkspace); this.virtualEnvMgr = serviceContainer.get(IVirtualEnvironmentManager); - this.versionProvider = serviceContainer.get(IInterpreterVersionService); + this.helper = serviceContainer.get(IInterpreterHelper); this.fileSystem = serviceContainer.get(IFileSystem); } // tslint:disable-next-line:no-empty @@ -30,16 +30,17 @@ export class BaseVirtualEnvService extends CacheableLocatorService { } private async suggestionsFromKnownVenvs(resource?: Uri) { const searchPaths = this.searchPathsProvider.getSearchPaths(resource); - return Promise.all(searchPaths.map(dir => this.lookForInterpretersInVenvs(dir))) + return Promise.all(searchPaths.map(dir => this.lookForInterpretersInVenvs(dir, resource))) .then(listOfInterpreters => _.flatten(listOfInterpreters)); } - private async lookForInterpretersInVenvs(pathToCheck: string) { + private async lookForInterpretersInVenvs(pathToCheck: string, resource?: Uri) { return this.fileSystem.getSubDirectories(pathToCheck) .then(subDirs => Promise.all(this.getProspectiveDirectoriesForLookup(subDirs))) .then(dirs => dirs.filter(dir => dir.length > 0)) .then(dirs => Promise.all(dirs.map(lookForInterpretersInDirectory))) .then(pathsWithInterpreters => _.flatten(pathsWithInterpreters)) .then(interpreters => Promise.all(interpreters.map(interpreter => this.getVirtualEnvDetails(interpreter)))) + .then(interpreters => interpreters.filter(interpreter => !!interpreter).map(interpreter => interpreter!)) .catch((err) => { console.error('Python Extension (lookForInterpretersInVenvs):', err); // Ignore exceptions. @@ -64,17 +65,22 @@ export class BaseVirtualEnvService extends CacheableLocatorService { return ''; })); } - private async getVirtualEnvDetails(interpreter: string): Promise { + private async getVirtualEnvDetails(interpreter: string): Promise { return Promise.all([ - this.versionProvider.getVersion(interpreter, path.basename(interpreter)), - this.virtualEnvMgr.getEnvironmentName(interpreter) + this.helper.getInterpreterInformation(interpreter), + this.virtualEnvMgr.getEnvironmentName(interpreter), + this.virtualEnvMgr.getEnvironmentType(interpreter) ]) - .then(([displayName, virtualEnvName]) => { + .then(([details, virtualEnvName, type]) => { + if (!details) { + return; + } const virtualEnvSuffix = virtualEnvName.length ? virtualEnvName : this.getVirtualEnvironmentRootDirectory(interpreter); return { - displayName: `${displayName} (${virtualEnvSuffix})`.trim(), - path: interpreter, - type: virtualEnvName.length > 0 ? InterpreterType.VirtualEnv : InterpreterType.Unknown + ...(details as PythonInterpreter), + displayName: `${details.version!} (${virtualEnvSuffix})`.trim(), + envName: virtualEnvName, + type: type }; }); } diff --git a/src/client/interpreter/locators/services/condaEnvFileService.ts b/src/client/interpreter/locators/services/condaEnvFileService.ts index 2f9e681abb3f..7984c2fb5495 100644 --- a/src/client/interpreter/locators/services/condaEnvFileService.ts +++ b/src/client/interpreter/locators/services/condaEnvFileService.ts @@ -1,12 +1,11 @@ import { inject, injectable } from 'inversify'; -import * as path from 'path'; import { Uri } from 'vscode'; import { IFileSystem } from '../../../common/platform/types'; import { ILogger } from '../../../common/types'; import { IServiceContainer } from '../../../ioc/types'; import { ICondaService, - IInterpreterVersionService, + IInterpreterHelper, InterpreterType, PythonInterpreter } from '../../contracts'; @@ -15,7 +14,7 @@ import { AnacondaCompanyName, AnacondaCompanyNames, AnacondaDisplayName } from ' @injectable() export class CondaEnvFileService extends CacheableLocatorService { - constructor(@inject(IInterpreterVersionService) private versionService: IInterpreterVersionService, + constructor(@inject(IInterpreterHelper) private helperService: IInterpreterHelper, @inject(ICondaService) private condaService: ICondaService, @inject(IFileSystem) private fileSystem: IFileSystem, @inject(IServiceContainer) serviceContainer: IServiceContainer, @@ -70,13 +69,16 @@ export class CondaEnvFileService extends CacheableLocatorService { return; } - const version = await this.versionService.getVersion(interpreter, path.basename(interpreter)); - const versionWithoutCompanyName = this.stripCompanyName(version); + const details = await this.helperService.getInterpreterInformation(interpreter); + if (!details) { + return; + } + const versionWithoutCompanyName = this.stripCompanyName(details.version!); return { displayName: `${AnacondaDisplayName} ${versionWithoutCompanyName}`, + ...(details as PythonInterpreter), path: interpreter, companyDisplayName: AnacondaCompanyName, - version: version, type: InterpreterType.Conda, envPath: environmentPath }; diff --git a/src/client/interpreter/locators/services/condaEnvService.ts b/src/client/interpreter/locators/services/condaEnvService.ts index 781f3b286f75..dfdde13f1001 100644 --- a/src/client/interpreter/locators/services/condaEnvService.ts +++ b/src/client/interpreter/locators/services/condaEnvService.ts @@ -6,7 +6,7 @@ import { Uri } from 'vscode'; import { IFileSystem } from '../../../common/platform/types'; import { ILogger } from '../../../common/types'; import { IServiceContainer } from '../../../ioc/types'; -import { CondaInfo, ICondaService, IInterpreterVersionService, InterpreterType, PythonInterpreter } from '../../contracts'; +import { CondaInfo, ICondaService, IInterpreterHelper, InterpreterType, PythonInterpreter } from '../../contracts'; import { CacheableLocatorService } from './cacheableLocatorService'; import { AnacondaCompanyName, AnacondaCompanyNames } from './conda'; import { CondaHelper } from './condaHelper'; @@ -15,7 +15,7 @@ import { CondaHelper } from './condaHelper'; export class CondaEnvService extends CacheableLocatorService { private readonly condaHelper = new CondaHelper(); constructor(@inject(ICondaService) private condaService: ICondaService, - @inject(IInterpreterVersionService) private versionService: IInterpreterVersionService, + @inject(IInterpreterHelper) private helper: IInterpreterHelper, @inject(ILogger) private logger: ILogger, @inject(IServiceContainer) serviceContainer: IServiceContainer, @inject(IFileSystem) private fileSystem: IFileSystem) { @@ -37,25 +37,24 @@ export class CondaEnvService extends CacheableLocatorService { .map(async envPath => { const pythonPath = this.condaService.getInterpreterPath(envPath); - const existsPromise = pythonPath ? this.fileSystem.fileExists(pythonPath) : Promise.resolve(false); - const versionPromise = this.versionService.getVersion(pythonPath, ''); - - const [exists, version] = await Promise.all([existsPromise, versionPromise]); - if (!exists) { + if (!(await this.fileSystem.fileExists(pythonPath))) { + return; + } + const details = await this.helper.getInterpreterInformation(pythonPath); + if (!details) { return; } - const versionWithoutCompanyName = this.stripCondaDisplayName(this.stripCompanyName(version), condaDisplayName); + const versionWithoutCompanyName = this.stripCondaDisplayName(this.stripCompanyName(details.version!), condaDisplayName); const displayName = `${condaDisplayName} ${versionWithoutCompanyName}`.trim(); - // tslint:disable-next-line:no-unnecessary-local-variable - const interpreter: PythonInterpreter = { + return { + ...(details as PythonInterpreter), path: pythonPath, displayName, companyDisplayName: AnacondaCompanyName, type: InterpreterType.Conda, envPath }; - return interpreter; }); return Promise.all(promises) diff --git a/src/client/interpreter/locators/services/currentPathService.ts b/src/client/interpreter/locators/services/currentPathService.ts index 5d8c6fc1b636..a4e81dbed34d 100644 --- a/src/client/interpreter/locators/services/currentPathService.ts +++ b/src/client/interpreter/locators/services/currentPathService.ts @@ -1,12 +1,11 @@ import { inject, injectable } from 'inversify'; import * as _ from 'lodash'; -import * as path from 'path'; import { Uri } from 'vscode'; import { IFileSystem } from '../../../common/platform/types'; import { IProcessServiceFactory } from '../../../common/process/types'; import { IConfigurationService } from '../../../common/types'; import { IServiceContainer } from '../../../ioc/types'; -import { IInterpreterVersionService, InterpreterType, PythonInterpreter } from '../../contracts'; +import { IInterpreterHelper, InterpreterType, PythonInterpreter } from '../../contracts'; import { IVirtualEnvironmentManager } from '../../virtualEnvs/types'; import { CacheableLocatorService } from './cacheableLocatorService'; @@ -14,7 +13,7 @@ import { CacheableLocatorService } from './cacheableLocatorService'; export class CurrentPathService extends CacheableLocatorService { private readonly fs: IFileSystem; public constructor(@inject(IVirtualEnvironmentManager) private virtualEnvMgr: IVirtualEnvironmentManager, - @inject(IInterpreterVersionService) private versionProvider: IInterpreterVersionService, + @inject(IInterpreterHelper) private helper: IInterpreterHelper, @inject(IProcessServiceFactory) private readonly processServiceFactory: IProcessServiceFactory, @inject(IServiceContainer) serviceContainer: IServiceContainer) { super('CurrentPathService', serviceContainer); @@ -36,19 +35,25 @@ export class CurrentPathService extends CacheableLocatorService { .then(listOfInterpreters => _.flatten(listOfInterpreters)) .then(interpreters => interpreters.filter(item => item.length > 0)) // tslint:disable-next-line:promise-function-async - .then(interpreters => Promise.all(interpreters.map(interpreter => this.getInterpreterDetails(interpreter)))); + .then(interpreters => Promise.all(interpreters.map(interpreter => this.getInterpreterDetails(interpreter, resource)))); } - private async getInterpreterDetails(interpreter: string): Promise { + private async getInterpreterDetails(interpreter: string, resource?: Uri): Promise { return Promise.all([ - this.versionProvider.getVersion(interpreter, path.basename(interpreter)), - this.virtualEnvMgr.getEnvironmentName(interpreter) + this.helper.getInterpreterInformation(interpreter), + this.virtualEnvMgr.getEnvironmentName(interpreter), + this.virtualEnvMgr.getEnvironmentType(interpreter, resource) ]). - then(([displayName, virtualEnvName]) => { - displayName += virtualEnvName.length > 0 ? ` (${virtualEnvName})` : ''; + then(([details, virtualEnvName, type]) => { + if (!details) { + return; + } + const displayName = `${details.version ? details.version : ''}${virtualEnvName.length > 0 ? ` (${virtualEnvName})` : ''}`; return { + ...(details as PythonInterpreter), displayName, + envName: virtualEnvName, path: interpreter, - type: virtualEnvName ? InterpreterType.VirtualEnv : InterpreterType.Unknown + type: type ? type : InterpreterType.Unknown }; }); } diff --git a/src/client/interpreter/locators/services/pipEnvService.ts b/src/client/interpreter/locators/services/pipEnvService.ts index 46985a70c81d..c3e737c65955 100644 --- a/src/client/interpreter/locators/services/pipEnvService.ts +++ b/src/client/interpreter/locators/services/pipEnvService.ts @@ -9,17 +9,16 @@ import { IFileSystem } from '../../../common/platform/types'; import { IProcessServiceFactory } from '../../../common/process/types'; import { ICurrentProcess } from '../../../common/types'; import { IEnvironmentVariablesProvider } from '../../../common/variables/types'; -import { getPythonExecutable } from '../../../debugger/Common/Utils'; import { IServiceContainer } from '../../../ioc/types'; -import { IInterpreterVersionService, InterpreterType, PythonInterpreter } from '../../contracts'; +import { IInterpreterHelper, InterpreterType, IPipEnvService, PythonInterpreter } from '../../contracts'; import { CacheableLocatorService } from './cacheableLocatorService'; const execName = 'pipenv'; const pipEnvFileNameVariable = 'PIPENV_PIPFILE'; @injectable() -export class PipEnvService extends CacheableLocatorService { - private readonly versionService: IInterpreterVersionService; +export class PipEnvService extends CacheableLocatorService implements IPipEnvService { + private readonly helper: IInterpreterHelper; private readonly processServiceFactory: IProcessServiceFactory; private readonly workspace: IWorkspaceService; private readonly fs: IFileSystem; @@ -27,7 +26,7 @@ export class PipEnvService extends CacheableLocatorService { constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { super('PipEnvService', serviceContainer); - this.versionService = this.serviceContainer.get(IInterpreterVersionService); + this.helper = this.serviceContainer.get(IInterpreterHelper); this.processServiceFactory = this.serviceContainer.get(IProcessServiceFactory); this.workspace = this.serviceContainer.get(IWorkspaceService); this.fs = this.serviceContainer.get(IFileSystem); @@ -35,6 +34,14 @@ export class PipEnvService extends CacheableLocatorService { } // tslint:disable-next-line:no-empty public dispose() { } + public async isRelatedPipEnvironment(dir: string, pythonPath: string): Promise { + // In PipEnv, the name of the cwd is used as a prefix in the virtual env. + if (pythonPath.indexOf(`${path.sep}${path.basename(dir)}-`) === -1) { + return false; + } + const envName = await this.getInterpreterPathFromPipenv(dir, true); + return !!envName; + } protected getInterpretersImplementation(resource?: Uri): Promise { const pipenvCwd = this.getPipenvWorkingDirectory(resource); if (!pipenvCwd) { @@ -52,13 +59,15 @@ export class PipEnvService extends CacheableLocatorService { return; } - const pythonExecutablePath = getPythonExecutable(interpreterPath); - const ver = await this.versionService.getVersion(pythonExecutablePath, ''); + const details = await this.helper.getInterpreterInformation(interpreterPath); + if (!details) { + return; + } return { - path: pythonExecutablePath, - displayName: `${ver} (${execName})`, - type: InterpreterType.VirtualEnv, - version: ver + ...(details as PythonInterpreter), + displayName: `${details.version} (${execName})`, + path: interpreterPath, + type: InterpreterType.PipEnv }; } @@ -72,13 +81,25 @@ export class PipEnvService extends CacheableLocatorService { return wsFolder ? wsFolder.uri.fsPath : this.workspace.rootPath; } - private async getInterpreterPathFromPipenv(cwd: string): Promise { + private async getInterpreterPathFromPipenv(cwd: string, ignoreErrors = false): Promise { // Quick check before actually running pipenv if (!await this.checkIfPipFileExists(cwd)) { return; } - const venvFolder = await this.invokePipenv('--venv', cwd); - return venvFolder && await this.fs.directoryExists(venvFolder) ? venvFolder : undefined; + try { + const pythonPath = await this.invokePipenv('--py', cwd); + // TODO: Why do we need to do this? + return pythonPath && await this.fs.fileExists(pythonPath) ? pythonPath : undefined; + // tslint:disable-next-line:no-empty + } catch (error) { + console.error(error); + if (ignoreErrors) { + return; + } + const errorMessage = error.message || error; + const appShell = this.serviceContainer.get(IApplicationShell); + appShell.showWarningMessage(`Workspace contains pipfile but attempt to run 'pipenv --py' failed with ${errorMessage}. Make sure pipenv is on the PATH.`); + } } private async checkIfPipFileExists(cwd: string): Promise { const currentProcess = this.serviceContainer.get(ICurrentProcess); diff --git a/src/client/interpreter/locators/services/windowsRegistryService.ts b/src/client/interpreter/locators/services/windowsRegistryService.ts index dc724fc7e7c8..2226fc3f387c 100644 --- a/src/client/interpreter/locators/services/windowsRegistryService.ts +++ b/src/client/interpreter/locators/services/windowsRegistryService.ts @@ -6,7 +6,7 @@ import { Uri } from 'vscode'; import { Architecture, IRegistry, RegistryHive } from '../../../common/platform/types'; import { Is64Bit } from '../../../common/types'; import { IServiceContainer } from '../../../ioc/types'; -import { InterpreterType, PythonInterpreter } from '../../contracts'; +import { IInterpreterHelper, InterpreterType, PythonInterpreter } from '../../contracts'; import { CacheableLocatorService } from './cacheableLocatorService'; import { AnacondaCompanyName, AnacondaCompanyNames } from './conda'; @@ -20,14 +20,14 @@ const PythonCoreCompanyDisplayName = 'Python Software Foundation'; const PythonCoreComany = 'PYTHONCORE'; type CompanyInterpreter = { - companyKey: string, - hive: RegistryHive, - arch?: Architecture + companyKey: string; + hive: RegistryHive; + arch?: Architecture; }; @injectable() export class WindowsRegistryService extends CacheableLocatorService { - constructor( @inject(IRegistry) private registry: IRegistry, + constructor(@inject(IRegistry) private registry: IRegistry, @inject(Is64Bit) private is64Bit: boolean, @inject(IServiceContainer) serviceContainer: IServiceContainer) { super('WindowsRegistryService', serviceContainer); @@ -84,11 +84,11 @@ export class WindowsRegistryService extends CacheableLocatorService { private getInreterpreterDetailsForCompany(tagKey: string, companyKey: string, hive: RegistryHive, arch?: Architecture): Promise { const key = `${tagKey}\\InstallPath`; type InterpreterInformation = null | undefined | { - installPath: string, - executablePath?: string, - displayName?: string, - version?: string, - companyDisplayName?: string + installPath: string; + executablePath?: string; + displayName?: string; + version?: string; + companyDisplayName?: string; }; return this.registry.getValue(key, hive, arch) .then(installPath => { @@ -109,20 +109,26 @@ export class WindowsRegistryService extends CacheableLocatorService { ]) .then(([installedPath, executablePath, displayName, version, companyDisplayName]) => { companyDisplayName = AnacondaCompanyNames.indexOf(companyDisplayName) === -1 ? companyDisplayName : AnacondaCompanyName; - // tslint:disable-next-line:prefer-type-cast + // tslint:disable-next-line:prefer-type-cast no-object-literal-type-assertion return { installPath: installedPath, executablePath, displayName, version, companyDisplayName } as InterpreterInformation; }); }) - .then((interpreterInfo?: InterpreterInformation) => { + .then(async (interpreterInfo?: InterpreterInformation) => { if (!interpreterInfo) { return; } const executablePath = interpreterInfo.executablePath && interpreterInfo.executablePath.length > 0 ? interpreterInfo.executablePath : path.join(interpreterInfo.installPath, DefaultPythonExecutable); const displayName = interpreterInfo.displayName; - const version = interpreterInfo.version ? path.basename(interpreterInfo.version) : path.basename(tagKey); - // tslint:disable-next-line:prefer-type-cast + const helper = this.serviceContainer.get(IInterpreterHelper); + const details = await helper.getInterpreterInformation(executablePath); + if (!details) { + return; + } + const version = interpreterInfo.version ? path.basename(interpreterInfo.version) : details.version; + // tslint:disable-next-line:prefer-type-cast no-object-literal-type-assertion return { + ...(details as PythonInterpreter), architecture: arch, displayName, path: executablePath, diff --git a/src/client/interpreter/serviceRegistry.ts b/src/client/interpreter/serviceRegistry.ts index 18b891608f26..f867bb2ce1ac 100644 --- a/src/client/interpreter/serviceRegistry.ts +++ b/src/client/interpreter/serviceRegistry.ts @@ -20,6 +20,7 @@ import { IInterpreterVersionService, IKnownSearchPathsForInterpreters, INTERPRETER_LOCATOR_SERVICE, + IPipEnvService, IShebangCodeLensProvider, IVirtualEnvironmentsSearchPathProvider, KNOWN_PATH_SERVICE, @@ -61,6 +62,7 @@ export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IInterpreterLocatorService, GlobalVirtualEnvService, GLOBAL_VIRTUAL_ENV_SERVICE); serviceManager.addSingleton(IInterpreterLocatorService, WorkspaceVirtualEnvService, WORKSPACE_VIRTUAL_ENV_SERVICE); serviceManager.addSingleton(IInterpreterLocatorService, PipEnvService, PIPENV_SERVICE); + serviceManager.addSingleton(IPipEnvService, PipEnvService); const isWindows = serviceManager.get(IsWindows); if (isWindows) { diff --git a/src/client/interpreter/virtualEnvs/index.ts b/src/client/interpreter/virtualEnvs/index.ts index bf05b6a3b917..61f14d7f1c6f 100644 --- a/src/client/interpreter/virtualEnvs/index.ts +++ b/src/client/interpreter/virtualEnvs/index.ts @@ -2,26 +2,84 @@ // Licensed under the MIT License. import { inject, injectable } from 'inversify'; +import * as path from 'path'; +import { Uri } from 'vscode'; +import { IWorkspaceService } from '../../common/application/types'; +import { IFileSystem } from '../../common/platform/types'; import { IProcessServiceFactory } from '../../common/process/types'; import { IServiceContainer } from '../../ioc/types'; +import { InterpreterType, IPipEnvService } from '../contracts'; import { IVirtualEnvironmentManager } from './types'; +const PYENVFILES = ['pyvenv.cfg', path.join('..', 'pyvenv.cfg')]; + @injectable() export class VirtualEnvironmentManager implements IVirtualEnvironmentManager { private processServiceFactory: IProcessServiceFactory; + private pipEnvService: IPipEnvService; + private fs: IFileSystem; + private pyEnvRoot?: string; + private workspaceService: IWorkspaceService; constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { this.processServiceFactory = serviceContainer.get(IProcessServiceFactory); + this.fs = serviceContainer.get(IFileSystem); + this.pipEnvService = serviceContainer.get(IPipEnvService); + this.workspaceService = serviceContainer.get(IWorkspaceService); } public async getEnvironmentName(pythonPath: string): Promise { // https://stackoverflow.com/questions/1871549/determine-if-python-is-running-inside-virtualenv // hasattr(sys, 'real_prefix') works for virtualenv while // '(hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix))' works for venv - const code = 'import sys\nif hasattr(sys, "real_prefix"):\n print("virtualenv")\nelif hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix:\n print("venv")'; - const processService = await this.processServiceFactory.create(); - const output = await processService.exec(pythonPath, ['-c', code]); - if (output.stdout.length > 0) { - return output.stdout.trim(); + try { + const processService = await this.processServiceFactory.create(); + const code = 'import sys\nif hasattr(sys, "real_prefix"):\n print("virtualenv")\nelif hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix:\n print("venv")'; + const output = await processService.exec(pythonPath, ['-c', code]); + if (output.stdout.length > 0) { + return output.stdout.trim(); + } + } catch { + // do nothing. } return ''; } + public async getEnvironmentType(pythonPath: string, resource?: Uri): Promise { + const dir = path.dirname(pythonPath); + const pyEnvCfgFiles = PYENVFILES.map(file => path.join(dir, file)); + for (const file of pyEnvCfgFiles) { + if (await this.fs.fileExists(file)) { + return InterpreterType.Venv; + } + } + + const pyEnvRoot = await this.getPyEnvRoot(resource); + if (pyEnvRoot && pythonPath.startsWith(pyEnvRoot)) { + return InterpreterType.Pyenv; + } + + const defaultWorkspaceUri = this.workspaceService.hasWorkspaceFolders ? this.workspaceService.workspaceFolders![0].uri : undefined; + const workspaceFolder = resource ? this.workspaceService.getWorkspaceFolder(resource) : undefined; + const workspaceUri = workspaceFolder ? workspaceFolder.uri : defaultWorkspaceUri; + if (workspaceUri && this.pipEnvService.isRelatedPipEnvironment(pythonPath, workspaceUri.fsPath)) { + return InterpreterType.PipEnv; + } + + if ((await this.getEnvironmentName(pythonPath)).length > 0) { + return InterpreterType.VirtualEnv; + } + + // Lets not try to determine whether this is a conda environment or not. + return InterpreterType.Unknown; + } + private async getPyEnvRoot(resource?: Uri): Promise { + if (this.pyEnvRoot) { + return this.pyEnvRoot; + } + try { + const processService = await this.processServiceFactory.create(resource); + const output = await processService.exec('pyenv', ['root']); + return this.pyEnvRoot = output.stdout.trim(); + } catch { + return; + } + } } diff --git a/src/client/interpreter/virtualEnvs/types.ts b/src/client/interpreter/virtualEnvs/types.ts index 971772fd009d..6096f88d374c 100644 --- a/src/client/interpreter/virtualEnvs/types.ts +++ b/src/client/interpreter/virtualEnvs/types.ts @@ -1,7 +1,11 @@ + // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { Uri } from 'vscode'; +import { InterpreterType } from '../contracts'; export const IVirtualEnvironmentManager = Symbol('VirtualEnvironmentManager'); export interface IVirtualEnvironmentManager { getEnvironmentName(pythonPath: string): Promise; + getEnvironmentType(pythonPath: string, resource?: Uri): Promise; } diff --git a/src/client/linters/errorHandlers/notInstalled.ts b/src/client/linters/errorHandlers/notInstalled.ts index 3ffd90363c58..c3fbd9447296 100644 --- a/src/client/linters/errorHandlers/notInstalled.ts +++ b/src/client/linters/errorHandlers/notInstalled.ts @@ -11,10 +11,10 @@ export class NotInstalledErrorHandler extends BaseErrorHandler { super(product, outputChannel, serviceContainer); } public async handleError(error: Error, resource: Uri, execInfo: ExecutionInfo): Promise { - const pythonExecutionService = await this.serviceContainer.get(IPythonExecutionFactory).create(resource); + const pythonExecutionService = await this.serviceContainer.get(IPythonExecutionFactory).create({ resource }); const isModuleInstalled = await pythonExecutionService.isModuleInstalled(execInfo.moduleName!); if (isModuleInstalled) { - return this.nextHandler ? await this.nextHandler.handleError(error, resource, execInfo) : false; + return this.nextHandler ? this.nextHandler.handleError(error, resource, execInfo) : false; } this.installer.promptToInstall(this.product, resource) diff --git a/src/client/providers/importSortProvider.ts b/src/client/providers/importSortProvider.ts index 74effd123947..90733f15bfe1 100644 --- a/src/client/providers/importSortProvider.ts +++ b/src/client/providers/importSortProvider.ts @@ -38,7 +38,7 @@ export class PythonImportSortProvider { const processService = await this.processServiceFactory.create(document.uri); promise = processService.exec(isort, args, { throwOnStdErr: true }); } else { - promise = this.pythonExecutionFactory.create(document.uri) + promise = this.pythonExecutionFactory.create({ resource: document.uri }) .then(executionService => executionService.exec([importScript].concat(args), { throwOnStdErr: true })); } diff --git a/src/client/providers/jediProxy.ts b/src/client/providers/jediProxy.ts index 85714dd9cf50..8d7811e161b8 100644 --- a/src/client/providers/jediProxy.ts +++ b/src/client/providers/jediProxy.ts @@ -330,7 +330,7 @@ export class JediProxy implements Disposable { this.languageServerStarted.reject(new Error('Language Server not started.')); } this.languageServerStarted = createDeferred(); - const pythonProcess = await this.serviceContainer.get(IPythonExecutionFactory).create(Uri.file(this.workspacePath)); + const pythonProcess = await this.serviceContainer.get(IPythonExecutionFactory).create({ resource: Uri.file(this.workspacePath) }); // Check if the python path is valid. if ((await pythonProcess.getExecutablePath().catch(() => '')).length === 0) { return; @@ -606,7 +606,7 @@ export class JediProxy implements Disposable { private async getPathFromPythonCommand(args: string[]): Promise { try { - const pythonProcess = await this.serviceContainer.get(IPythonExecutionFactory).create(Uri.file(this.workspacePath)); + const pythonProcess = await this.serviceContainer.get(IPythonExecutionFactory).create({ resource: Uri.file(this.workspacePath) }); const result = await pythonProcess.exec(args, { cwd: this.workspacePath }); const lines = result.stdout.trim().splitLines(); if (lines.length === 0) { diff --git a/src/client/refactor/proxy.ts b/src/client/refactor/proxy.ts index 725f5eb65005..d5268d1f931f 100644 --- a/src/client/refactor/proxy.ts +++ b/src/client/refactor/proxy.ts @@ -106,7 +106,7 @@ export class RefactorProxy extends Disposable { }); } private async initialize(pythonPath: string): Promise { - const pythonProc = await this.serviceContainer.get(IPythonExecutionFactory).create(Uri.file(this.workspaceRoot)); + const pythonProc = await this.serviceContainer.get(IPythonExecutionFactory).create({ resource: Uri.file(this.workspaceRoot) }); this.initialized = createDeferred(); const args = ['refactor.py', this.workspaceRoot]; const cwd = path.join(this._extensionDir, 'pythonFiles'); diff --git a/src/client/unittests/common/runner.ts b/src/client/unittests/common/runner.ts index 56f7fba07eab..9bf612a33b58 100644 --- a/src/client/unittests/common/runner.ts +++ b/src/client/unittests/common/runner.ts @@ -3,15 +3,14 @@ import { CancellationToken, OutputChannel, Uri } from 'vscode'; import { PythonSettings } from '../../common/configSettings'; import { ErrorUtils } from '../../common/errors/errorUtils'; import { ModuleNotInstalledError } from '../../common/errors/moduleNotInstalledError'; -import { IPythonToolExecutionService } from '../../common/process/types'; import { IPythonExecutionFactory, IPythonExecutionService, + IPythonToolExecutionService, ObservableExecutionResult, SpawnOptions } from '../../common/process/types'; -import { IPythonSettings } from '../../common/types'; -import { ExecutionInfo } from '../../common/types'; +import { ExecutionInfo, IPythonSettings } from '../../common/types'; import { IServiceContainer } from '../../ioc/types'; import { NOSETEST_PROVIDER, PYTEST_PROVIDER, UNITTEST_PROVIDER } from './constants'; import { ITestsHelper, TestProvider } from './types'; @@ -36,7 +35,7 @@ export async function run(serviceContainer: IServiceContainer, testProvider: Tes if (!testExecutablePath && testProvider === UNITTEST_PROVIDER) { // Unit tests have a special way of being executed const pythonServiceFactory = serviceContainer.get(IPythonExecutionFactory); - pythonExecutionServicePromise = pythonServiceFactory.create(options.workspaceFolder); + pythonExecutionServicePromise = pythonServiceFactory.create({ resource: options.workspaceFolder }); promise = pythonExecutionServicePromise.then(executionService => executionService.execObservable(options.args, { ...spawnOptions })); } else { const pythonToolsExecutionService = serviceContainer.get(IPythonToolExecutionService); diff --git a/src/test/common/moduleInstaller.test.ts b/src/test/common/moduleInstaller.test.ts index 84835f18f7b2..3ee2379fcc7e 100644 --- a/src/test/common/moduleInstaller.test.ts +++ b/src/test/common/moduleInstaller.test.ts @@ -27,6 +27,19 @@ import { MockProcessService } from '../mocks/proc'; import { UnitTestIocContainer } from '../unittests/serviceRegistry'; import { closeActiveWindows, initializeTest } from './../initialize'; +const info: PythonInterpreter = { + architecture: Architecture.Unknown, + companyDisplayName: '', + displayName: '', + envName: '', + path: '', + type: InterpreterType.Unknown, + version: '', + version_info: [0, 0, 0, 'alpha'], + sysPrefix: '', + sysVersion: '' +}; + // tslint:disable-next-line:max-func-body-length suite('Module Installer', () => { let ioc: UnitTestIocContainer; @@ -91,7 +104,7 @@ suite('Module Installer', () => { async function getCurrentPythonPath(): Promise { const pythonPath = PythonSettings.getInstance(workspaceUri).pythonPath; if (path.basename(pythonPath) === pythonPath) { - const pythonProc = await ioc.serviceContainer.get(IPythonExecutionFactory).create(workspaceUri); + const pythonProc = await ioc.serviceContainer.get(IPythonExecutionFactory).create({ resource: workspaceUri }); return pythonProc.getExecutablePath().catch(() => pythonPath); } else { return pythonPath; @@ -133,7 +146,7 @@ suite('Module Installer', () => { ioc.serviceManager.addSingletonInstance(IModuleInstaller, new MockModuleInstaller('mock', true)); const pythonPath = await getCurrentPythonPath(); const mockInterpreterLocator = TypeMoq.Mock.ofType(); - mockInterpreterLocator.setup(p => p.getInterpreters(TypeMoq.It.isAny())).returns(() => Promise.resolve([{ architecture: Architecture.Unknown, companyDisplayName: '', displayName: '', envName: '', path: pythonPath, type: InterpreterType.Conda, version: '' }])); + mockInterpreterLocator.setup(p => p.getInterpreters(TypeMoq.It.isAny())).returns(() => Promise.resolve([{ ...info, architecture: Architecture.Unknown, companyDisplayName: '', displayName: '', envName: '', path: pythonPath, type: InterpreterType.Conda, version: '' }])); ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, mockInterpreterLocator.object, INTERPRETER_LOCATOR_SERVICE); ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, TypeMoq.Mock.ofType().object, PIPENV_SERVICE); @@ -189,11 +202,12 @@ suite('Module Installer', () => { test('Validate pip install arguments', async () => { const interpreterPath = await getCurrentPythonPath(); const mockInterpreterLocator = TypeMoq.Mock.ofType(); - mockInterpreterLocator.setup(p => p.getInterpreters(TypeMoq.It.isAny())).returns(() => Promise.resolve([{ path: interpreterPath, type: InterpreterType.Unknown }])); + mockInterpreterLocator.setup(p => p.getInterpreters(TypeMoq.It.isAny())).returns(() => Promise.resolve([{ ...info, path: interpreterPath, type: InterpreterType.Unknown }])); ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, mockInterpreterLocator.object, INTERPRETER_LOCATOR_SERVICE); ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, TypeMoq.Mock.ofType().object, PIPENV_SERVICE); const interpreter: PythonInterpreter = { + ...info, type: InterpreterType.Unknown, path: PYTHON_PATH }; @@ -220,7 +234,7 @@ suite('Module Installer', () => { test('Validate Conda install arguments', async () => { const interpreterPath = await getCurrentPythonPath(); const mockInterpreterLocator = TypeMoq.Mock.ofType(); - mockInterpreterLocator.setup(p => p.getInterpreters(TypeMoq.It.isAny())).returns(() => Promise.resolve([{ path: interpreterPath, type: InterpreterType.Conda }])); + mockInterpreterLocator.setup(p => p.getInterpreters(TypeMoq.It.isAny())).returns(() => Promise.resolve([{ ...info, path: interpreterPath, type: InterpreterType.Conda }])); ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, mockInterpreterLocator.object, INTERPRETER_LOCATOR_SERVICE); ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, TypeMoq.Mock.ofType().object, PIPENV_SERVICE); @@ -242,7 +256,7 @@ suite('Module Installer', () => { test('Validate pipenv install arguments', async () => { const mockInterpreterLocator = TypeMoq.Mock.ofType(); - mockInterpreterLocator.setup(p => p.getInterpreters(TypeMoq.It.isAny())).returns(() => Promise.resolve([{ path: 'interpreterPath', type: InterpreterType.VirtualEnv }])); + mockInterpreterLocator.setup(p => p.getInterpreters(TypeMoq.It.isAny())).returns(() => Promise.resolve([{ ...info, path: 'interpreterPath', type: InterpreterType.VirtualEnv }])); ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, mockInterpreterLocator.object, PIPENV_SERVICE); const moduleName = 'xyz'; diff --git a/src/test/common/process/pythonProc.simple.multiroot.test.ts b/src/test/common/process/pythonProc.simple.multiroot.test.ts index d8ff7c09cbb4..c0c914ec7982 100644 --- a/src/test/common/process/pythonProc.simple.multiroot.test.ts +++ b/src/test/common/process/pythonProc.simple.multiroot.test.ts @@ -83,7 +83,7 @@ suite('PythonExecutableService', () => { test('Importing without a valid PYTHONPATH should fail', async () => { await configService.updateSettingAsync('envFile', 'someInvalidFile.env', workspace4PyFile, ConfigurationTarget.WorkspaceFolder); pythonExecFactory = serviceContainer.get(IPythonExecutionFactory); - const pythonExecService = await pythonExecFactory.create(workspace4PyFile); + const pythonExecService = await pythonExecFactory.create({ resource: workspace4PyFile }); const promise = pythonExecService.exec([workspace4PyFile.fsPath], { cwd: path.dirname(workspace4PyFile.fsPath), throwOnStdErr: true }); await expect(promise).to.eventually.be.rejectedWith(StdErrError); @@ -91,14 +91,14 @@ suite('PythonExecutableService', () => { test('Importing with a valid PYTHONPATH from .env file should succeed', async () => { await configService.updateSettingAsync('envFile', undefined, workspace4PyFile, ConfigurationTarget.WorkspaceFolder); - const pythonExecService = await pythonExecFactory.create(workspace4PyFile); + const pythonExecService = await pythonExecFactory.create({ resource: workspace4PyFile }); const promise = pythonExecService.exec([workspace4PyFile.fsPath], { cwd: path.dirname(workspace4PyFile.fsPath), throwOnStdErr: true }); await expect(promise).to.eventually.have.property('stdout', `Hello${EOL}`); }); test('Known modules such as \'os\' and \'sys\' should be deemed \'installed\'', async () => { - const pythonExecService = await pythonExecFactory.create(workspace4PyFile); + const pythonExecService = await pythonExecFactory.create({ resource: workspace4PyFile }); const osModuleIsInstalled = pythonExecService.isModuleInstalled('os'); const sysModuleIsInstalled = pythonExecService.isModuleInstalled('sys'); await expect(osModuleIsInstalled).to.eventually.equal(true, 'os module is not installed'); @@ -106,7 +106,7 @@ suite('PythonExecutableService', () => { }); test('Unknown modules such as \'xyzabc123\' be deemed \'not installed\'', async () => { - const pythonExecService = await pythonExecFactory.create(workspace4PyFile); + const pythonExecService = await pythonExecFactory.create({ resource: workspace4PyFile }); const randomModuleName = `xyz123${new Date().getSeconds()}`; const randomModuleIsInstalled = pythonExecService.isModuleInstalled(randomModuleName); await expect(randomModuleIsInstalled).to.eventually.equal(false, `Random module '${randomModuleName}' is installed`); @@ -119,7 +119,7 @@ suite('PythonExecutableService', () => { resolve(stdout.trim()); }); }); - const pythonExecService = await pythonExecFactory.create(workspace4PyFile); + const pythonExecService = await pythonExecFactory.create({ resource: workspace4PyFile }); const executablePath = await pythonExecService.getExecutablePath(); expect(executablePath).to.equal(expectedExecutablePath, 'Executable paths are not the same'); }); diff --git a/src/test/configuration/interpreterSelector.test.ts b/src/test/configuration/interpreterSelector.test.ts index 98ea1fd27cc9..9d222f5e1df6 100644 --- a/src/test/configuration/interpreterSelector.test.ts +++ b/src/test/configuration/interpreterSelector.test.ts @@ -5,13 +5,26 @@ import * as assert from 'assert'; import { Container } from 'inversify'; import * as TypeMoq from 'typemoq'; import { IApplicationShell, ICommandManager, IDocumentManager, IWorkspaceService } from '../../client/common/application/types'; -import { IFileSystem } from '../../client/common/platform/types'; +import { Architecture, IFileSystem } from '../../client/common/platform/types'; import { IInterpreterQuickPickItem, InterpreterSelector } from '../../client/interpreter/configuration/interpreterSelector'; import { IInterpreterService, InterpreterType, PythonInterpreter } from '../../client/interpreter/contracts'; import { ServiceContainer } from '../../client/ioc/container'; import { ServiceManager } from '../../client/ioc/serviceManager'; import { IServiceContainer } from '../../client/ioc/types'; +const info: PythonInterpreter = { + architecture: Architecture.Unknown, + companyDisplayName: '', + displayName: '', + envName: '', + path: '', + type: InterpreterType.Unknown, + version: '', + version_info: [0, 0, 0, 'alpha'], + sysPrefix: '', + sysVersion: '' +}; + class InterpreterQuickPickItem implements IInterpreterQuickPickItem { public path: string; public label: string; @@ -73,7 +86,7 @@ suite('Interpreters - selector', () => { { displayName: '2 (virtualenv)', path: 'c:/path2/path2', type: InterpreterType.VirtualEnv }, { displayName: '3', path: 'c:/path2/path2', type: InterpreterType.Unknown }, { displayName: '4', path: 'c:/path4/path4', type: InterpreterType.Conda } - ]; + ].map(item => { return { ...info, ...item }; }); interpreterService .setup(x => x.getInterpreters(TypeMoq.It.isAny())) .returns(() => new Promise((resolve) => resolve(initial))); diff --git a/src/test/format/extension.format.test.ts b/src/test/format/extension.format.test.ts index 503de9c117c2..db99ec32de41 100644 --- a/src/test/format/extension.format.test.ts +++ b/src/test/format/extension.format.test.ts @@ -36,7 +36,7 @@ suite('Formatting', () => { fs.copySync(originalUnformattedFile, file, { overwrite: true }); }); fs.ensureDirSync(path.dirname(autoPep8FileToFormat)); - const pythonProcess = await ioc.serviceContainer.get(IPythonExecutionFactory).create(vscode.Uri.file(workspaceRootPath)); + const pythonProcess = await ioc.serviceContainer.get(IPythonExecutionFactory).create({ resource: vscode.Uri.file(workspaceRootPath) }); const py2 = await ioc.getPythonMajorVersion(vscode.Uri.parse(originalUnformattedFile)) === 2; const yapf = pythonProcess.execModule('yapf', [originalUnformattedFile], { cwd: workspaceRootPath }); const autoPep8 = pythonProcess.execModule('autopep8', [originalUnformattedFile], { cwd: workspaceRootPath }); @@ -113,7 +113,7 @@ suite('Formatting', () => { } test('AutoPep8', async () => testFormatting(new AutoPep8Formatter(ioc.serviceContainer), formattedAutoPep8, autoPep8FileToFormat, 'autopep8.output')); - test('Black', async function() { + test('Black', async function () { if (await ioc.getPythonMajorVersion(vscode.Uri.parse(blackFileToFormat)) === 2) { // tslint:disable-next-line:no-invalid-this return this.skip(); diff --git a/src/test/install/channelManager.channels.test.ts b/src/test/install/channelManager.channels.test.ts index 714f9c65a8f8..3662859ee1fc 100644 --- a/src/test/install/channelManager.channels.test.ts +++ b/src/test/install/channelManager.channels.test.ts @@ -8,12 +8,26 @@ import { QuickPickOptions } from 'vscode'; import { IApplicationShell } from '../../client/common/application/types'; import { InstallationChannelManager } from '../../client/common/installer/channelManager'; import { IModuleInstaller } from '../../client/common/installer/types'; +import { Architecture } from '../../client/common/platform/types'; import { Product } from '../../client/common/types'; import { IInterpreterLocatorService, InterpreterType, PIPENV_SERVICE, PythonInterpreter } from '../../client/interpreter/contracts'; import { ServiceContainer } from '../../client/ioc/container'; import { ServiceManager } from '../../client/ioc/serviceManager'; import { IServiceContainer } from '../../client/ioc/types'; +const info: PythonInterpreter = { + architecture: Architecture.Unknown, + companyDisplayName: '', + displayName: '', + envName: '', + path: '', + type: InterpreterType.Unknown, + version: '', + version_info: [0, 0, 0, 'alpha'], + sysPrefix: '', + sysVersion: '' +}; + // tslint:disable-next-line:max-func-body-length suite('Installation - installation channels', () => { let serviceManager: ServiceManager; @@ -55,6 +69,7 @@ suite('Installation - installation channels', () => { const pipenvInstaller = mockInstaller(true, 'pipenv', 10); const interpreter: PythonInterpreter = { + ...info, path: 'pipenv', type: InterpreterType.VirtualEnv }; diff --git a/src/test/install/channelManager.messages.test.ts b/src/test/install/channelManager.messages.test.ts index 0762b4242014..7d332917a0f1 100644 --- a/src/test/install/channelManager.messages.test.ts +++ b/src/test/install/channelManager.messages.test.ts @@ -6,13 +6,26 @@ import { Container } from 'inversify'; import * as TypeMoq from 'typemoq'; import { IApplicationShell } from '../../client/common/application/types'; import { InstallationChannelManager } from '../../client/common/installer/channelManager'; -import { IPlatformService } from '../../client/common/platform/types'; +import { Architecture, IPlatformService } from '../../client/common/platform/types'; import { Product } from '../../client/common/types'; import { IInterpreterService, InterpreterType, PythonInterpreter } from '../../client/interpreter/contracts'; import { ServiceContainer } from '../../client/ioc/container'; import { ServiceManager } from '../../client/ioc/serviceManager'; import { IServiceContainer } from '../../client/ioc/types'; +const info: PythonInterpreter = { + architecture: Architecture.Unknown, + companyDisplayName: '', + displayName: '', + envName: '', + path: '', + type: InterpreterType.Unknown, + version: '', + version_info: [0, 0, 0, 'alpha'], + sysPrefix: '', + sysVersion: '' +}; + // tslint:disable-next-line:max-func-body-length suite('Installation - channel messages', () => { let serviceContainer: IServiceContainer; @@ -132,6 +145,7 @@ suite('Installation - channel messages', () => { verify: (c: InstallationChannelManager, m: string, u: string) => void): Promise { const activeInterpreter: PythonInterpreter = { + ...info, type: interpreterType, path: '' }; diff --git a/src/test/install/pythonInstallation.test.ts b/src/test/install/pythonInstallation.test.ts index 23e5429c1bb9..b6131cc9c077 100644 --- a/src/test/install/pythonInstallation.test.ts +++ b/src/test/install/pythonInstallation.test.ts @@ -7,15 +7,27 @@ import { Container } from 'inversify'; import * as TypeMoq from 'typemoq'; import { IApplicationShell } from '../../client/common/application/types'; import { PythonInstaller } from '../../client/common/installer/pythonInstallation'; -import { IPlatformService } from '../../client/common/platform/types'; +import { Architecture, IPlatformService } from '../../client/common/platform/types'; import { IPythonSettings } from '../../client/common/types'; -import { IInterpreterLocatorService, IInterpreterService } from '../../client/interpreter/contracts'; -import { InterpreterType, PythonInterpreter } from '../../client/interpreter/contracts'; +import { IInterpreterLocatorService, IInterpreterService, InterpreterType, PythonInterpreter } from '../../client/interpreter/contracts'; import { ServiceContainer } from '../../client/ioc/container'; import { ServiceManager } from '../../client/ioc/serviceManager'; import { IServiceContainer } from '../../client/ioc/types'; import { closeActiveWindows, initialize, initializeTest } from '../initialize'; +const info: PythonInterpreter = { + architecture: Architecture.Unknown, + companyDisplayName: '', + displayName: '', + envName: '', + path: '', + type: InterpreterType.Unknown, + version: '', + version_info: [0, 0, 0, 'alpha'], + sysPrefix: '', + sysVersion: '' +}; + class TestContext { public serviceManager: ServiceManager; public serviceContainer: IServiceContainer; @@ -36,6 +48,7 @@ class TestContext { this.settings = TypeMoq.Mock.ofType(); const activeInterpreter: PythonInterpreter = { + ...info, type: InterpreterType.Unknown, path: '' }; @@ -116,6 +129,7 @@ suite('Installation', () => { c.appShell.setup(x => x.showWarningMessage(TypeMoq.It.isAnyString())).callback(() => called = true); c.settings.setup(x => x.pythonPath).returns(() => 'python'); const interpreter: PythonInterpreter = { + ...info, path: 'python', type: InterpreterType.Unknown }; diff --git a/src/test/interpreters/condaEnvFileService.test.ts b/src/test/interpreters/condaEnvFileService.test.ts index 92783ddf3bc9..062722354c90 100644 --- a/src/test/interpreters/condaEnvFileService.test.ts +++ b/src/test/interpreters/condaEnvFileService.test.ts @@ -4,7 +4,7 @@ import * as path from 'path'; import * as TypeMoq from 'typemoq'; import { IFileSystem } from '../../client/common/platform/types'; import { ILogger, IPersistentStateFactory } from '../../client/common/types'; -import { ICondaService, IInterpreterLocatorService, IInterpreterVersionService, InterpreterType } from '../../client/interpreter/contracts'; +import { ICondaService, IInterpreterHelper, IInterpreterLocatorService, InterpreterType } from '../../client/interpreter/contracts'; import { AnacondaCompanyName, AnacondaCompanyNames, AnacondaDisplayName } from '../../client/interpreter/locators/services/conda'; import { CondaEnvFileService } from '../../client/interpreter/locators/services/condaEnvFileService'; import { IServiceContainer } from '../../client/ioc/types'; @@ -18,7 +18,7 @@ const environmentsFilePath = path.join(environmentsPath, 'environments.txt'); suite('Interpreters from Conda Environments Text File', () => { let logger: TypeMoq.IMock; let condaService: TypeMoq.IMock; - let interpreterVersion: TypeMoq.IMock; + let interpreterHelper: TypeMoq.IMock; let condaFileProvider: IInterpreterLocatorService; let fileSystem: TypeMoq.IMock; suiteSetup(initialize); @@ -31,14 +31,14 @@ suite('Interpreters from Conda Environments Text File', () => { stateFactory.setup(s => s.createGlobalPersistentState(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => state); condaService = TypeMoq.Mock.ofType(); - interpreterVersion = TypeMoq.Mock.ofType(); + interpreterHelper = TypeMoq.Mock.ofType(); fileSystem = TypeMoq.Mock.ofType(); logger = TypeMoq.Mock.ofType(); - condaFileProvider = new CondaEnvFileService(interpreterVersion.object, condaService.object, fileSystem.object, serviceContainer.object, logger.object); + condaFileProvider = new CondaEnvFileService(interpreterHelper.object, condaService.object, fileSystem.object, serviceContainer.object, logger.object); }); test('Must return an empty list if environment file cannot be found', async () => { condaService.setup(c => c.condaEnvironmentsFile).returns(() => undefined); - interpreterVersion.setup(i => i.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('Mock Name')); + interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: 'Mock Name' })); const interpreters = await condaFileProvider.getInterpreters(); assert.equal(interpreters.length, 0, 'Incorrect number of entries'); }); @@ -46,7 +46,7 @@ suite('Interpreters from Conda Environments Text File', () => { condaService.setup(c => c.condaEnvironmentsFile).returns(() => environmentsFilePath); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(environmentsFilePath))).returns(() => Promise.resolve(true)); fileSystem.setup(fs => fs.readFile(TypeMoq.It.isValue(environmentsFilePath))).returns(() => Promise.resolve('')); - interpreterVersion.setup(i => i.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('Mock Name')); + interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: 'Mock Name' })); const interpreters = await condaFileProvider.getInterpreters(); assert.equal(interpreters.length, 0, 'Incorrect number of entries'); }); @@ -81,7 +81,7 @@ suite('Interpreters from Conda Environments Text File', () => { }); fileSystem.setup(fs => fs.readFile(TypeMoq.It.isValue(environmentsFilePath))).returns(() => Promise.resolve(interpreterPaths.join(EOL))); - interpreterVersion.setup(i => i.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('Mock Name')); + interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: 'Mock Name' })); const interpreters = await condaFileProvider.getInterpreters(); @@ -113,7 +113,7 @@ suite('Interpreters from Conda Environments Text File', () => { for (const companyName of AnacondaCompanyNames) { const versionWithCompanyName = `Mock Version :: ${companyName}`; - interpreterVersion.setup(c => c.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(versionWithCompanyName)); + interpreterHelper.setup(c => c.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: versionWithCompanyName })); const interpreters = await condaFileProvider.getInterpreters(); assert.equal(interpreters.length, 1, 'Incorrect number of entries'); diff --git a/src/test/interpreters/condaEnvService.test.ts b/src/test/interpreters/condaEnvService.test.ts index 82da233fb9e9..2523ce3e6a0a 100644 --- a/src/test/interpreters/condaEnvService.test.ts +++ b/src/test/interpreters/condaEnvService.test.ts @@ -3,7 +3,8 @@ import * as path from 'path'; import * as TypeMoq from 'typemoq'; import { IFileSystem } from '../../client/common/platform/types'; import { ILogger, IPersistentStateFactory } from '../../client/common/types'; -import { ICondaService, IInterpreterVersionService, InterpreterType } from '../../client/interpreter/contracts'; +import { ICondaService, InterpreterType } from '../../client/interpreter/contracts'; +import { InterpreterHelper } from '../../client/interpreter/helpers'; import { AnacondaCompanyName, AnacondaDisplayName } from '../../client/interpreter/locators/services/conda'; import { CondaEnvService } from '../../client/interpreter/locators/services/condaEnvService'; import { IServiceContainer } from '../../client/ioc/types'; @@ -14,12 +15,12 @@ import { MockState } from './mocks'; const environmentsPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'environments'); // tslint:disable-next-line:max-func-body-length -suite('Interpreters from Conda Environments', () => { +suite('Interpreters from Conda Environmentsx', () => { let ioc: UnitTestIocContainer; let logger: TypeMoq.IMock; let condaProvider: CondaEnvService; let condaService: TypeMoq.IMock; - let interpreterVersion: TypeMoq.IMock; + let interpreterHelper: TypeMoq.IMock; let fileSystem: TypeMoq.IMock; suiteSetup(initialize); setup(async () => { @@ -32,9 +33,9 @@ suite('Interpreters from Conda Environments', () => { stateFactory.setup(s => s.createGlobalPersistentState(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => state); condaService = TypeMoq.Mock.ofType(); - interpreterVersion = TypeMoq.Mock.ofType(); + interpreterHelper = TypeMoq.Mock.ofType(); fileSystem = TypeMoq.Mock.ofType(); - condaProvider = new CondaEnvService(condaService.object, interpreterVersion.object, logger.object, serviceContainer.object, fileSystem.object); + condaProvider = new CondaEnvService(condaService.object, interpreterHelper.object, logger.object, serviceContainer.object, fileSystem.object); }); teardown(() => ioc.dispose()); function initializeDI() { @@ -65,7 +66,7 @@ suite('Interpreters from Conda Environments', () => { const pythonPath = isWindows ? path.join(validPath, 'python.exe') : path.join(validPath, 'bin', 'python'); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); }); - interpreterVersion.setup(i => i.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((_p, defaultValue) => Promise.resolve(defaultValue)); + interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: '' })); const interpreters = await condaProvider.parseCondaInfo(info); assert.equal(interpreters.length, 2, 'Incorrect number of entries'); @@ -102,7 +103,7 @@ suite('Interpreters from Conda Environments', () => { const pythonPath = isWindows ? path.join(validPath, 'python.exe') : path.join(validPath, 'bin', 'python'); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); }); - interpreterVersion.setup(i => i.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((_p, defaultValue) => Promise.resolve(defaultValue)); + interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: '' })); condaService.setup(c => c.getCondaFile()).returns(() => Promise.resolve('conda')); condaService.setup(c => c.getCondaInfo()).returns(() => Promise.resolve(info)); condaService.setup(c => c.getCondaEnvironments(TypeMoq.It.isAny())).returns(() => Promise.resolve([ @@ -147,7 +148,7 @@ suite('Interpreters from Conda Environments', () => { const pythonPath = isWindows ? path.join(validPath, 'python.exe') : path.join(validPath, 'bin', 'python'); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); }); - interpreterVersion.setup(i => i.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((_p, defaultValue) => Promise.resolve(defaultValue)); + interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: '' })); const interpreters = await condaProvider.parseCondaInfo(info); assert.equal(interpreters.length, 1, 'Incorrect number of entries'); @@ -171,7 +172,7 @@ suite('Interpreters from Conda Environments', () => { default_prefix: '', 'sys.version': '3.6.1 |Anaonda 4.4.0 (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]' }; - interpreterVersion.setup(i => i.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((_p, defaultValue) => Promise.resolve(defaultValue)); + interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: '' })); condaService.setup(c => c.getCondaInfo()).returns(() => Promise.resolve(info)); condaService.setup(c => c.getCondaEnvironments(TypeMoq.It.isAny())).returns(() => Promise.resolve([ { name: 'base', path: environmentsPath }, @@ -185,7 +186,7 @@ suite('Interpreters from Conda Environments', () => { const pythonPath = isWindows ? path.join(validPath, 'python.exe') : path.join(validPath, 'bin', 'python'); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); }); - interpreterVersion.setup(i => i.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((_p, defaultValue) => Promise.resolve(defaultValue)); + interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); fileSystem.setup(fs => fs.arePathsSame(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((p1: string, p2: string) => isWindows ? p1 === p2 : p1.toUpperCase() === p2.toUpperCase()); const interpreters = await condaProvider.getInterpreters(); @@ -215,7 +216,7 @@ suite('Interpreters from Conda Environments', () => { const pythonPath = isWindows ? path.join(validPath, 'python.exe') : path.join(validPath, 'bin', 'python'); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); }); - interpreterVersion.setup(i => i.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((_p, defaultValue) => Promise.resolve(defaultValue)); + interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: '' })); const interpreters = await condaProvider.parseCondaInfo(info); assert.equal(interpreters.length, 1, 'Incorrect number of entries'); @@ -245,7 +246,7 @@ suite('Interpreters from Conda Environments', () => { const pythonPath = isWindows ? path.join(validPath, 'python.exe') : path.join(validPath, 'bin', 'python'); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); }); - interpreterVersion.setup(i => i.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((_p, defaultValue) => Promise.resolve(defaultValue)); + interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: '' })); condaService.setup(c => c.getCondaFile()).returns(() => Promise.resolve('conda')); condaService.setup(c => c.getCondaInfo()).returns(() => Promise.resolve(info)); condaService.setup(c => c.getCondaEnvironments(TypeMoq.It.isAny())).returns(() => Promise.resolve([ @@ -280,7 +281,7 @@ suite('Interpreters from Conda Environments', () => { }); const pythonPath = isWindows ? path.join(info.default_prefix, 'python.exe') : path.join(info.default_prefix, 'bin', 'python'); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); - interpreterVersion.setup(i => i.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((_p, defaultValue) => Promise.resolve(defaultValue)); + interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: '' })); const interpreters = await condaProvider.parseCondaInfo(info); assert.equal(interpreters.length, 1, 'Incorrect number of entries'); @@ -308,7 +309,7 @@ suite('Interpreters from Conda Environments', () => { path.join(environmentsPath, 'path3', 'three.exe')] }; const validPaths = info.envs.filter((_, index) => index % 2 === 0); - interpreterVersion.setup(i => i.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((_p, defaultValue) => Promise.resolve(defaultValue)); + interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: '' })); validPaths.forEach(envPath => { condaService.setup(c => c.getInterpreterPath(TypeMoq.It.isValue(envPath))).returns(environmentPath => { return isWindows ? path.join(environmentPath, 'python.exe') : path.join(environmentPath, 'bin', 'python'); diff --git a/src/test/interpreters/condaService.test.ts b/src/test/interpreters/condaService.test.ts index cbcdcf5642da..10a3db6c6fc0 100644 --- a/src/test/interpreters/condaService.test.ts +++ b/src/test/interpreters/condaService.test.ts @@ -5,7 +5,7 @@ import { EOL } from 'os'; import * as path from 'path'; import * as TypeMoq from 'typemoq'; import { FileSystem } from '../../client/common/platform/fileSystem'; -import { IFileSystem, IPlatformService } from '../../client/common/platform/types'; +import { Architecture, IFileSystem, IPlatformService } from '../../client/common/platform/types'; import { IProcessService, IProcessServiceFactory } from '../../client/common/process/types'; import { ILogger, IPersistentStateFactory } from '../../client/common/types'; import { IInterpreterLocatorService, InterpreterType, PythonInterpreter } from '../../client/interpreter/contracts'; @@ -16,6 +16,18 @@ import { MockState } from './mocks'; const untildify: (value: string) => string = require('untildify'); const environmentsPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'environments'); +const info: PythonInterpreter = { + architecture: Architecture.Unknown, + companyDisplayName: '', + displayName: '', + envName: '', + path: '', + type: InterpreterType.Unknown, + version: '', + version_info: [0, 0, 0, 'alpha'], + sysPrefix: '', + sysVersion: '' +}; suite('Interpreters Conda Service', () => { let processService: TypeMoq.IMock; @@ -256,10 +268,12 @@ suite('Interpreters Conda Service', () => { const condaPythonExePath = path.join('dumyPath', 'environments', 'conda', 'Scripts', 'python.exe'); const registryInterpreters: PythonInterpreter[] = [ { displayName: 'One', path: path.join(environmentsPath, 'path1', 'one.exe'), companyDisplayName: 'One 1', version: '1', type: InterpreterType.Unknown }, - { displayName: 'Anaconda', path: condaPythonExePath, companyDisplayName: 'Two 2', version: '1.11.0', type: InterpreterType.Unknown }, + { displayName: 'Anaconda', path: condaPythonExePath, companyDisplayName: 'Two 2', version: '1.11.0', enyTpe: InterpreterType.Unknown }, { displayName: 'Three', path: path.join(environmentsPath, 'path2', 'one.exe'), companyDisplayName: 'Three 3', version: '2.10.1', type: InterpreterType.Unknown }, { displayName: 'Seven', path: path.join(environmentsPath, 'conda', 'envs', 'numpy'), companyDisplayName: 'Continuum Analytics, Inc.', type: InterpreterType.Unknown } - ]; + ].map(item => { + return { ...info, ...item }; + }); const condaInterpreterIndex = registryInterpreters.findIndex(i => i.displayName === 'Anaconda'); const expectedCodnaPath = path.join(path.dirname(registryInterpreters[condaInterpreterIndex].path), 'conda.exe'); platformService.setup(p => p.isWindows).returns(() => true); @@ -281,7 +295,9 @@ suite('Interpreters Conda Service', () => { { displayName: 'Anaconda', path: path.join(condaPythonExePath, 'conda221', 'Scripts', 'python.exe'), companyDisplayName: 'Two 2.21', version: '2.21.0', type: InterpreterType.Unknown }, { displayName: 'Three', path: path.join(environmentsPath, 'path2', 'one.exe'), companyDisplayName: 'Three 3', version: '2.10.1', type: InterpreterType.Unknown }, { displayName: 'Seven', path: path.join(environmentsPath, 'conda', 'envs', 'numpy'), companyDisplayName: 'Continuum Analytics, Inc.', type: InterpreterType.Unknown } - ]; + ].map(item => { + return { ...info, ...item }; + }); const indexOfLatestVersion = 3; const expectedCodnaPath = path.join(path.dirname(registryInterpreters[indexOfLatestVersion].path), 'conda.exe'); platformService.setup(p => p.isWindows).returns(() => true); @@ -303,7 +319,7 @@ suite('Interpreters Conda Service', () => { { displayName: 'Anaconda', path: path.join(condaPythonExePath, 'conda221', 'Scripts', 'python.exe'), companyDisplayName: 'Two 2.21', version: '2.21.0', type: InterpreterType.Unknown }, { displayName: 'Three', path: path.join(environmentsPath, 'path2', 'one.exe'), companyDisplayName: 'Three 3', version: '2.10.1', type: InterpreterType.Unknown }, { displayName: 'Seven', path: path.join(environmentsPath, 'conda', 'envs', 'numpy'), companyDisplayName: 'Continuum Analytics, Inc.', type: InterpreterType.Unknown } - ]; + ].map(item => { return { ...info, ...item }; }); platformService.setup(p => p.isWindows).returns(() => true); processService.setup(p => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())).returns(() => Promise.reject(new Error('Not Found'))); registryInterpreterLocatorService.setup(r => r.getInterpreters(TypeMoq.It.isAny())).returns(() => Promise.resolve(registryInterpreters)); @@ -375,17 +391,17 @@ suite('Interpreters Conda Service', () => { }); test('Returns condaInfo when conda exists', async () => { - const info = { + const expectedInfo = { envs: [path.join(environmentsPath, 'conda', 'envs', 'numpy'), path.join(environmentsPath, 'conda', 'envs', 'scipy')], default_prefix: '', 'sys.version': '3.6.1 |Anaconda 4.4.0 (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]' }; processService.setup(p => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: 'xyz' })); - processService.setup(p => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['info', '--json']), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: JSON.stringify(info) })); + processService.setup(p => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['info', '--json']), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: JSON.stringify(expectedInfo) })); const condaInfo = await condaService.getCondaInfo(); - assert.deepEqual(condaInfo, info, 'Conda info does not match'); + assert.deepEqual(condaInfo, expectedInfo, 'Conda info does not match'); }); test('Returns undefined if there\'s and error in getting the info', async () => { @@ -464,7 +480,9 @@ suite('Interpreters Conda Service', () => { { displayName: 'Anaconda', path: condaPythonExePath, companyDisplayName: 'Two 2', version: '1.11.0', type: InterpreterType.Unknown }, { displayName: 'Three', path: path.join(environmentsPath, 'path2', 'one.exe'), companyDisplayName: 'Three 3', version: '2.10.1', type: InterpreterType.Unknown }, { displayName: 'Seven', path: path.join(environmentsPath, 'conda', 'envs', 'numpy'), companyDisplayName: 'Continuum Analytics, Inc.', type: InterpreterType.Unknown } - ]; + ].map(item => { + return { ...info, ...item }; + }); const expectedCodaExe = path.join(path.dirname(condaPythonExePath), 'conda.exe'); diff --git a/src/test/interpreters/currentPathService.test.ts b/src/test/interpreters/currentPathService.test.ts index dec8298fb205..87949998b5e6 100644 --- a/src/test/interpreters/currentPathService.test.ts +++ b/src/test/interpreters/currentPathService.test.ts @@ -11,6 +11,7 @@ import { IFileSystem } from '../../client/common/platform/types'; import { IProcessService, IProcessServiceFactory } from '../../client/common/process/types'; import { IConfigurationService, IPersistentState, IPersistentStateFactory, IPythonSettings } from '../../client/common/types'; import { IInterpreterVersionService, InterpreterType, PythonInterpreter } from '../../client/interpreter/contracts'; +import { InterpreterHelper } from '../../client/interpreter/helpers'; import { CurrentPathService } from '../../client/interpreter/locators/services/currentPathService'; import { IVirtualEnvironmentManager } from '../../client/interpreter/virtualEnvs/types'; import { IServiceContainer } from '../../client/ioc/types'; @@ -20,14 +21,14 @@ suite('Interpreters CurrentPath Service', () => { let fileSystem: TypeMoq.IMock; let serviceContainer: TypeMoq.IMock; let virtualEnvironmentManager: TypeMoq.IMock; - let interpreterVersionService: TypeMoq.IMock; + let interpreterHelper: TypeMoq.IMock; let pythonSettings: TypeMoq.IMock; let currentPathService: CurrentPathService; let persistentState: TypeMoq.IMock>; setup(async () => { processService = TypeMoq.Mock.ofType(); virtualEnvironmentManager = TypeMoq.Mock.ofType(); - interpreterVersionService = TypeMoq.Mock.ofType(); + interpreterHelper = TypeMoq.Mock.ofType(); const configurationService = TypeMoq.Mock.ofType(); pythonSettings = TypeMoq.Mock.ofType(); configurationService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); @@ -43,20 +44,21 @@ suite('Interpreters CurrentPath Service', () => { serviceContainer = TypeMoq.Mock.ofType(); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IVirtualEnvironmentManager), TypeMoq.It.isAny())).returns(() => virtualEnvironmentManager.object); - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInterpreterVersionService), TypeMoq.It.isAny())).returns(() => interpreterVersionService.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInterpreterVersionService), TypeMoq.It.isAny())).returns(() => interpreterHelper.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IFileSystem), TypeMoq.It.isAny())).returns(() => fileSystem.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPersistentStateFactory), TypeMoq.It.isAny())).returns(() => persistentStateFactory.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationService), TypeMoq.It.isAny())).returns(() => configurationService.object); - currentPathService = new CurrentPathService(virtualEnvironmentManager.object, interpreterVersionService.object, procServiceFactory.object, serviceContainer.object); + currentPathService = new CurrentPathService(virtualEnvironmentManager.object, interpreterHelper.object, procServiceFactory.object, serviceContainer.object); }); test('Interpreters that do not exist on the file system are not excluded from the list', async () => { // Specific test for 1305 const version = 'mockVersion'; const envName = 'mockEnvName'; - interpreterVersionService.setup(v => v.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(version)); + interpreterHelper.setup(v => v.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version })); virtualEnvironmentManager.setup(v => v.getEnvironmentName(TypeMoq.It.isAny())).returns(() => Promise.resolve(envName)); + virtualEnvironmentManager.setup(v => v.getEnvironmentType(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(InterpreterType.VirtualEnv)); const execArgs = ['-c', 'import sys;print(sys.executable)']; pythonSettings.setup(p => p.pythonPath).returns(() => 'root:Python'); @@ -74,7 +76,7 @@ suite('Interpreters CurrentPath Service', () => { processService.verifyAll(); fileSystem.verifyAll(); expect(interpreters).to.be.of.length(2); - expect(interpreters).to.deep.include({ displayName: `${version} (${envName})`, path: 'c:/root:python', type: InterpreterType.VirtualEnv }); - expect(interpreters).to.deep.include({ displayName: `${version} (${envName})`, path: 'c:/python3', type: InterpreterType.VirtualEnv }); + expect(interpreters).to.deep.include({ version, envName, displayName: `${version} (${envName})`, path: 'c:/root:python', type: InterpreterType.VirtualEnv }); + expect(interpreters).to.deep.include({ version, envName, displayName: `${version} (${envName})`, path: 'c:/python3', type: InterpreterType.VirtualEnv }); }); }); diff --git a/src/test/interpreters/display.test.ts b/src/test/interpreters/display.test.ts index 2d9d212d41a5..b87c1ac7698f 100644 --- a/src/test/interpreters/display.test.ts +++ b/src/test/interpreters/display.test.ts @@ -4,13 +4,26 @@ import * as path from 'path'; import * as TypeMoq from 'typemoq'; import { ConfigurationTarget, Disposable, StatusBarAlignment, StatusBarItem, Uri, WorkspaceFolder } from 'vscode'; import { IApplicationShell, IWorkspaceService } from '../../client/common/application/types'; -import { IFileSystem } from '../../client/common/platform/types'; +import { Architecture, IFileSystem } from '../../client/common/platform/types'; import { IConfigurationService, IDisposableRegistry, IPythonSettings } from '../../client/common/types'; -import { IInterpreterDisplay, IInterpreterHelper, IInterpreterService, IInterpreterVersionService, InterpreterType, PythonInterpreter } from '../../client/interpreter/contracts'; +import { IInterpreterDisplay, IInterpreterHelper, IInterpreterService, InterpreterType, PythonInterpreter } from '../../client/interpreter/contracts'; import { InterpreterDisplay } from '../../client/interpreter/display'; import { IVirtualEnvironmentManager } from '../../client/interpreter/virtualEnvs/types'; import { IServiceContainer } from '../../client/ioc/types'; +const info: PythonInterpreter = { + architecture: Architecture.Unknown, + companyDisplayName: '', + displayName: '', + envName: '', + path: '', + type: InterpreterType.Unknown, + version: '', + version_info: [0, 0, 0, 'alpha'], + sysPrefix: '', + sysVersion: '' +}; + // tslint:disable-next-line:max-func-body-length suite('Interpreters Display', () => { let applicationShell: TypeMoq.IMock; @@ -18,7 +31,6 @@ suite('Interpreters Display', () => { let serviceContainer: TypeMoq.IMock; let interpreterService: TypeMoq.IMock; let virtualEnvMgr: TypeMoq.IMock; - let versionProvider: TypeMoq.IMock; let fileSystem: TypeMoq.IMock; let disposableRegistry: Disposable[]; let statusBar: TypeMoq.IMock; @@ -32,7 +44,6 @@ suite('Interpreters Display', () => { applicationShell = TypeMoq.Mock.ofType(); interpreterService = TypeMoq.Mock.ofType(); virtualEnvMgr = TypeMoq.Mock.ofType(); - versionProvider = TypeMoq.Mock.ofType(); fileSystem = TypeMoq.Mock.ofType(); interpreterHelper = TypeMoq.Mock.ofType(); disposableRegistry = []; @@ -44,7 +55,6 @@ suite('Interpreters Display', () => { serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IApplicationShell))).returns(() => applicationShell.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInterpreterService))).returns(() => interpreterService.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IVirtualEnvironmentManager))).returns(() => virtualEnvMgr.object); - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInterpreterVersionService))).returns(() => versionProvider.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IFileSystem))).returns(() => fileSystem.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IDisposableRegistry))).returns(() => disposableRegistry); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationService))).returns(() => configurationService.object); @@ -72,6 +82,7 @@ suite('Interpreters Display', () => { const resource = Uri.file('x'); const workspaceFolder = Uri.file('workspace'); const activeInterpreter: PythonInterpreter = { + ...info, displayName: 'Dummy_Display_Name', type: InterpreterType.Unknown, path: path.join('user', 'development', 'env', 'bin', 'python') @@ -89,6 +100,7 @@ suite('Interpreters Display', () => { const resource = Uri.file('x'); const workspaceFolder = Uri.file('workspace'); const activeInterpreter: PythonInterpreter = { + ...info, displayName: 'Dummy_Display_Name', type: InterpreterType.Unknown, companyDisplayName: 'Company Name', @@ -114,7 +126,7 @@ suite('Interpreters Display', () => { configurationService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); pythonSettings.setup(p => p.pythonPath).returns(() => pythonPath); virtualEnvMgr.setup(v => v.getEnvironmentName(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve('')); - versionProvider.setup(v => v.getVersion(TypeMoq.It.isValue(pythonPath), TypeMoq.It.isAny())).returns((_path, defaultDisplayName) => Promise.resolve(defaultDisplayName)); + interpreterHelper.setup(v => v.getInterpreterInformation(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(undefined)); await interpreterDisplay.refresh(resource); @@ -132,7 +144,7 @@ suite('Interpreters Display', () => { pythonSettings.setup(p => p.pythonPath).returns(() => pythonPath); // tslint:disable-next-line:no-any virtualEnvMgr.setup(v => v.getEnvironmentName(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve('Mock Name')); - versionProvider.setup(v => v.getVersion(TypeMoq.It.isValue(pythonPath), TypeMoq.It.isAny())).returns((_path, defaultDisplayName) => Promise.resolve(defaultDisplayName)); + interpreterHelper.setup(v => v.getInterpreterInformation(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(undefined)); await interpreterDisplay.refresh(resource); @@ -150,8 +162,7 @@ suite('Interpreters Display', () => { configurationService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); pythonSettings.setup(p => p.pythonPath).returns(() => pythonPath); fileSystem.setup(f => f.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(false)); - const defaultDisplayName = `${path.basename(pythonPath)} [Environment]`; - versionProvider.setup(v => v.getVersion(TypeMoq.It.isValue(pythonPath), TypeMoq.It.isAny())).returns(() => Promise.resolve(defaultDisplayName)); + interpreterHelper.setup(v => v.getInterpreterInformation(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(undefined)); virtualEnvMgr.setup(v => v.getEnvironmentName(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve('')); await interpreterDisplay.refresh(resource); @@ -171,7 +182,7 @@ suite('Interpreters Display', () => { pythonSettings.setup(p => p.pythonPath).returns(() => pythonPath); fileSystem.setup(f => f.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); const defaultDisplayName = `${path.basename(pythonPath)} [Environment]`; - versionProvider.setup(v => v.getVersion(TypeMoq.It.isValue(pythonPath), TypeMoq.It.isAny())).returns(() => Promise.resolve(defaultDisplayName)); + interpreterHelper.setup(v => v.getInterpreterInformation(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(undefined)); // tslint:disable-next-line:no-any virtualEnvMgr.setup(v => v.getEnvironmentName(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve('Mock Env Name')); const expectedText = `${defaultDisplayName} (Mock Env Name)`; @@ -192,7 +203,7 @@ suite('Interpreters Display', () => { pythonSettings.setup(p => p.pythonPath).returns(() => pythonPath); fileSystem.setup(f => f.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); const displayName = 'Version from Interperter'; - versionProvider.setup(v => v.getVersion(TypeMoq.It.isValue(pythonPath), TypeMoq.It.isAny())).returns(() => Promise.resolve(displayName)); + interpreterHelper.setup(v => v.getInterpreterInformation(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve({ version: displayName })); // tslint:disable-next-line:no-any virtualEnvMgr.setup(v => v.getEnvironmentName(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve('')); @@ -204,6 +215,7 @@ suite('Interpreters Display', () => { const workspaceFolder = Uri.file('x'); const resource = workspaceFolder; const activeInterpreter: PythonInterpreter = { + ...info, displayName: 'Dummy_Display_Name', type: InterpreterType.Unknown, companyDisplayName: 'Company Name', @@ -211,6 +223,7 @@ suite('Interpreters Display', () => { }; interpreterService.setup(i => i.getInterpreters(TypeMoq.It.isValue(resource))).returns(() => Promise.resolve([])); interpreterService.setup(i => i.getActiveInterpreter(TypeMoq.It.isValue(resource))).returns(() => Promise.resolve(activeInterpreter)); + interpreterHelper.setup(i => i.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); const expectedTooltip = `${activeInterpreter.path}${EOL}${activeInterpreter.companyDisplayName}`; interpreterHelper.setup(i => i.getActiveWorkspaceUri()).returns(() => { return { folderUri: workspaceFolder, configTarget: ConfigurationTarget.Workspace }; }); diff --git a/src/test/interpreters/helper.test.ts b/src/test/interpreters/helper.test.ts index ecab21922e6c..7ff97624c9d0 100644 --- a/src/test/interpreters/helper.test.ts +++ b/src/test/interpreters/helper.test.ts @@ -28,7 +28,7 @@ suite('Interpreters Display Helper', () => { }); test('getActiveWorkspaceUri should return undefined if there are no workspaces', () => { workspaceService.setup(w => w.workspaceFolders).returns(() => []); - + documentManager.setup(doc => doc.activeTextEditor).returns(() => undefined); const workspace = helper.getActiveWorkspaceUri(); expect(workspace).to.be.equal(undefined, 'incorrect value'); }); diff --git a/src/test/interpreters/interpreterService.test.ts b/src/test/interpreters/interpreterService.test.ts index 7b41dcf45a94..f6033919d9b0 100644 --- a/src/test/interpreters/interpreterService.test.ts +++ b/src/test/interpreters/interpreterService.test.ts @@ -9,7 +9,7 @@ import * as TypeMoq from 'typemoq'; import { ConfigurationTarget, Disposable, TextDocument, TextEditor, Uri, WorkspaceConfiguration } from 'vscode'; import { IDocumentManager, IWorkspaceService } from '../../client/common/application/types'; import { noop } from '../../client/common/core.utils'; -import { IFileSystem } from '../../client/common/platform/types'; +import { Architecture, IFileSystem } from '../../client/common/platform/types'; import { IConfigurationService, IDisposableRegistry } from '../../client/common/types'; import { IPythonPathUpdaterServiceManager } from '../../client/interpreter/configuration/types'; import { @@ -27,6 +27,19 @@ import { InterpreterService } from '../../client/interpreter/interpreterService' import { ServiceContainer } from '../../client/ioc/container'; import { ServiceManager } from '../../client/ioc/serviceManager'; +const info: PythonInterpreter = { + architecture: Architecture.Unknown, + companyDisplayName: '', + displayName: '', + envName: '', + path: '', + type: InterpreterType.Unknown, + version: '', + version_info: [0, 0, 0, 'alpha'], + sysPrefix: '', + sysVersion: '' +}; + // tslint:disable-next-line:max-func-body-length suite('Interpreters service', () => { let serviceManager: ServiceManager; @@ -85,6 +98,7 @@ suite('Interpreters service', () => { return { key: 'python' }; }); const interpreter: PythonInterpreter = { + ...info, path: path.join(path.sep, 'folder', 'py1', 'bin', 'python.exe'), type: InterpreterType.Unknown }; @@ -107,6 +121,7 @@ suite('Interpreters service', () => { return { key: 'python', workspaceValue: 'python' }; }); const interpreter: PythonInterpreter = { + ...info, path: 'python', type: InterpreterType.VirtualEnv }; @@ -120,6 +135,7 @@ suite('Interpreters service', () => { return { key: 'python', workspaceValue: 'elsewhere' }; }); const interpreter: PythonInterpreter = { + ...info, path: 'elsewhere', type: InterpreterType.Unknown }; @@ -135,6 +151,7 @@ suite('Interpreters service', () => { }); const intPath = path.join(path.sep, 'root', 'under', 'bin', 'python.exe'); const interpreter: PythonInterpreter = { + ...info, path: intPath, type: InterpreterType.Unknown }; diff --git a/src/test/interpreters/pipEnvService.test.ts b/src/test/interpreters/pipEnvService.test.ts index b32b6c4c74a3..39763455f3a6 100644 --- a/src/test/interpreters/pipEnvService.test.ts +++ b/src/test/interpreters/pipEnvService.test.ts @@ -15,7 +15,7 @@ import { IFileSystem } from '../../client/common/platform/types'; import { IProcessService, IProcessServiceFactory } from '../../client/common/process/types'; import { ICurrentProcess, IPersistentState, IPersistentStateFactory } from '../../client/common/types'; import { IEnvironmentVariablesProvider } from '../../client/common/variables/types'; -import { IInterpreterLocatorService, IInterpreterVersionService } from '../../client/interpreter/contracts'; +import { IInterpreterHelper, IInterpreterLocatorService } from '../../client/interpreter/contracts'; import { PipEnvService } from '../../client/interpreter/locators/services/pipEnvService'; import { IServiceContainer } from '../../client/ioc/types'; @@ -31,7 +31,7 @@ suite('Interpreters - PipEnv', () => { let pipEnvService: IInterpreterLocatorService; let serviceContainer: TypeMoq.IMock; - let interpreterVersionService: TypeMoq.IMock; + let interpreterHelper: TypeMoq.IMock; let processService: TypeMoq.IMock; let currentProcess: TypeMoq.IMock; let fileSystem: TypeMoq.IMock; @@ -42,7 +42,7 @@ suite('Interpreters - PipEnv', () => { setup(() => { serviceContainer = TypeMoq.Mock.ofType(); const workspaceService = TypeMoq.Mock.ofType(); - interpreterVersionService = TypeMoq.Mock.ofType(); + interpreterHelper = TypeMoq.Mock.ofType(); fileSystem = TypeMoq.Mock.ofType(); processService = TypeMoq.Mock.ofType(); appShell = TypeMoq.Mock.ofType(); @@ -67,7 +67,7 @@ suite('Interpreters - PipEnv', () => { serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IProcessServiceFactory), TypeMoq.It.isAny())).returns(() => procServiceFactory.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IWorkspaceService))).returns(() => workspaceService.object); - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInterpreterVersionService))).returns(() => interpreterVersionService.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInterpreterHelper))).returns(() => interpreterHelper.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ICurrentProcess))).returns(() => currentProcess.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IFileSystem))).returns(() => fileSystem.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IApplicationShell))).returns(() => appShell.object); @@ -116,12 +116,13 @@ suite('Interpreters - PipEnv', () => { }); test(`Should return interpreter information${testSuffix}`, async () => { const env = {}; - const venvDir = 'one'; + const pythonPath = 'one'; + envVarsProvider.setup(e => e.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})).verifiable(TypeMoq.Times.once()); currentProcess.setup(c => c.env).returns(() => env); - processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: venvDir })); - interpreterVersionService.setup(v => v.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('xyz')); + processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: pythonPath })); + interpreterHelper.setup(v => v.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: 'xyz' })); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(true)).verifiable(); - fileSystem.setup(fs => fs.directoryExists(TypeMoq.It.isValue(venvDir))).returns(() => Promise.resolve(true)).verifiable(); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)).verifiable(); const environments = await pipEnvService.getInterpreters(resource); expect(environments).to.be.lengthOf(1); @@ -132,13 +133,14 @@ suite('Interpreters - PipEnv', () => { const env = { PIPENV_PIPFILE: envPipFile }; - const venvDir = 'one'; + const pythonPath = 'one'; + envVarsProvider.setup(e => e.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})).verifiable(TypeMoq.Times.once()); currentProcess.setup(c => c.env).returns(() => env); - processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: venvDir })); - interpreterVersionService.setup(v => v.getVersion(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('xyz')); + processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: pythonPath })); + interpreterHelper.setup(v => v.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: 'xyz' })); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(false)).verifiable(TypeMoq.Times.never()); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(path.join(rootWorkspace, envPipFile)))).returns(() => Promise.resolve(true)).verifiable(TypeMoq.Times.once()); - fileSystem.setup(fs => fs.directoryExists(TypeMoq.It.isValue(venvDir))).returns(() => Promise.resolve(true)).verifiable(); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)).verifiable(); const environments = await pipEnvService.getInterpreters(resource); expect(environments).to.be.lengthOf(1); diff --git a/src/test/interpreters/virtualEnvManager.test.ts b/src/test/interpreters/virtualEnvManager.test.ts index 4db50c4ac344..de16e18c03bd 100644 --- a/src/test/interpreters/virtualEnvManager.test.ts +++ b/src/test/interpreters/virtualEnvManager.test.ts @@ -6,9 +6,14 @@ import { expect } from 'chai'; import { Container } from 'inversify'; import * as TypeMoq from 'typemoq'; +import { IWorkspaceService } from '../../client/common/application/types'; +import { FileSystem } from '../../client/common/platform/fileSystem'; +import { PlatformService } from '../../client/common/platform/platformService'; +import { IFileSystem, IPlatformService } from '../../client/common/platform/types'; import { BufferDecoder } from '../../client/common/process/decoder'; import { ProcessService } from '../../client/common/process/proc'; import { IBufferDecoder, IProcessService, IProcessServiceFactory } from '../../client/common/process/types'; +import { IPipEnvService } from '../../client/interpreter/contracts'; import { VirtualEnvironmentManager } from '../../client/interpreter/virtualEnvs'; import { ServiceContainer } from '../../client/ioc/container'; import { ServiceManager } from '../../client/ioc/serviceManager'; @@ -33,6 +38,10 @@ suite('Virtual environment manager', () => { processServiceFactory.setup(f => f.create(TypeMoq.It.isAny())).returns(() => Promise.resolve(new ProcessService(new BufferDecoder(), process.env as any))); serviceManager.addSingletonInstance(IProcessServiceFactory, processServiceFactory.object); serviceManager.addSingleton(IBufferDecoder, BufferDecoder); + serviceManager.addSingleton(IFileSystem, FileSystem); + serviceManager.addSingleton(IPlatformService, PlatformService); + serviceManager.addSingletonInstance(IPipEnvService, TypeMoq.Mock.ofType().object); + serviceManager.addSingletonInstance(IWorkspaceService, TypeMoq.Mock.ofType().object); const venvManager = new VirtualEnvironmentManager(serviceContainer); const name = await venvManager.getEnvironmentName(PYTHON_PATH); const result = name === '' || name === 'venv' || name === 'virtualenv'; @@ -45,6 +54,9 @@ suite('Virtual environment manager', () => { processService.setup((x: any) => x.then).returns(() => undefined); processServiceFactory.setup(f => f.create(TypeMoq.It.isAny())).returns(() => Promise.resolve(processService.object)); serviceManager.addSingletonInstance(IProcessServiceFactory, processServiceFactory.object); + serviceManager.addSingletonInstance(IFileSystem, TypeMoq.Mock.ofType().object); + serviceManager.addSingletonInstance(IPipEnvService, TypeMoq.Mock.ofType().object); + serviceManager.addSingletonInstance(IWorkspaceService, TypeMoq.Mock.ofType().object); const venvManager = new VirtualEnvironmentManager(serviceContainer); processService diff --git a/src/test/interpreters/windowsRegistryService.test.ts b/src/test/interpreters/windowsRegistryService.test.ts index eba551ea4b50..c06563cd0c8d 100644 --- a/src/test/interpreters/windowsRegistryService.test.ts +++ b/src/test/interpreters/windowsRegistryService.test.ts @@ -4,6 +4,7 @@ import * as TypeMoq from 'typemoq'; import { Architecture, RegistryHive } from '../../client/common/platform/types'; import { IPersistentStateFactory } from '../../client/common/types'; import { IS_WINDOWS } from '../../client/debugger/Common/Utils'; +import { IInterpreterHelper } from '../../client/interpreter/contracts'; import { WindowsRegistryService } from '../../client/interpreter/locators/services/windowsRegistryService'; import { IServiceContainer } from '../../client/ioc/types'; import { initialize, initializeTest } from '../initialize'; @@ -18,8 +19,12 @@ suite('Interpreters from Windows Registry', () => { setup(() => { serviceContainer = TypeMoq.Mock.ofType(); const stateFactory = TypeMoq.Mock.ofType(); + const interpreterHelper = TypeMoq.Mock.ofType(); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPersistentStateFactory))).returns(() => stateFactory.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInterpreterHelper))).returns(() => interpreterHelper.object); const state = new MockState(undefined); + // tslint:disable-next-line:no-empty no-any + interpreterHelper.setup(h => h.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({} as any)); stateFactory.setup(s => s.createGlobalPersistentState(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => state); return initializeTest(); }); @@ -182,7 +187,7 @@ suite('Interpreters from Windows Registry', () => { { key: '\\Software\\Python', hive: RegistryHive.HKLM, arch: Architecture.x86, values: ['A'] }, { key: '\\Software\\Python\\Company A', hive: RegistryHive.HKLM, arch: Architecture.x86, values: ['Another Tag'] } ]; - const registryValues: { key: string, hive: RegistryHive, arch?: Architecture, value: string, name?: string }[] = [ + const registryValues: { key: string; hive: RegistryHive; arch?: Architecture; value: string; name?: string }[] = [ { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Display Name for Company One', name: 'DisplayName' }, { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy', 'python.exe'), name: 'ExecutablePath' }, @@ -241,7 +246,7 @@ suite('Interpreters from Windows Registry', () => { { key: '\\Software\\Python', hive: RegistryHive.HKLM, arch: Architecture.x86, values: ['A'] }, { key: '\\Software\\Python\\Company A', hive: RegistryHive.HKLM, arch: Architecture.x86, values: ['Another Tag'] } ]; - const registryValues: { key: string, hive: RegistryHive, arch?: Architecture, value: string, name?: string }[] = [ + const registryValues: { key: string; hive: RegistryHive; arch?: Architecture; value: string; name?: string }[] = [ { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Display Name for Company One', name: 'DisplayName' }, { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy', 'python.exe'), name: 'ExecutablePath' }, diff --git a/src/test/unittests/serviceRegistry.ts b/src/test/unittests/serviceRegistry.ts index f3a6d21d2315..88b73407a1d1 100644 --- a/src/test/unittests/serviceRegistry.ts +++ b/src/test/unittests/serviceRegistry.ts @@ -32,7 +32,7 @@ export class UnitTestIocContainer extends IocContainer { super(); } public getPythonMajorVersion(resource: Uri) { - return this.serviceContainer.get(IPythonExecutionFactory).create(resource) + return this.serviceContainer.get(IPythonExecutionFactory).create({ resource }) .then(pythonProcess => pythonProcess.exec(['-c', 'import sys;print(sys.version_info[0])'], {})) .then(output => parseInt(output.stdout.trim(), 10)); } From 87b8fe56958715208970b6dea3a29e35d09e2420 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 10 May 2018 23:52:54 -0400 Subject: [PATCH 244/433] Ensure none of the npm packages rely on native dependencies (#1655) * Check native depedencies during build and pre-commit * Ensure none of the npm packages use native dependencies --- gulpfile.js | 25 +++++++++++++++++++++++++ news/3 Code Health/1416.md | 1 + package.json | 4 +++- yarn.lock | 30 +++++++++++++++++++++++++++++- 4 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 news/3 Code Health/1416.md diff --git a/gulpfile.js b/gulpfile.js index 753af8c46292..5f28dc0ca69e 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -26,6 +26,8 @@ const istanbul = require('istanbul'); const glob = require('glob'); const os = require('os'); const _ = require('lodash'); +const nativeDependencyChecker = require('node-has-native-dependencies'); +const flat = require('flat'); /** * Hygiene works by creating cascading subsets of all our files and @@ -97,6 +99,12 @@ gulp.task('cover:clean', () => del(['coverage', 'debug_coverage*'])); gulp.task('clean:ptvsd', () => del(['coverage', 'pythonFiles/experimental/ptvsd*'])); +gulp.task('checkNativeDependencies', () => { + if (hasNativeDependencies()) { + throw new Error('Native dependencies deteced'); + } +}); + gulp.task('cover:enable', () => { return gulp.src("./coverconfig.json") .pipe(jeditor((json) => { @@ -115,6 +123,23 @@ gulp.task('cover:disable', () => { .pipe(gulp.dest("./out", { 'overwrite': true })); }); +function hasNativeDependencies() { + let nativeDependencies = nativeDependencyChecker.check(path.join(__dirname, 'node_modules')); + if (!Array.isArray(nativeDependencies) || nativeDependencies.length === 0) { + return false; + } + const dependencies = JSON.parse(cp.spawnSync('npm', ['ls', '--json', '--prod']).stdout.toString()); + const jsonProperties = Object.keys(flat.flatten(dependencies)); + nativeDependencies = _.flatMap(nativeDependencies, item => path.dirname(item.substring(item.indexOf('node_modules') + 'node_modules'.length)).split(path.sep)) + .filter(item => item.length > 0) + .filter(item => jsonProperties.findIndex(flattenedDependency => flattenedDependency.endsWith(`dependencies.${item}.version`)) >= 0); + if (nativeDependencies.length > 0) { + console.error('Native dependencies detected', nativeDependencies); + return true; + } + return false; +} + function buildDebugAdapterCoverage() { const matches = glob.sync(path.join(__dirname, 'debug_coverage*/coverage.json')); matches.forEach(coverageFile => { diff --git a/news/3 Code Health/1416.md b/news/3 Code Health/1416.md new file mode 100644 index 000000000000..52747061cad1 --- /dev/null +++ b/news/3 Code Health/1416.md @@ -0,0 +1 @@ +Ensure none of the npm packages (used by the extension) rely on native dependencies. diff --git a/package.json b/package.json index b80589c88802..4e2db3383084 100644 --- a/package.json +++ b/package.json @@ -1847,7 +1847,7 @@ ] }, "scripts": { - "vscode:prepublish": "tsc -p ./", + "vscode:prepublish": "gulp checkNativeDependencies && tsc -p ./", "compile": "tsc -watch -p ./", "postinstall": "node ./node_modules/vscode/bin/install", "test": "node ./out/test/standardTest.js && node ./out/test/multiRootTest.js", @@ -1930,6 +1930,7 @@ "decache": "^4.4.0", "del": "^3.0.0", "event-stream": "^3.3.4", + "flat": "^4.0.0", "gulp": "^3.9.1", "gulp-debounced-watch": "^1.0.4", "gulp-filter": "^5.1.0", @@ -1942,6 +1943,7 @@ "is-running": "^2.1.0", "istanbul": "^0.4.5", "mocha": "^5.0.4", + "node-has-native-dependencies": "^1.0.2", "relative": "^3.0.2", "remap-istanbul": "^0.10.1", "retyped-diff-match-patch-tsd-ambient": "^1.0.0-0", diff --git a/yarn.lock b/yarn.lock index 4f8867ffeff1..d003b9ad9505 100644 --- a/yarn.lock +++ b/yarn.lock @@ -388,6 +388,12 @@ async-each@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" +async@*: + version "2.6.0" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4" + dependencies: + lodash "^4.14.0" + async@1.x, async@^1.4.0: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" @@ -1381,6 +1387,12 @@ flagged-respawn@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-1.0.0.tgz#4e79ae9b2eb38bf86b3bb56bf3e0a56aa5fcabd7" +flat@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/flat/-/flat-4.0.0.tgz#3abc7f3b588e64ce77dc42fd59aa35806622fea8" + dependencies: + is-buffer "~1.1.5" + flush-write-stream@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.2.tgz#c81b90d8746766f1a609a46809946c45dd8ae417" @@ -1453,6 +1465,12 @@ fs-mkdirp-stream@^1.0.0: graceful-fs "^4.1.11" through2 "^2.0.3" +fs-walk@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/fs-walk/-/fs-walk-0.0.1.tgz#f7fc91c3ae1eead07c998bc5d0dd41f2dbebd335" + dependencies: + async "*" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -2173,7 +2191,7 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" -is-buffer@^1.1.5, is-buffer@~1.1.1: +is-buffer@^1.1.5, is-buffer@~1.1.1, is-buffer@~1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" @@ -2851,6 +2869,10 @@ lodash@4.17.5, lodash@^4.17.4: version "4.17.5" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" +lodash@^4.14.0: + version "4.17.10" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" + lodash@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/lodash/-/lodash-1.0.2.tgz#8f57560c83b59fc270bd3d561b690043430e2551" @@ -3152,6 +3174,12 @@ nise@^1.2.0: path-to-regexp "^1.7.0" text-encoding "^0.6.4" +node-has-native-dependencies@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/node-has-native-dependencies/-/node-has-native-dependencies-1.0.2.tgz#3152ec9753b6641e4d322d185dd4930649ada3da" + dependencies: + fs-walk "0.0.1" + node-pre-gyp@^0.6.39: version "0.6.39" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649" From 70f093fa4e19c4a424a7dbcebb466ee125cde5c8 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 11 May 2018 00:00:10 -0400 Subject: [PATCH 245/433] Prefix display name with 'Python' only if not already prefixed (#1652) * Prefix display name with Python only if not already prefixed * Interpreter display names should be prefixed with python only once * Fix compiler issue --- news/2 Fixes/1651.md | 1 + src/client/interpreter/locators/helpers.ts | 3 ++- src/test/interpreters/helper.test.ts | 28 ++++++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 news/2 Fixes/1651.md diff --git a/news/2 Fixes/1651.md b/news/2 Fixes/1651.md new file mode 100644 index 000000000000..fa562bc618e2 --- /dev/null +++ b/news/2 Fixes/1651.md @@ -0,0 +1 @@ +Ensure the display name of an interpreter does not get prefixed twice with the words `Python`. diff --git a/src/client/interpreter/locators/helpers.ts b/src/client/interpreter/locators/helpers.ts index 1b5cf698fcbe..b5605e946384 100644 --- a/src/client/interpreter/locators/helpers.ts +++ b/src/client/interpreter/locators/helpers.ts @@ -18,7 +18,8 @@ export function fixInterpreterDisplayName(item: PythonInterpreter) { if (!item.displayName) { const arch = getArchitectureDislayName(item.architecture); const version = typeof item.version === 'string' ? item.version : ''; - item.displayName = ['Python', version, arch].filter(namePart => namePart.length > 0).join(' ').trim(); + const prefix = version.toUpperCase().startsWith('PYTHON') ? '' : 'Python'; + item.displayName = [prefix, version, arch].filter(namePart => namePart.length > 0).join(' ').trim(); } return item; } diff --git a/src/test/interpreters/helper.test.ts b/src/test/interpreters/helper.test.ts index 7ff97624c9d0..cca633519d2d 100644 --- a/src/test/interpreters/helper.test.ts +++ b/src/test/interpreters/helper.test.ts @@ -7,7 +7,10 @@ import { expect } from 'chai'; import * as TypeMoq from 'typemoq'; import { ConfigurationTarget, TextDocument, TextEditor, Uri } from 'vscode'; import { IDocumentManager, IWorkspaceService } from '../../client/common/application/types'; +import { Architecture } from '../../client/common/platform/types'; +import { InterpreterType, PythonInterpreter } from '../../client/interpreter/contracts'; import { InterpreterHelper } from '../../client/interpreter/helpers'; +import { fixInterpreterDisplayName } from '../../client/interpreter/locators/helpers'; import { IServiceContainer } from '../../client/ioc/types'; // tslint:disable-next-line:max-func-body-length @@ -85,4 +88,29 @@ suite('Interpreters Display Helper', () => { expect(workspace!.folderUri).to.be.equal(documentWorkspaceFolderUri); expect(workspace!.configTarget).to.be.equal(ConfigurationTarget.WorkspaceFolder); }); + test('Ensure Python prefix is added to displayName', () => { + const interpreter: PythonInterpreter = { + path: '', + type: InterpreterType.Unknown, + version: 'Something', + sysPrefix: '', + architecture: Architecture.Unknown, + sysVersion: '', + version_info: [0, 0, 0, 'alpha'] + }; + const expectedDisplayName = `Python ${interpreter.version!}`; + expect(fixInterpreterDisplayName(interpreter)).to.have.property('displayName', expectedDisplayName); + }); + test('Ensure Python prefix is not added to displayName', () => { + const interpreter: PythonInterpreter = { + path: '', + type: InterpreterType.Unknown, + version: 'Python Something', + sysPrefix: '', + architecture: Architecture.Unknown, + sysVersion: '', + version_info: [0, 0, 0, 'alpha'] + }; + expect(fixInterpreterDisplayName(interpreter)).to.have.property('displayName', interpreter.version); + }); }); From 51569fa917736c411cc3ba75e0ed1be26b87f5c3 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 11 May 2018 00:02:21 -0400 Subject: [PATCH 246/433] Telemetry to capture the type of terminal being used (#1650) * :hammer: split out different types of bash shells * Capture type of terminal on startup --- src/client/common/enumUtils.ts | 13 +++++++------ .../environmentActivationProviders/bash.ts | 12 +++++++++++- src/client/common/terminal/helper.ts | 14 ++++++++++++-- src/client/common/terminal/types.ts | 19 ++++++++++++------- src/client/extension.ts | 5 ++++- src/client/telemetry/types.ts | 8 +++++--- .../common/terminals/activation.bash.test.ts | 10 ++++++++++ .../common/terminals/activation.conda.test.ts | 4 ++-- src/test/common/terminals/helper.test.ts | 9 +++++---- 9 files changed, 68 insertions(+), 26 deletions(-) diff --git a/src/client/common/enumUtils.ts b/src/client/common/enumUtils.ts index 59736d799f80..a2f1a9b833e1 100644 --- a/src/client/common/enumUtils.ts +++ b/src/client/common/enumUtils.ts @@ -1,14 +1,15 @@ +// tslint:disable:no-any no-unnecessary-class export class EnumEx { - static getNamesAndValues(e: any) { - return EnumEx.getNames(e).map(n => ({ name: n, value: e[n] as T })); + public static getNamesAndValues(e: any): { name: string; value: T }[] { + return EnumEx.getNames(e).map(n => ({ name: n, value: e[n] })); } - static getNames(e: any) { - return EnumEx.getObjValues(e).filter(v => typeof v === "string") as string[]; + public static getNames(e: any) { + return EnumEx.getObjValues(e).filter(v => typeof v === 'string') as string[]; } - static getValues(e: any) { - return EnumEx.getObjValues(e).filter(v => typeof v === "number") as T[]; + public static getValues(e: any) { + return EnumEx.getObjValues(e).filter(v => typeof v === 'number') as any as T[]; } private static getObjValues(e: any): (number | string)[] { diff --git a/src/client/common/terminal/environmentActivationProviders/bash.ts b/src/client/common/terminal/environmentActivationProviders/bash.ts index afa26f153ccd..3a1780df539f 100644 --- a/src/client/common/terminal/environmentActivationProviders/bash.ts +++ b/src/client/common/terminal/environmentActivationProviders/bash.ts @@ -10,12 +10,17 @@ import { BaseActivationCommandProvider } from './baseActivationProvider'; @injectable() export class Bash extends BaseActivationCommandProvider { - constructor( @inject(IServiceContainer) serviceContainer: IServiceContainer) { + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { super(serviceContainer); } public isShellSupported(targetShell: TerminalShellType): boolean { return targetShell === TerminalShellType.bash || + targetShell === TerminalShellType.gitbash || + targetShell === TerminalShellType.wsl || + targetShell === TerminalShellType.ksh || + targetShell === TerminalShellType.zsh || targetShell === TerminalShellType.cshell || + targetShell === TerminalShellType.tcshell || targetShell === TerminalShellType.fish; } public async getActivationCommands(resource: Uri | undefined, targetShell: TerminalShellType): Promise { @@ -28,9 +33,14 @@ export class Bash extends BaseActivationCommandProvider { private getScriptsInOrderOfPreference(targetShell: TerminalShellType): string[] { switch (targetShell) { + case TerminalShellType.wsl: + case TerminalShellType.ksh: + case TerminalShellType.zsh: + case TerminalShellType.gitbash: case TerminalShellType.bash: { return ['activate.sh', 'activate']; } + case TerminalShellType.tcshell: case TerminalShellType.cshell: { return ['activate.csh']; } diff --git a/src/client/common/terminal/helper.ts b/src/client/common/terminal/helper.ts index bc2f9c78a237..af0f50484d38 100644 --- a/src/client/common/terminal/helper.ts +++ b/src/client/common/terminal/helper.ts @@ -14,23 +14,33 @@ import { ITerminalActivationCommandProvider, ITerminalHelper, TerminalShellType // Types of shells can be found here: // 1. https://wiki.ubuntu.com/ChangingShells -const IS_BASH = /(bash.exe$|wsl.exe$|bash$|zsh$|ksh$)/i; +const IS_GITBASH = /(gitbash.exe$)/i; +const IS_BASH = /(bash.exe$|bash$)/i; +const IS_WSL = /(wsl.exe$)/i; +const IS_ZSH = /(zsh$)/i; +const IS_KSH = /(ksh$)/i; const IS_COMMAND = /cmd.exe$/i; const IS_POWERSHELL = /(powershell.exe$|powershell$)/i; const IS_POWERSHELL_CORE = /(pwsh.exe$|pwsh$)/i; const IS_FISH = /(fish$)/i; const IS_CSHELL = /(csh$)/i; +const IS_TCSHELL = /(tcsh$)/i; @injectable() export class TerminalHelper implements ITerminalHelper { private readonly detectableShells: Map; - constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer) { + constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { this.detectableShells = new Map(); this.detectableShells.set(TerminalShellType.powershell, IS_POWERSHELL); + this.detectableShells.set(TerminalShellType.gitbash, IS_GITBASH); this.detectableShells.set(TerminalShellType.bash, IS_BASH); + this.detectableShells.set(TerminalShellType.wsl, IS_WSL); + this.detectableShells.set(TerminalShellType.zsh, IS_ZSH); + this.detectableShells.set(TerminalShellType.ksh, IS_KSH); this.detectableShells.set(TerminalShellType.commandPrompt, IS_COMMAND); this.detectableShells.set(TerminalShellType.fish, IS_FISH); + this.detectableShells.set(TerminalShellType.tcshell, IS_TCSHELL); this.detectableShells.set(TerminalShellType.cshell, IS_CSHELL); this.detectableShells.set(TerminalShellType.powershellCore, IS_POWERSHELL_CORE); } diff --git a/src/client/common/terminal/types.ts b/src/client/common/terminal/types.ts index 56cdb305c077..a13569d6094f 100644 --- a/src/client/common/terminal/types.ts +++ b/src/client/common/terminal/types.ts @@ -5,13 +5,18 @@ import { Event, Terminal, Uri } from 'vscode'; export enum TerminalShellType { - powershell = 1, - powershellCore = 2, - commandPrompt = 3, - bash = 4, - fish = 5, - cshell = 6, - other = 7 + powershell = 'powershell', + powershellCore = 'powershellCore', + commandPrompt = 'commandPrompt', + gitbash = 'gitbash', + bash = 'bash', + zsh = 'zsh', + ksh = 'ksh', + fish = 'fish', + cshell = 'cshell', + tcshell = 'tshell', + wsl = 'wsl', + other = 'other' } export interface ITerminalService { diff --git a/src/client/extension.ts b/src/client/extension.ts index 723e471c0761..70499bd31280 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -24,6 +24,7 @@ import { registerTypes as platformRegisterTypes } from './common/platform/servic import { registerTypes as processRegisterTypes } from './common/process/serviceRegistry'; import { registerTypes as commonRegisterTypes } from './common/serviceRegistry'; import { StopWatch } from './common/stopWatch'; +import { ITerminalHelper } from './common/terminal/types'; import { GLOBAL_MEMENTO, IConfigurationService, IDisposableRegistry, ILogger, IMemento, IOutputChannel, IPersistentStateFactory, WORKSPACE_MEMENTO } from './common/types'; import { registerTypes as variableRegisterTypes } from './common/variables/serviceRegistry'; import { AttachRequestArguments, LaunchRequestArguments } from './debugger/Common/Contracts'; @@ -186,10 +187,12 @@ async function sendStartupTelemetry(activatedPromise: Promise, serviceCont const logger = serviceContainer.get(ILogger); try { await activatedPromise; + const terminalHelper = serviceContainer.get(ITerminalHelper); + const terminalShellType = terminalHelper.identifyTerminalShell(terminalHelper.getTerminalShellPath()); const duration = stopWatch.elapsedTime; const condaLocator = serviceContainer.get(ICondaService); const condaVersion = await condaLocator.getCondaVersion().catch(() => undefined); - const props = condaVersion ? { condaVersion } : undefined; + const props = { condaVersion, terminal: terminalShellType }; sendTelemetryEvent(EDITOR_LOAD, duration, props); } catch (ex) { logger.logError('sendStartupTelemetry failed.', ex); diff --git a/src/client/telemetry/types.ts b/src/client/telemetry/types.ts index 818f892b00c4..50d017ee82fd 100644 --- a/src/client/telemetry/types.ts +++ b/src/client/telemetry/types.ts @@ -1,10 +1,12 @@ -import { LinterId } from '../linters/types'; - // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { TerminalShellType } from '../common/terminal/types'; +import { LinterId } from '../linters/types'; + export type EditorLoadTelemetry = { - condaVersion: string; + condaVersion: string | undefined; + terminal: TerminalShellType; }; export type FormatTelemetry = { tool: 'autopep8' | 'black' | 'yapf'; diff --git a/src/test/common/terminals/activation.bash.test.ts b/src/test/common/terminals/activation.bash.test.ts index 88f677f5dc21..a859f79cbbfc 100644 --- a/src/test/common/terminals/activation.bash.test.ts +++ b/src/test/common/terminals/activation.bash.test.ts @@ -37,6 +37,10 @@ suite('Terminal Environment Activation (bash)', () => { EnumEx.getNamesAndValues(TerminalShellType).forEach(shellType => { let isScriptFileSupported = false; switch (shellType.value) { + case TerminalShellType.zsh: + case TerminalShellType.ksh: + case TerminalShellType.wsl: + case TerminalShellType.gitbash: case TerminalShellType.bash: { isScriptFileSupported = ['activate', 'activate.sh'].indexOf(scriptFileName) >= 0; break; @@ -45,6 +49,7 @@ suite('Terminal Environment Activation (bash)', () => { isScriptFileSupported = ['activate.fish'].indexOf(scriptFileName) >= 0; break; } + case TerminalShellType.tcshell: case TerminalShellType.cshell: { isScriptFileSupported = ['activate.csh'].indexOf(scriptFileName) >= 0; break; @@ -61,7 +66,12 @@ suite('Terminal Environment Activation (bash)', () => { const supported = bash.isShellSupported(shellType.value); switch (shellType.value) { + case TerminalShellType.wsl: + case TerminalShellType.zsh: + case TerminalShellType.ksh: case TerminalShellType.bash: + case TerminalShellType.gitbash: + case TerminalShellType.tcshell: case TerminalShellType.cshell: case TerminalShellType.fish: { expect(supported).to.be.equal(true, `${shellType.name} shell not supported (it should be)`); diff --git a/src/test/common/terminals/activation.conda.test.ts b/src/test/common/terminals/activation.conda.test.ts index 87d10d7c2b38..671f896e3be0 100644 --- a/src/test/common/terminals/activation.conda.test.ts +++ b/src/test/common/terminals/activation.conda.test.ts @@ -104,7 +104,7 @@ suite('Terminal Environment Activation conda', () => { } expect(activationCommands).to.deep.equal(expectedActivationCommamnd, 'Incorrect Activation command'); } - EnumEx.getNamesAndValues(TerminalShellType).forEach(shellType => { + EnumEx.getNamesAndValues(TerminalShellType).forEach(shellType => { test(`Conda activation command for shell ${shellType.name} on (windows)`, async () => { const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'enva', 'python.exe'); await expectNoCondaActivationCommandForPowershell(true, false, false, pythonPath, shellType.value); @@ -120,7 +120,7 @@ suite('Terminal Environment Activation conda', () => { await expectNoCondaActivationCommandForPowershell(false, true, false, pythonPath, shellType.value); }); }); - EnumEx.getNamesAndValues(TerminalShellType).forEach(shellType => { + EnumEx.getNamesAndValues(TerminalShellType).forEach(shellType => { test(`Conda activation command for shell ${shellType.name} on (windows), containing spaces in environment name`, async () => { const pythonPath = path.join('c', 'users', 'xyz', '.conda', 'envs', 'enva', 'python.exe'); await expectNoCondaActivationCommandForPowershell(true, false, false, pythonPath, shellType.value, true); diff --git a/src/test/common/terminals/helper.test.ts b/src/test/common/terminals/helper.test.ts index e692f480a6ca..2008ca545a78 100644 --- a/src/test/common/terminals/helper.test.ts +++ b/src/test/common/terminals/helper.test.ts @@ -48,11 +48,11 @@ suite('Terminal Service helpers', () => { shellPathsAndIdentification.set('c:\\windows\\system32\\cmd.exe', TerminalShellType.commandPrompt); shellPathsAndIdentification.set('c:\\windows\\system32\\bash.exe', TerminalShellType.bash); - shellPathsAndIdentification.set('c:\\windows\\system32\\wsl.exe', TerminalShellType.bash); - shellPathsAndIdentification.set('c:\\windows\\system32\\gitbash.exe', TerminalShellType.bash); + shellPathsAndIdentification.set('c:\\windows\\system32\\wsl.exe', TerminalShellType.wsl); + shellPathsAndIdentification.set('c:\\windows\\system32\\gitbash.exe', TerminalShellType.gitbash); shellPathsAndIdentification.set('/usr/bin/bash', TerminalShellType.bash); - shellPathsAndIdentification.set('/usr/bin/zsh', TerminalShellType.bash); - shellPathsAndIdentification.set('/usr/bin/ksh', TerminalShellType.bash); + shellPathsAndIdentification.set('/usr/bin/zsh', TerminalShellType.zsh); + shellPathsAndIdentification.set('/usr/bin/ksh', TerminalShellType.ksh); shellPathsAndIdentification.set('c:\\windows\\system32\\powershell.exe', TerminalShellType.powershell); shellPathsAndIdentification.set('c:\\windows\\system32\\pwsh.exe', TerminalShellType.powershellCore); @@ -65,6 +65,7 @@ suite('Terminal Service helpers', () => { shellPathsAndIdentification.set('/usr/bin/shell', TerminalShellType.other); shellPathsAndIdentification.set('/usr/bin/csh', TerminalShellType.cshell); + shellPathsAndIdentification.set('/usr/bin/tcsh', TerminalShellType.tcshell); shellPathsAndIdentification.forEach((shellType, shellPath) => { expect(helper.identifyTerminalShell(shellPath)).to.equal(shellType, `Incorrect Shell Type for path '${shellPath}'`); From 85ceeef1d529cbc4a67c118b24a547215d40ea20 Mon Sep 17 00:00:00 2001 From: Larry Li Date: Mon, 14 May 2018 14:47:49 -0400 Subject: [PATCH 247/433] Fix setup script in contributing doc (#1684) * Fix setup script in contributing documentaion * Add entry file * Fix script error * Address pull request * Updated to highlight keyword * Address comment * thanks myself --- CONTRIBUTING.md | 2 +- news/3 Code Health/1682.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 news/3 Code Health/1682.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 67aa7cdce4fe..e85d0d1e1529 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,7 +22,7 @@ ```shell git clone https://github.com/microsoft/vscode-python cd vscode-python -yarn install +yarn install --lock-file ``` You may see warnings that ```The engine "vscode" appears to be invalid.```, you can ignore these. diff --git a/news/3 Code Health/1682.md b/news/3 Code Health/1682.md new file mode 100644 index 000000000000..603813eaeb57 --- /dev/null +++ b/news/3 Code Health/1682.md @@ -0,0 +1,2 @@ +Change yarn install script to include the keyword `--lock-file` +(thanks [Lingyu Li](https://github.com/lingyv-li/)) \ No newline at end of file From c61d312aac3a8d6d8cecd2de6f367448afda3749 Mon Sep 17 00:00:00 2001 From: Waleed Sehgal Date: Mon, 14 May 2018 15:28:04 -0400 Subject: [PATCH 248/433] constraints.txt highlighting (#1685) * adds highlighting * adds contribution entry * moved from news to 3 Code Health * changed constraints syntax to constraints.txt highlighting * Changed news entry * Add syntax highlighting to constraints.txt --- news/3 Code Health/1053.md | 2 ++ package.json | 3 +++ 2 files changed, 5 insertions(+) create mode 100644 news/3 Code Health/1053.md diff --git a/news/3 Code Health/1053.md b/news/3 Code Health/1053.md new file mode 100644 index 000000000000..0d281defa4c0 --- /dev/null +++ b/news/3 Code Health/1053.md @@ -0,0 +1,2 @@ +Add syntax highlighting to constraints.txt file to match that of piprequirements files +(thanks [Waleed Sehgal](https://github.com/waleedsehgal)) diff --git a/package.json b/package.json index 4e2db3383084..0bb0d18f2cef 100644 --- a/package.json +++ b/package.json @@ -1783,11 +1783,14 @@ ], "filenames": [ "requirements.txt", + "constraints.txt", "requirements.in" ], "filenamePatterns": [ "*-requirements.txt", "requirements-*.txt", + "constraints-*.txt", + "*-constraints.txt", "*-requirements.in", "requirements-*.in" ], From d3335447903bc0d03b2da5706e2c978f053b988b Mon Sep 17 00:00:00 2001 From: Derek Keeler Date: Tue, 15 May 2018 10:34:55 -0400 Subject: [PATCH 249/433] Upgrade Typscript to v2.8.3 (#1686) * Upgrade Typescript to 2.8.3 --- news/3 Code Health/1604.md | 1 + package.json | 2 +- yarn.lock | 6 +++--- 3 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 news/3 Code Health/1604.md diff --git a/news/3 Code Health/1604.md b/news/3 Code Health/1604.md new file mode 100644 index 000000000000..4f693181c6df --- /dev/null +++ b/news/3 Code Health/1604.md @@ -0,0 +1 @@ +- Update typescript package to 2.8.3 diff --git a/package.json b/package.json index 0bb0d18f2cef..ec57caec2beb 100644 --- a/package.json +++ b/package.json @@ -1956,7 +1956,7 @@ "tslint-eslint-rules": "^5.1.0", "tslint-microsoft-contrib": "^5.0.3", "typemoq": "^2.1.0", - "typescript": "^2.7.2", + "typescript": "2.8.3", "typescript-formatter": "^7.1.0", "vscode": "^1.1.5", "vscode-debugadapter-testsupport": "^1.27.0" diff --git a/yarn.lock b/yarn.lock index d003b9ad9505..5ea193766243 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4596,9 +4596,9 @@ typescript-formatter@^7.1.0: commandpost "^1.0.0" editorconfig "^0.15.0" -typescript@^2.7.2: - version "2.7.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.7.2.tgz#2d615a1ef4aee4f574425cdff7026edf81919836" +typescript@2.8.3: + version "2.8.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.8.3.tgz#5d817f9b6f31bb871835f4edf0089f21abe6c170" uglify-js@^2.6: version "2.8.29" From 1695b328142a587a6ed3a0aa102fa0d145063805 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 21 May 2018 10:17:22 -0700 Subject: [PATCH 250/433] Ensure resource is passed into getTerminalService method (#1691) * Ensure resource is passed into getTerminalService method * Use relative paths for prospector * Fixes #1476 --- news/2 Fixes/1476.md | 1 + .../common/installer/moduleInstaller.ts | 2 +- .../common/installer/pipEnvInstaller.ts | 4 +- .../common/installer/productInstaller.ts | 2 +- src/client/linters/prospector.ts | 5 +- src/test/common/moduleInstaller.test.ts | 444 +++++++++--------- 6 files changed, 239 insertions(+), 219 deletions(-) create mode 100644 news/2 Fixes/1476.md diff --git a/news/2 Fixes/1476.md b/news/2 Fixes/1476.md new file mode 100644 index 000000000000..071cddec5a2f --- /dev/null +++ b/news/2 Fixes/1476.md @@ -0,0 +1 @@ +Ensure python environment activation works as expected within a multi-root workspace. diff --git a/src/client/common/installer/moduleInstaller.ts b/src/client/common/installer/moduleInstaller.ts index f69401faa9ff..5fe19952bba9 100644 --- a/src/client/common/installer/moduleInstaller.ts +++ b/src/client/common/installer/moduleInstaller.ts @@ -21,7 +21,7 @@ export abstract class ModuleInstaller { constructor(protected serviceContainer: IServiceContainer) { } public async installModule(name: string, resource?: vscode.Uri): Promise { const executionInfo = await this.getExecutionInfo(name, resource); - const terminalService = this.serviceContainer.get(ITerminalServiceFactory).getTerminalService(); + const terminalService = this.serviceContainer.get(ITerminalServiceFactory).getTerminalService(resource); if (executionInfo.moduleName) { const settings = PythonSettings.getInstance(resource); diff --git a/src/client/common/installer/pipEnvInstaller.ts b/src/client/common/installer/pipEnvInstaller.ts index 23ac3e52ab95..4b01df9fd3e2 100644 --- a/src/client/common/installer/pipEnvInstaller.ts +++ b/src/client/common/installer/pipEnvInstaller.ts @@ -25,8 +25,8 @@ export class PipEnvInstaller implements IModuleInstaller { this.pipenv = this.serviceContainer.get(IInterpreterLocatorService, PIPENV_SERVICE); } - public installModule(name: string): Promise { - const terminalService = this.serviceContainer.get(ITerminalServiceFactory).getTerminalService(); + public installModule(name: string, resource?: Uri): Promise { + const terminalService = this.serviceContainer.get(ITerminalServiceFactory).getTerminalService(resource); return terminalService.sendCommand(pipenvName, ['install', name, '--dev']); } diff --git a/src/client/common/installer/productInstaller.ts b/src/client/common/installer/productInstaller.ts index 6c56fdc4da7d..9659ef0debad 100644 --- a/src/client/common/installer/productInstaller.ts +++ b/src/client/common/installer/productInstaller.ts @@ -108,7 +108,7 @@ class CTagsInstaller extends BaseInstaller { this.outputChannel.appendLine('Option 3: Extract to any folder and define that path in the python.workspaceSymbols.ctagsPath setting of your user settings file (settings.json).'); this.outputChannel.show(); } else { - const terminalService = this.serviceContainer.get(ITerminalServiceFactory).getTerminalService(); + const terminalService = this.serviceContainer.get(ITerminalServiceFactory).getTerminalService(resource); const logger = this.serviceContainer.get(ILogger); terminalService.sendCommand(CTagsInsllationScript, []) .catch(logger.logError.bind(logger, `Failed to install ctags. Script sent '${CTagsInsllationScript}'.`)); diff --git a/src/client/linters/prospector.ts b/src/client/linters/prospector.ts index 5642c5433848..0bd9873d5b8a 100644 --- a/src/client/linters/prospector.ts +++ b/src/client/linters/prospector.ts @@ -1,3 +1,4 @@ +import * as path from 'path'; import { CancellationToken, OutputChannel, TextDocument } from 'vscode'; import '../common/extensions'; import { Product } from '../common/types'; @@ -28,7 +29,9 @@ export class Prospector extends BaseLinter { } protected async runLinter(document: TextDocument, cancellation: CancellationToken): Promise { - return this.run(['--absolute-paths', '--output-format=json', document.uri.fsPath], document, cancellation); + const cwd = this.getWorkspaceRootPath(document); + const relativePath = path.relative(cwd, document.uri.fsPath); + return this.run(['--absolute-paths', '--output-format=json', relativePath], document, cancellation); } protected async parseMessages(output: string, document: TextDocument, token: CancellationToken, regEx: string) { let parsedData: IProspectorResponse; diff --git a/src/test/common/moduleInstaller.test.ts b/src/test/common/moduleInstaller.test.ts index 3ee2379fcc7e..27b57e2ed519 100644 --- a/src/test/common/moduleInstaller.test.ts +++ b/src/test/common/moduleInstaller.test.ts @@ -1,3 +1,5 @@ +// tslint:disable:max-func-body-length + import { expect } from 'chai'; import * as path from 'path'; import * as TypeMoq from 'typemoq'; @@ -40,244 +42,258 @@ const info: PythonInterpreter = { sysVersion: '' }; -// tslint:disable-next-line:max-func-body-length -suite('Module Installer', () => { - let ioc: UnitTestIocContainer; - let mockTerminalService: TypeMoq.IMock; - let condaService: TypeMoq.IMock; - let interpreterService: TypeMoq.IMock; - - const workspaceUri = Uri.file(path.join(__dirname, '..', '..', '..', 'src', 'test')); - suiteSetup(initializeTest); - setup(async () => { - initializeDI(); - await initializeTest(); - await resetSettings(); - }); - suiteTeardown(async () => { - await closeActiveWindows(); - await resetSettings(); - }); - teardown(async () => { - ioc.dispose(); - await closeActiveWindows(); - }); +suite('Module Installerx', () => { + [undefined, Uri.file(__filename)].forEach(resource => { + let ioc: UnitTestIocContainer; + let mockTerminalService: TypeMoq.IMock; + let condaService: TypeMoq.IMock; + let interpreterService: TypeMoq.IMock; + let mockTerminalFactory: TypeMoq.IMock; + + const workspaceUri = Uri.file(path.join(__dirname, '..', '..', '..', 'src', 'test')); + suiteSetup(initializeTest); + setup(async () => { + initializeDI(); + await initializeTest(); + await resetSettings(); + }); + suiteTeardown(async () => { + await closeActiveWindows(); + await resetSettings(); + }); + teardown(async () => { + ioc.dispose(); + await closeActiveWindows(); + }); - function initializeDI() { - ioc = new UnitTestIocContainer(); - ioc.registerUnitTestTypes(); - ioc.registerVariableTypes(); - ioc.registerLinterTypes(); - ioc.registerFormatterTypes(); - - ioc.serviceManager.addSingleton(IPersistentStateFactory, PersistentStateFactory); - ioc.serviceManager.addSingleton(ILogger, Logger); - ioc.serviceManager.addSingleton(IInstaller, ProductInstaller); - - mockTerminalService = TypeMoq.Mock.ofType(); - const mockTerminalFactory = TypeMoq.Mock.ofType(); - mockTerminalFactory.setup(t => t.getTerminalService(TypeMoq.It.isAny())).returns(() => mockTerminalService.object); - ioc.serviceManager.addSingletonInstance(ITerminalServiceFactory, mockTerminalFactory.object); - - ioc.serviceManager.addSingleton(IModuleInstaller, PipInstaller); - ioc.serviceManager.addSingleton(IModuleInstaller, CondaInstaller); - ioc.serviceManager.addSingleton(IModuleInstaller, PipEnvInstaller); - condaService = TypeMoq.Mock.ofType(); - ioc.serviceManager.addSingletonInstance(ICondaService, condaService.object); - - interpreterService = TypeMoq.Mock.ofType(); - ioc.serviceManager.addSingletonInstance(IInterpreterService, interpreterService.object); - - ioc.serviceManager.addSingleton(IPathUtils, PathUtils); - ioc.serviceManager.addSingleton(ICurrentProcess, CurrentProcess); - ioc.serviceManager.addSingleton(IFileSystem, FileSystem); - ioc.serviceManager.addSingleton(IPlatformService, PlatformService); - ioc.serviceManager.addSingleton(IConfigurationService, ConfigurationService); - - ioc.registerMockProcessTypes(); - ioc.serviceManager.addSingletonInstance(IsWindows, false); - } - async function resetSettings(): Promise { - const configService = ioc.serviceManager.get(IConfigurationService); - await configService.updateSettingAsync('linting.pylintEnabled', true, rootWorkspaceUri, ConfigurationTarget.Workspace); - } - async function getCurrentPythonPath(): Promise { - const pythonPath = PythonSettings.getInstance(workspaceUri).pythonPath; - if (path.basename(pythonPath) === pythonPath) { - const pythonProc = await ioc.serviceContainer.get(IPythonExecutionFactory).create({ resource: workspaceUri }); - return pythonProc.getExecutablePath().catch(() => pythonPath); - } else { - return pythonPath; + function initializeDI() { + ioc = new UnitTestIocContainer(); + ioc.registerUnitTestTypes(); + ioc.registerVariableTypes(); + ioc.registerLinterTypes(); + ioc.registerFormatterTypes(); + + ioc.serviceManager.addSingleton(IPersistentStateFactory, PersistentStateFactory); + ioc.serviceManager.addSingleton(ILogger, Logger); + ioc.serviceManager.addSingleton(IInstaller, ProductInstaller); + + mockTerminalService = TypeMoq.Mock.ofType(); + mockTerminalFactory = TypeMoq.Mock.ofType(); + mockTerminalFactory.setup(t => t.getTerminalService(TypeMoq.It.isValue(resource))) + .returns(() => mockTerminalService.object) + .verifiable(TypeMoq.Times.atLeastOnce()); + // If resource is provided, then ensure we do not invoke without the resource. + mockTerminalFactory.setup(t => t.getTerminalService(TypeMoq.It.isAny())) + .callback(passedInResource => expect(passedInResource).to.be.equal(resource)) + .returns(() => mockTerminalService.object); + ioc.serviceManager.addSingletonInstance(ITerminalServiceFactory, mockTerminalFactory.object); + + ioc.serviceManager.addSingleton(IModuleInstaller, PipInstaller); + ioc.serviceManager.addSingleton(IModuleInstaller, CondaInstaller); + ioc.serviceManager.addSingleton(IModuleInstaller, PipEnvInstaller); + condaService = TypeMoq.Mock.ofType(); + ioc.serviceManager.addSingletonInstance(ICondaService, condaService.object); + + interpreterService = TypeMoq.Mock.ofType(); + ioc.serviceManager.addSingletonInstance(IInterpreterService, interpreterService.object); + + ioc.serviceManager.addSingleton(IPathUtils, PathUtils); + ioc.serviceManager.addSingleton(ICurrentProcess, CurrentProcess); + ioc.serviceManager.addSingleton(IFileSystem, FileSystem); + ioc.serviceManager.addSingleton(IPlatformService, PlatformService); + ioc.serviceManager.addSingleton(IConfigurationService, ConfigurationService); + + ioc.registerMockProcessTypes(); + ioc.serviceManager.addSingletonInstance(IsWindows, false); } - } - test('Ensure pip is supported and conda is not', async () => { - ioc.serviceManager.addSingletonInstance(IModuleInstaller, new MockModuleInstaller('mock', true)); - const mockInterpreterLocator = TypeMoq.Mock.ofType(); - mockInterpreterLocator.setup(p => p.getInterpreters(TypeMoq.It.isAny())).returns(() => Promise.resolve([])); - ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, mockInterpreterLocator.object, INTERPRETER_LOCATOR_SERVICE); - ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, TypeMoq.Mock.ofType().object, PIPENV_SERVICE); - - const processService = await ioc.serviceContainer.get(IProcessServiceFactory).create() as MockProcessService; - processService.onExec((file, args, options, callback) => { - if (args.length > 1 && args[0] === '-c' && args[1] === 'import pip') { - callback({ stdout: '' }); - } - if (args.length > 0 && args[0] === '--version' && file === 'conda') { - callback({ stdout: '', stderr: 'not available' }); + async function resetSettings(): Promise { + const configService = ioc.serviceManager.get(IConfigurationService); + await configService.updateSettingAsync('linting.pylintEnabled', true, rootWorkspaceUri, ConfigurationTarget.Workspace); + } + async function getCurrentPythonPath(): Promise { + const pythonPath = PythonSettings.getInstance(workspaceUri).pythonPath; + if (path.basename(pythonPath) === pythonPath) { + const pythonProc = await ioc.serviceContainer.get(IPythonExecutionFactory).create({ resource: workspaceUri }); + return pythonProc.getExecutablePath().catch(() => pythonPath); + } else { + return pythonPath; } - }); - const moduleInstallers = ioc.serviceContainer.getAll(IModuleInstaller); - expect(moduleInstallers).length(4, 'Incorrect number of installers'); + } + test('Ensure pip is supported and conda is not', async () => { + ioc.serviceManager.addSingletonInstance(IModuleInstaller, new MockModuleInstaller('mock', true)); + const mockInterpreterLocator = TypeMoq.Mock.ofType(); + mockInterpreterLocator.setup(p => p.getInterpreters(TypeMoq.It.isAny())).returns(() => Promise.resolve([])); + ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, mockInterpreterLocator.object, INTERPRETER_LOCATOR_SERVICE); + ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, TypeMoq.Mock.ofType().object, PIPENV_SERVICE); + + const processService = await ioc.serviceContainer.get(IProcessServiceFactory).create() as MockProcessService; + processService.onExec((file, args, options, callback) => { + if (args.length > 1 && args[0] === '-c' && args[1] === 'import pip') { + callback({ stdout: '' }); + } + if (args.length > 0 && args[0] === '--version' && file === 'conda') { + callback({ stdout: '', stderr: 'not available' }); + } + }); + const moduleInstallers = ioc.serviceContainer.getAll(IModuleInstaller); + expect(moduleInstallers).length(4, 'Incorrect number of installers'); - const pipInstaller = moduleInstallers.find(item => item.displayName === 'Pip')!; - expect(pipInstaller).not.to.be.an('undefined', 'Pip installer not found'); - await expect(pipInstaller.isSupported()).to.eventually.equal(true, 'Pip is not supported'); + const pipInstaller = moduleInstallers.find(item => item.displayName === 'Pip')!; + expect(pipInstaller).not.to.be.an('undefined', 'Pip installer not found'); + await expect(pipInstaller.isSupported()).to.eventually.equal(true, 'Pip is not supported'); - const condaInstaller = moduleInstallers.find(item => item.displayName === 'Conda')!; - expect(condaInstaller).not.to.be.an('undefined', 'Conda installer not found'); - await expect(condaInstaller.isSupported()).to.eventually.equal(false, 'Conda is supported'); + const condaInstaller = moduleInstallers.find(item => item.displayName === 'Conda')!; + expect(condaInstaller).not.to.be.an('undefined', 'Conda installer not found'); + await expect(condaInstaller.isSupported()).to.eventually.equal(false, 'Conda is supported'); - const mockInstaller = moduleInstallers.find(item => item.displayName === 'mock')!; - expect(mockInstaller).not.to.be.an('undefined', 'mock installer not found'); - await expect(mockInstaller.isSupported()).to.eventually.equal(true, 'mock is not supported'); - }); + const mockInstaller = moduleInstallers.find(item => item.displayName === 'mock')!; + expect(mockInstaller).not.to.be.an('undefined', 'mock installer not found'); + await expect(mockInstaller.isSupported()).to.eventually.equal(true, 'mock is not supported'); + }); - test('Ensure pip is supported', async () => { - ioc.serviceManager.addSingletonInstance(IModuleInstaller, new MockModuleInstaller('mock', true)); - const pythonPath = await getCurrentPythonPath(); - const mockInterpreterLocator = TypeMoq.Mock.ofType(); - mockInterpreterLocator.setup(p => p.getInterpreters(TypeMoq.It.isAny())).returns(() => Promise.resolve([{ ...info, architecture: Architecture.Unknown, companyDisplayName: '', displayName: '', envName: '', path: pythonPath, type: InterpreterType.Conda, version: '' }])); - ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, mockInterpreterLocator.object, INTERPRETER_LOCATOR_SERVICE); - ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, TypeMoq.Mock.ofType().object, PIPENV_SERVICE); - - const processService = await ioc.serviceContainer.get(IProcessServiceFactory).create() as MockProcessService; - processService.onExec((file, args, options, callback) => { - if (args.length > 1 && args[0] === '-c' && args[1] === 'import pip') { - callback({ stdout: '' }); - } - if (args.length > 0 && args[0] === '--version' && file === 'conda') { - callback({ stdout: '' }); - } + test('Ensure pip is supported', async () => { + ioc.serviceManager.addSingletonInstance(IModuleInstaller, new MockModuleInstaller('mock', true)); + const pythonPath = await getCurrentPythonPath(); + const mockInterpreterLocator = TypeMoq.Mock.ofType(); + mockInterpreterLocator.setup(p => p.getInterpreters(TypeMoq.It.isAny())).returns(() => Promise.resolve([{ ...info, architecture: Architecture.Unknown, companyDisplayName: '', displayName: '', envName: '', path: pythonPath, type: InterpreterType.Conda, version: '' }])); + ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, mockInterpreterLocator.object, INTERPRETER_LOCATOR_SERVICE); + ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, TypeMoq.Mock.ofType().object, PIPENV_SERVICE); + + const processService = await ioc.serviceContainer.get(IProcessServiceFactory).create() as MockProcessService; + processService.onExec((file, args, options, callback) => { + if (args.length > 1 && args[0] === '-c' && args[1] === 'import pip') { + callback({ stdout: '' }); + } + if (args.length > 0 && args[0] === '--version' && file === 'conda') { + callback({ stdout: '' }); + } + }); + const moduleInstallers = ioc.serviceContainer.getAll(IModuleInstaller); + expect(moduleInstallers).length(4, 'Incorrect number of installers'); + + const pipInstaller = moduleInstallers.find(item => item.displayName === 'Pip')!; + expect(pipInstaller).not.to.be.an('undefined', 'Pip installer not found'); + await expect(pipInstaller.isSupported()).to.eventually.equal(true, 'Pip is not supported'); + }); + test('Ensure conda is supported', async () => { + const serviceContainer = TypeMoq.Mock.ofType(); + + const configService = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationService))).returns(() => configService.object); + const settings = TypeMoq.Mock.ofType(); + const pythonPath = 'pythonABC'; + settings.setup(s => s.pythonPath).returns(() => pythonPath); + configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ICondaService))).returns(() => condaService.object); + condaService.setup(c => c.isCondaAvailable()).returns(() => Promise.resolve(true)); + condaService.setup(c => c.isCondaEnvironment(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); + + const condaInstaller = new CondaInstaller(serviceContainer.object); + await expect(condaInstaller.isSupported()).to.eventually.equal(true, 'Conda is not supported'); + }); + test('Ensure conda is not supported even if conda is available', async () => { + const serviceContainer = TypeMoq.Mock.ofType(); + + const configService = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationService))).returns(() => configService.object); + const settings = TypeMoq.Mock.ofType(); + const pythonPath = 'pythonABC'; + settings.setup(s => s.pythonPath).returns(() => pythonPath); + configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ICondaService))).returns(() => condaService.object); + condaService.setup(c => c.isCondaAvailable()).returns(() => Promise.resolve(true)); + condaService.setup(c => c.isCondaEnvironment(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(false)); + + const condaInstaller = new CondaInstaller(serviceContainer.object); + await expect(condaInstaller.isSupported()).to.eventually.equal(false, 'Conda should not be supported'); }); - const moduleInstallers = ioc.serviceContainer.getAll(IModuleInstaller); - expect(moduleInstallers).length(4, 'Incorrect number of installers'); - const pipInstaller = moduleInstallers.find(item => item.displayName === 'Pip')!; - expect(pipInstaller).not.to.be.an('undefined', 'Pip installer not found'); - await expect(pipInstaller.isSupported()).to.eventually.equal(true, 'Pip is not supported'); - }); - test('Ensure conda is supported', async () => { - const serviceContainer = TypeMoq.Mock.ofType(); - - const configService = TypeMoq.Mock.ofType(); - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationService))).returns(() => configService.object); - const settings = TypeMoq.Mock.ofType(); - const pythonPath = 'pythonABC'; - settings.setup(s => s.pythonPath).returns(() => pythonPath); - configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ICondaService))).returns(() => condaService.object); - condaService.setup(c => c.isCondaAvailable()).returns(() => Promise.resolve(true)); - condaService.setup(c => c.isCondaEnvironment(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)); - - const condaInstaller = new CondaInstaller(serviceContainer.object); - await expect(condaInstaller.isSupported()).to.eventually.equal(true, 'Conda is not supported'); - }); - test('Ensure conda is not supported even if conda is available', async () => { - const serviceContainer = TypeMoq.Mock.ofType(); - - const configService = TypeMoq.Mock.ofType(); - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationService))).returns(() => configService.object); - const settings = TypeMoq.Mock.ofType(); - const pythonPath = 'pythonABC'; - settings.setup(s => s.pythonPath).returns(() => pythonPath); - configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ICondaService))).returns(() => condaService.object); - condaService.setup(c => c.isCondaAvailable()).returns(() => Promise.resolve(true)); - condaService.setup(c => c.isCondaEnvironment(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(false)); - - const condaInstaller = new CondaInstaller(serviceContainer.object); - await expect(condaInstaller.isSupported()).to.eventually.equal(false, 'Conda should not be supported'); - }); + const resourceTestNameSuffix = resource ? ' with a resource' : ' without a resource'; + test(`Validate pip install arguments ${resourceTestNameSuffix}`, async () => { + const interpreterPath = await getCurrentPythonPath(); + const mockInterpreterLocator = TypeMoq.Mock.ofType(); + mockInterpreterLocator.setup(p => p.getInterpreters(TypeMoq.It.isAny())).returns(() => Promise.resolve([{ ...info, path: interpreterPath, type: InterpreterType.Unknown }])); + ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, mockInterpreterLocator.object, INTERPRETER_LOCATOR_SERVICE); + ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, TypeMoq.Mock.ofType().object, PIPENV_SERVICE); - test('Validate pip install arguments', async () => { - const interpreterPath = await getCurrentPythonPath(); - const mockInterpreterLocator = TypeMoq.Mock.ofType(); - mockInterpreterLocator.setup(p => p.getInterpreters(TypeMoq.It.isAny())).returns(() => Promise.resolve([{ ...info, path: interpreterPath, type: InterpreterType.Unknown }])); - ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, mockInterpreterLocator.object, INTERPRETER_LOCATOR_SERVICE); - ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, TypeMoq.Mock.ofType().object, PIPENV_SERVICE); + const interpreter: PythonInterpreter = { + ...info, + type: InterpreterType.Unknown, + path: PYTHON_PATH + }; + interpreterService.setup(x => x.getActiveInterpreter(TypeMoq.It.isAny())).returns(() => Promise.resolve(interpreter)); - const interpreter: PythonInterpreter = { - ...info, - type: InterpreterType.Unknown, - path: PYTHON_PATH - }; - interpreterService.setup(x => x.getActiveInterpreter(TypeMoq.It.isAny())).returns(() => Promise.resolve(interpreter)); + const moduleName = 'xyz'; - const moduleName = 'xyz'; + const moduleInstallers = ioc.serviceContainer.getAll(IModuleInstaller); + const pipInstaller = moduleInstallers.find(item => item.displayName === 'Pip')!; - const moduleInstallers = ioc.serviceContainer.getAll(IModuleInstaller); - const pipInstaller = moduleInstallers.find(item => item.displayName === 'Pip')!; + expect(pipInstaller).not.to.be.an('undefined', 'Pip installer not found'); - expect(pipInstaller).not.to.be.an('undefined', 'Pip installer not found'); + let argsSent: string[] = []; + mockTerminalService + .setup(t => t.sendCommand(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())) + .returns((cmd: string, args: string[]) => { argsSent = args; return Promise.resolve(void 0); }); + // tslint:disable-next-line:no-any + interpreterService.setup(i => i.getActiveInterpreter(TypeMoq.It.isAny())).returns(() => Promise.resolve({ type: InterpreterType.Unknown } as any)); - let argsSent: string[] = []; - mockTerminalService - .setup(t => t.sendCommand(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())) - .returns((cmd: string, args: string[]) => { argsSent = args; return Promise.resolve(void 0); }); - // tslint:disable-next-line:no-any - interpreterService.setup(i => i.getActiveInterpreter(TypeMoq.It.isAny())).returns(() => Promise.resolve({ type: InterpreterType.Unknown } as any)); - await pipInstaller.installModule(moduleName); + await pipInstaller.installModule(moduleName, resource); - expect(argsSent.join(' ')).equal(`-m pip install -U ${moduleName} --user`, 'Invalid command sent to terminal for installation.'); - }); + mockTerminalFactory.verifyAll(); + expect(argsSent.join(' ')).equal(`-m pip install -U ${moduleName} --user`, 'Invalid command sent to terminal for installation.'); + }); - test('Validate Conda install arguments', async () => { - const interpreterPath = await getCurrentPythonPath(); - const mockInterpreterLocator = TypeMoq.Mock.ofType(); - mockInterpreterLocator.setup(p => p.getInterpreters(TypeMoq.It.isAny())).returns(() => Promise.resolve([{ ...info, path: interpreterPath, type: InterpreterType.Conda }])); - ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, mockInterpreterLocator.object, INTERPRETER_LOCATOR_SERVICE); - ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, TypeMoq.Mock.ofType().object, PIPENV_SERVICE); + test(`Validate Conda install arguments ${resourceTestNameSuffix}`, async () => { + const interpreterPath = await getCurrentPythonPath(); + const mockInterpreterLocator = TypeMoq.Mock.ofType(); + mockInterpreterLocator.setup(p => p.getInterpreters(TypeMoq.It.isAny())).returns(() => Promise.resolve([{ ...info, path: interpreterPath, type: InterpreterType.Conda }])); + ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, mockInterpreterLocator.object, INTERPRETER_LOCATOR_SERVICE); + ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, TypeMoq.Mock.ofType().object, PIPENV_SERVICE); - const moduleName = 'xyz'; + const moduleName = 'xyz'; - const moduleInstallers = ioc.serviceContainer.getAll(IModuleInstaller); - const pipInstaller = moduleInstallers.find(item => item.displayName === 'Pip')!; + const moduleInstallers = ioc.serviceContainer.getAll(IModuleInstaller); + const pipInstaller = moduleInstallers.find(item => item.displayName === 'Pip')!; - expect(pipInstaller).not.to.be.an('undefined', 'Pip installer not found'); + expect(pipInstaller).not.to.be.an('undefined', 'Pip installer not found'); - let argsSent: string[] = []; - mockTerminalService - .setup(t => t.sendCommand(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())) - .returns((cmd: string, args: string[]) => { argsSent = args; return Promise.resolve(void 0); }); - await pipInstaller.installModule(moduleName); + let argsSent: string[] = []; + mockTerminalService + .setup(t => t.sendCommand(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())) + .returns((cmd: string, args: string[]) => { argsSent = args; return Promise.resolve(void 0); }); - expect(argsSent.join(' ')).equal(`-m pip install -U ${moduleName}`, 'Invalid command sent to terminal for installation.'); - }); + await pipInstaller.installModule(moduleName, resource); - test('Validate pipenv install arguments', async () => { - const mockInterpreterLocator = TypeMoq.Mock.ofType(); - mockInterpreterLocator.setup(p => p.getInterpreters(TypeMoq.It.isAny())).returns(() => Promise.resolve([{ ...info, path: 'interpreterPath', type: InterpreterType.VirtualEnv }])); - ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, mockInterpreterLocator.object, PIPENV_SERVICE); - - const moduleName = 'xyz'; - const moduleInstallers = ioc.serviceContainer.getAll(IModuleInstaller); - const pipInstaller = moduleInstallers.find(item => item.displayName === 'pipenv')!; - - expect(pipInstaller).not.to.be.an('undefined', 'pipenv installer not found'); - - let argsSent: string[] = []; - let command: string | undefined; - mockTerminalService - .setup(t => t.sendCommand(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())) - .returns((cmd: string, args: string[]) => { - argsSent = args; - command = cmd; - return Promise.resolve(void 0); - }); + mockTerminalFactory.verifyAll(); + expect(argsSent.join(' ')).equal(`-m pip install -U ${moduleName}`, 'Invalid command sent to terminal for installation.'); + }); + + test(`Validate pipenv install arguments ${resourceTestNameSuffix}`, async () => { + const mockInterpreterLocator = TypeMoq.Mock.ofType(); + mockInterpreterLocator.setup(p => p.getInterpreters(TypeMoq.It.isAny())).returns(() => Promise.resolve([{ ...info, path: 'interpreterPath', type: InterpreterType.VirtualEnv }])); + ioc.serviceManager.addSingletonInstance(IInterpreterLocatorService, mockInterpreterLocator.object, PIPENV_SERVICE); - await pipInstaller.installModule(moduleName); + const moduleName = 'xyz'; + const moduleInstallers = ioc.serviceContainer.getAll(IModuleInstaller); + const pipInstaller = moduleInstallers.find(item => item.displayName === 'pipenv')!; - expect(command!).equal('pipenv', 'Invalid command sent to terminal for installation.'); - expect(argsSent.join(' ')).equal(`install ${moduleName} --dev`, 'Invalid command arguments sent to terminal for installation.'); + expect(pipInstaller).not.to.be.an('undefined', 'pipenv installer not found'); + + let argsSent: string[] = []; + let command: string | undefined; + mockTerminalService + .setup(t => t.sendCommand(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())) + .returns((cmd: string, args: string[]) => { + argsSent = args; + command = cmd; + return Promise.resolve(void 0); + }); + + await pipInstaller.installModule(moduleName, resource); + + mockTerminalFactory.verifyAll(); + expect(command!).equal('pipenv', 'Invalid command sent to terminal for installation.'); + expect(argsSent.join(' ')).equal(`install ${moduleName} --dev`, 'Invalid command arguments sent to terminal for installation.'); + }); }); }); From 8c29d50c9573286a378805089b482bb1828a45a9 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 21 May 2018 10:17:40 -0700 Subject: [PATCH 251/433] Display documentation for auto completion items when addBrackets is true (#1659) * Get snippet value using relevant API * :memo: add news entry * Changes to allow testability * Add tests * Fixes #452 --- news/2 Fixes/452.md | 1 + src/client/providers/completionProvider.ts | 3 +- src/client/providers/completionSource.ts | 22 +++-- src/client/providers/itemInfoSource.ts | 16 +++- src/test/providers/completionSource.test.ts | 89 +++++++++++++++++++++ 5 files changed, 119 insertions(+), 12 deletions(-) create mode 100644 news/2 Fixes/452.md create mode 100644 src/test/providers/completionSource.test.ts diff --git a/news/2 Fixes/452.md b/news/2 Fixes/452.md new file mode 100644 index 000000000000..69f64d85a6ab --- /dev/null +++ b/news/2 Fixes/452.md @@ -0,0 +1 @@ +Display documentation for auto completion items when the feature to automatically insert of brackets for selected item is turned on. diff --git a/src/client/providers/completionProvider.ts b/src/client/providers/completionProvider.ts index fb0ae33bfb1a..89c89f732091 100644 --- a/src/client/providers/completionProvider.ts +++ b/src/client/providers/completionProvider.ts @@ -7,13 +7,14 @@ import { JediFactory } from '../languageServices/jediProxyFactory'; import { captureTelemetry } from '../telemetry'; import { COMPLETION } from '../telemetry/constants'; import { CompletionSource } from './completionSource'; +import { ItemInfoSource } from './itemInfoSource'; export class PythonCompletionItemProvider implements vscode.CompletionItemProvider { private completionSource: CompletionSource; private configService: IConfigurationService; constructor(jediFactory: JediFactory, serviceContainer: IServiceContainer) { - this.completionSource = new CompletionSource(jediFactory); + this.completionSource = new CompletionSource(jediFactory, serviceContainer, new ItemInfoSource(jediFactory)); this.configService = serviceContainer.get(IConfigurationService); } diff --git a/src/client/providers/completionSource.ts b/src/client/providers/completionSource.ts index 5a2064c9338c..5084a6a958f7 100644 --- a/src/client/providers/completionSource.ts +++ b/src/client/providers/completionSource.ts @@ -3,9 +3,10 @@ 'use strict'; import * as vscode from 'vscode'; -import { PythonSettings } from '../common/configSettings'; +import { IConfigurationService } from '../common/types'; +import { IServiceContainer } from '../ioc/types'; import { JediFactory } from '../languageServices/jediProxyFactory'; -import { ItemInfoSource, LanguageItemInfo } from './itemInfoSource'; +import { IItemInfoSource, LanguageItemInfo } from './itemInfoSource'; import * as proxy from './jediProxy'; import { isPositionInsideStringOrComment } from './providerUtilities'; @@ -25,11 +26,10 @@ class DocumentPosition { export class CompletionSource { private jediFactory: JediFactory; - private itemInfoSource: ItemInfoSource; - constructor(jediFactory: JediFactory) { + constructor(jediFactory: JediFactory, private serviceContainer: IServiceContainer, + private itemInfoSource: IItemInfoSource) { this.jediFactory = jediFactory; - this.itemInfoSource = new ItemInfoSource(jediFactory); } public async getVsCodeCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken) @@ -50,7 +50,13 @@ export class CompletionSource { // Supply hover source with simulated document text where item in question was 'already typed'. const document = documentPosition.document; const position = documentPosition.position; - const itemText = completionItem.insertText ? completionItem.insertText : completionItem.label; + let insertText: string | undefined; + if (typeof completionItem.insertText === 'string') { + insertText = completionItem.insertText!; + } else if (completionItem.insertText instanceof vscode.SnippetString) { + insertText = (completionItem.insertText! as vscode.SnippetString).value; + } + const itemText = insertText ? insertText : completionItem.label; const wordRange = document.getWordRangeAtPosition(position); const leadingRange = wordRange !== undefined @@ -100,7 +106,9 @@ export class CompletionSource { private toVsCodeCompletion(documentPosition: DocumentPosition, item: proxy.IAutoCompleteItem, resource: vscode.Uri): vscode.CompletionItem { const completionItem = new vscode.CompletionItem(item.text); completionItem.kind = item.type; - if (PythonSettings.getInstance(resource).autoComplete.addBrackets === true && + const configurationService = this.serviceContainer.get(IConfigurationService); + const pythonSettings = configurationService.getSettings(resource); + if (pythonSettings.autoComplete.addBrackets === true && (item.kind === vscode.SymbolKind.Function || item.kind === vscode.SymbolKind.Method)) { completionItem.insertText = new vscode.SnippetString(item.text).appendText('(').appendTabstop().appendText(')'); } diff --git a/src/client/providers/itemInfoSource.ts b/src/client/providers/itemInfoSource.ts index b851f61b533d..5effbe7483e4 100644 --- a/src/client/providers/itemInfoSource.ts +++ b/src/client/providers/itemInfoSource.ts @@ -15,7 +15,15 @@ export class LanguageItemInfo { public signature: vscode.MarkdownString) { } } -export class ItemInfoSource { +export interface IItemInfoSource { + getItemInfoFromText(documentUri: vscode.Uri, fileName: string, + range: vscode.Range, sourceText: string, + token: vscode.CancellationToken): Promise; + getItemInfoFromDocument(document: vscode.TextDocument, position: vscode.Position, + token: vscode.CancellationToken): Promise; +} + +export class ItemInfoSource implements IItemInfoSource { private textConverter = new RestTextConverter(); constructor(private jediFactory: JediFactory) { } @@ -51,7 +59,7 @@ export class ItemInfoSource { if (!range || range.isEmpty) { return; } - return await this.getHoverResultFromDocumentRange(document, range, token); + return this.getHoverResultFromDocumentRange(document, range, token); } private async getHoverResultFromDocumentRange(document: vscode.TextDocument, range: vscode.Range, token: vscode.CancellationToken) @@ -65,7 +73,7 @@ export class ItemInfoSource { if (document.isDirty) { cmd.source = document.getText(); } - return await this.jediFactory.getJediProxyHandler(document.uri).sendCommand(cmd, token); + return this.jediFactory.getJediProxyHandler(document.uri).sendCommand(cmd, token); } private async getHoverResultFromTextRange(documentUri: vscode.Uri, fileName: string, range: vscode.Range, sourceText: string, token: vscode.CancellationToken) @@ -77,7 +85,7 @@ export class ItemInfoSource { lineIndex: range.end.line, source: sourceText }; - return await this.jediFactory.getJediProxyHandler(documentUri).sendCommand(cmd, token); + return this.jediFactory.getJediProxyHandler(documentUri).sendCommand(cmd, token); } private getItemInfoFromHoverResult(data: proxy.IHoverResult, currentWord: string): LanguageItemInfo[] { diff --git a/src/test/providers/completionSource.test.ts b/src/test/providers/completionSource.test.ts new file mode 100644 index 000000000000..0c64677849c8 --- /dev/null +++ b/src/test/providers/completionSource.test.ts @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:max-func-body-length no-any + +import * as TypeMoq from 'typemoq'; +import { CancellationTokenSource, CompletionItemKind, Position, SymbolKind, TextDocument, TextLine } from 'vscode'; +import { IAutoCompleteSettings, IConfigurationService, IPythonSettings } from '../../client/common/types'; +import { IServiceContainer } from '../../client/ioc/types'; +import { JediFactory } from '../../client/languageServices/jediProxyFactory'; +import { CompletionSource } from '../../client/providers/completionSource'; +import { IItemInfoSource } from '../../client/providers/itemInfoSource'; +import { IAutoCompleteItem, ICompletionResult, JediProxyHandler } from '../../client/providers/jediProxy'; + +suite('Completion Provider', () => { + let completionSource: CompletionSource; + let jediHandler: TypeMoq.IMock>; + let autoCompleteSettings: TypeMoq.IMock; + let itemInfoSource: TypeMoq.IMock; + setup(() => { + const jediFactory = TypeMoq.Mock.ofType(JediFactory); + jediHandler = TypeMoq.Mock.ofType>(); + const serviceContainer = TypeMoq.Mock.ofType(); + const configService = TypeMoq.Mock.ofType(); + const pythonSettings = TypeMoq.Mock.ofType(); + autoCompleteSettings = TypeMoq.Mock.ofType(); + autoCompleteSettings = TypeMoq.Mock.ofType(); + + jediFactory.setup(j => j.getJediProxyHandler(TypeMoq.It.isAny())) + .returns(() => jediHandler.object); + serviceContainer.setup(s => s.get(TypeMoq.It.isValue(IConfigurationService), TypeMoq.It.isAny())) + .returns(() => configService.object); + configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); + pythonSettings.setup(p => p.autoComplete).returns(() => autoCompleteSettings.object); + itemInfoSource = TypeMoq.Mock.ofType(); + completionSource = new CompletionSource(jediFactory.object, serviceContainer.object, itemInfoSource.object); + }); + + async function testDocumentation(source: string, addBrackets: boolean) { + const doc = TypeMoq.Mock.ofType(); + const position = new Position(1, 1); + const token = new CancellationTokenSource().token; + const lineText = TypeMoq.Mock.ofType(); + const completionResult = TypeMoq.Mock.ofType(); + + const autoCompleteItems: IAutoCompleteItem[] = [{ + description: 'description', kind: SymbolKind.Function, + raw_docstring: 'raw docstring', + rawType: CompletionItemKind.Function, + rightLabel: 'right label', + text: 'some text', type: CompletionItemKind.Function + }]; + + autoCompleteSettings.setup(a => a.addBrackets).returns(() => addBrackets); + doc.setup(d => d.fileName).returns(() => ''); + doc.setup(d => d.getText(TypeMoq.It.isAny())).returns(() => source); + doc.setup(d => d.lineAt(TypeMoq.It.isAny())).returns(() => lineText.object); + doc.setup(d => d.offsetAt(TypeMoq.It.isAny())).returns(() => 0); + lineText.setup(l => l.text).returns(() => source); + completionResult.setup(c => c.requestId).returns(() => 1); + completionResult.setup(c => c.items).returns(() => autoCompleteItems); + completionResult.setup((c: any) => c.then).returns(() => undefined); + jediHandler.setup(j => j.sendCommand(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { + return Promise.resolve(completionResult.object); + }); + + const expectedSource = `${source}${autoCompleteItems[0].text}${addBrackets ? '($)' : ''}`; + itemInfoSource.setup(i => i.getItemInfoFromText(TypeMoq.It.isAny(), TypeMoq.It.isAny(), + TypeMoq.It.isAny(), expectedSource, TypeMoq.It.isAny())) + .returns(() => Promise.resolve(undefined)) + .verifiable(TypeMoq.Times.once()); + + const [item] = await completionSource.getVsCodeCompletionItems(doc.object, position, token); + await completionSource.getDocumentation(item, token); + itemInfoSource.verifyAll(); + } + + test('Ensure docs are provided when \'addBrackets\' setting is false', async () => { + const source = 'if True:\n print("Hello")\n'; + await testDocumentation(source, false); + }); + test('Ensure docs are provided when \'addBrackets\' setting is true', async () => { + const source = 'if True:\n print("Hello")\n'; + await testDocumentation(source, true); + }); + +}); From fb637a76d161365a7c891f142dadc97b3a48bb50 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 21 May 2018 10:18:36 -0700 Subject: [PATCH 252/433] Ensure the prompt to install missing packages is displayed only once (#1649) * Ensure the prompt to install missing packages is displayed only once * Add missing dependency * Fixes #980 --- news/2 Fixes/980 | 1 + .../common/installer/productInstaller.ts | 54 +++++++++++------ .../linters/errorHandlers/notInstalled.ts | 1 - src/test/common/installer.test.ts | 6 +- src/test/common/installer/installer.test.ts | 59 ++++++++++++++++++- 5 files changed, 99 insertions(+), 22 deletions(-) create mode 100644 news/2 Fixes/980 diff --git a/news/2 Fixes/980 b/news/2 Fixes/980 new file mode 100644 index 000000000000..0dcbcaacd8ff --- /dev/null +++ b/news/2 Fixes/980 @@ -0,0 +1 @@ +Ensure the prompt to install missing packages is not displayed more than once. diff --git a/src/client/common/installer/productInstaller.ts b/src/client/common/installer/productInstaller.ts index 9659ef0debad..8e3bde192995 100644 --- a/src/client/common/installer/productInstaller.ts +++ b/src/client/common/installer/productInstaller.ts @@ -2,11 +2,12 @@ import { inject, injectable, named } from 'inversify'; import * as os from 'os'; import * as path from 'path'; import * as vscode from 'vscode'; +import '../../common/extensions'; import { IFormatterHelper } from '../../formatters/types'; import { IServiceContainer } from '../../ioc/types'; import { ILinterManager } from '../../linters/types'; import { ITestsHelper } from '../../unittests/common/types'; -import { IApplicationShell } from '../application/types'; +import { IApplicationShell, IWorkspaceService } from '../application/types'; import { STANDARD_OUTPUT_CHANNEL } from '../constants'; import { IPlatformService } from '../platform/types'; import { IProcessServiceFactory, IPythonExecutionFactory } from '../process/types'; @@ -28,16 +29,34 @@ enum ProductType { } // tslint:disable-next-line:max-classes-per-file -abstract class BaseInstaller { +export abstract class BaseInstaller { + private static readonly PromptPromises = new Map>(); protected appShell: IApplicationShell; protected configService: IConfigurationService; + private readonly workspaceService: IWorkspaceService; constructor(protected serviceContainer: IServiceContainer, protected outputChannel: vscode.OutputChannel) { this.appShell = serviceContainer.get(IApplicationShell); this.configService = serviceContainer.get(IConfigurationService); + this.workspaceService = serviceContainer.get(IWorkspaceService); } - public abstract promptToInstall(product: Product, resource?: vscode.Uri): Promise; + public promptToInstall(product: Product, resource?: vscode.Uri): Promise { + // If this method gets called twice, while previous promise has not been resolved, then return that same promise. + // E.g. previous promise is not resolved as a message has been displayed to the user, so no point displaying + // another message. + const workspaceFolder = resource ? this.workspaceService.getWorkspaceFolder(resource) : undefined; + const key = `${product}${workspaceFolder ? workspaceFolder.uri.fsPath : ''}`; + if (BaseInstaller.PromptPromises.has(key)) { + return BaseInstaller.PromptPromises.get(key)!; + } + const promise = this.promptToInstallImplementation(product, resource); + BaseInstaller.PromptPromises.set(key, promise); + promise.then(() => BaseInstaller.PromptPromises.delete(key)).ignoreErrors(); + promise.catch(() => BaseInstaller.PromptPromises.delete(key)).ignoreErrors(); + + return promise; + } public async install(product: Product, resource?: vscode.Uri): Promise { if (product === Product.unittest) { @@ -83,22 +102,17 @@ abstract class BaseInstaller { .catch(() => false); } } - + protected abstract promptToInstallImplementation(product: Product, resource?: vscode.Uri): Promise; protected getExecutableNameFromSettings(product: Product, resource?: vscode.Uri): string { throw new Error('getExecutableNameFromSettings is not supported on this object'); } } -class CTagsInstaller extends BaseInstaller { +export class CTagsInstaller extends BaseInstaller { constructor(serviceContainer: IServiceContainer, outputChannel: vscode.OutputChannel) { super(serviceContainer, outputChannel); } - public async promptToInstall(product: Product, resource?: vscode.Uri): Promise { - const item = await this.appShell.showErrorMessage('Install CTags to enable Python workspace symbols?', 'Yes', 'No'); - return item === 'Yes' ? this.install(product, resource) : InstallerResponse.Ignore; - } - public async install(product: Product, resource?: vscode.Uri): Promise { if (this.serviceContainer.get(IPlatformService).isWindows) { this.outputChannel.appendLine('Install Universal Ctags Win32 to enable support for Workspace Symbols'); @@ -115,6 +129,10 @@ class CTagsInstaller extends BaseInstaller { } return InstallerResponse.Ignore; } + protected async promptToInstallImplementation(product: Product, resource?: vscode.Uri): Promise { + const item = await this.appShell.showErrorMessage('Install CTags to enable Python workspace symbols?', 'Yes', 'No'); + return item === 'Yes' ? this.install(product, resource) : InstallerResponse.Ignore; + } protected getExecutableNameFromSettings(product: Product, resource?: vscode.Uri): string { const settings = this.configService.getSettings(resource); @@ -122,8 +140,8 @@ class CTagsInstaller extends BaseInstaller { } } -class FormatterInstaller extends BaseInstaller { - public async promptToInstall(product: Product, resource?: vscode.Uri): Promise { +export class FormatterInstaller extends BaseInstaller { + protected async promptToInstallImplementation(product: Product, resource?: vscode.Uri): Promise { // Hard-coded on purpose because the UI won't necessarily work having // another formatter. const formatters = [Product.autopep8, Product.black, Product.yapf]; @@ -159,8 +177,8 @@ class FormatterInstaller extends BaseInstaller { } // tslint:disable-next-line:max-classes-per-file -class LinterInstaller extends BaseInstaller { - public async promptToInstall(product: Product, resource?: vscode.Uri): Promise { +export class LinterInstaller extends BaseInstaller { + protected async promptToInstallImplementation(product: Product, resource?: vscode.Uri): Promise { const productName = ProductNames.get(product)!; const install = 'Install'; const disableAllLinting = 'Disable linting'; @@ -188,8 +206,8 @@ class LinterInstaller extends BaseInstaller { } // tslint:disable-next-line:max-classes-per-file -class TestFrameworkInstaller extends BaseInstaller { - public async promptToInstall(product: Product, resource?: vscode.Uri): Promise { +export class TestFrameworkInstaller extends BaseInstaller { + protected async promptToInstallImplementation(product: Product, resource?: vscode.Uri): Promise { const productName = ProductNames.get(product)!; const item = await this.appShell.showErrorMessage(`Test framework ${productName} is not installed. Install?`, 'Yes', 'No'); return item === 'Yes' ? this.install(product, resource) : InstallerResponse.Ignore; @@ -208,8 +226,8 @@ class TestFrameworkInstaller extends BaseInstaller { } // tslint:disable-next-line:max-classes-per-file -class RefactoringLibraryInstaller extends BaseInstaller { - public async promptToInstall(product: Product, resource?: vscode.Uri): Promise { +export class RefactoringLibraryInstaller extends BaseInstaller { + protected async promptToInstallImplementation(product: Product, resource?: vscode.Uri): Promise { const productName = ProductNames.get(product)!; const item = await this.appShell.showErrorMessage(`Refactoring library ${productName} is not installed. Install?`, 'Yes', 'No'); return item === 'Yes' ? this.install(product, resource) : InstallerResponse.Ignore; diff --git a/src/client/linters/errorHandlers/notInstalled.ts b/src/client/linters/errorHandlers/notInstalled.ts index c3fbd9447296..c7c56c66e7a9 100644 --- a/src/client/linters/errorHandlers/notInstalled.ts +++ b/src/client/linters/errorHandlers/notInstalled.ts @@ -1,5 +1,4 @@ import { OutputChannel, Uri } from 'vscode'; -import { isNotInstalledError } from '../../common/helpers'; import { IPythonExecutionFactory } from '../../common/process/types'; import { ExecutionInfo, Product } from '../../common/types'; import { IServiceContainer } from '../../ioc/types'; diff --git a/src/test/common/installer.test.ts b/src/test/common/installer.test.ts index 09b0385eaca2..ea1fd200b983 100644 --- a/src/test/common/installer.test.ts +++ b/src/test/common/installer.test.ts @@ -1,7 +1,7 @@ import * as path from 'path'; import * as TypeMoq from 'typemoq'; import { ConfigurationTarget, Uri } from 'vscode'; -import { IApplicationShell } from '../../client/common/application/types'; +import { IApplicationShell, IWorkspaceService } from '../../client/common/application/types'; import { ConfigurationService } from '../../client/common/configuration/service'; import { EnumEx } from '../../client/common/enumUtils'; import { createDeferred } from '../../client/common/helpers'; @@ -58,6 +58,10 @@ suite('Installer', () => { ioc.serviceManager.addSingletonInstance(IApplicationShell, TypeMoq.Mock.ofType().object); ioc.serviceManager.addSingleton(IConfigurationService, ConfigurationService); + const workspaceService = TypeMoq.Mock.ofType(); + workspaceService.setup(w => w.getWorkspaceFolder(TypeMoq.It.isAny())).returns(() => undefined); + ioc.serviceManager.addSingletonInstance(IWorkspaceService, workspaceService.object); + ioc.registerMockProcessTypes(); ioc.serviceManager.addSingletonInstance(IsWindows, false); } diff --git a/src/test/common/installer/installer.test.ts b/src/test/common/installer/installer.test.ts index b1c37cf6bb85..69f88f2c21ea 100644 --- a/src/test/common/installer/installer.test.ts +++ b/src/test/common/installer/installer.test.ts @@ -1,11 +1,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +// tslint:disable:max-func-body-length no-invalid-this + import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import * as TypeMoq from 'typemoq'; -import { Disposable, OutputChannel, Uri } from 'vscode'; +import { Disposable, OutputChannel, Uri, WorkspaceFolder } from 'vscode'; +import { IApplicationShell, IWorkspaceService } from '../../../client/common/application/types'; import { EnumEx } from '../../../client/common/enumUtils'; +import '../../../client/common/extensions'; +import { createDeferred, Deferred } from '../../../client/common/helpers'; import { ProductInstaller } from '../../../client/common/installer/productInstaller'; import { IInstallationChannelManager, IModuleInstaller } from '../../../client/common/installer/types'; import { IDisposableRegistry, ILogger, InstallerResponse, ModuleNamePurpose, Product } from '../../../client/common/types'; @@ -13,7 +18,6 @@ import { IServiceContainer } from '../../../client/ioc/types'; use(chaiAsPromised); -// tslint:disable-next-line:max-func-body-length suite('Module Installer', () => { [undefined, Uri.file('resource')].forEach(resource => { EnumEx.getNamesAndValues(Product).forEach(product => { @@ -22,7 +26,11 @@ suite('Module Installer', () => { let installationChannel: TypeMoq.IMock; let moduleInstaller: TypeMoq.IMock; let serviceContainer: TypeMoq.IMock; + let app: TypeMoq.IMock; + let promptDeferred: Deferred; + let workspaceService: TypeMoq.IMock; setup(() => { + promptDeferred = createDeferred(); serviceContainer = TypeMoq.Mock.ofType(); const outputChannel = TypeMoq.Mock.ofType(); @@ -33,6 +41,10 @@ suite('Module Installer', () => { installationChannel = TypeMoq.Mock.ofType(); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInstallationChannelManager), TypeMoq.It.isAny())).returns(() => installationChannel.object); + app = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IApplicationShell), TypeMoq.It.isAny())).returns(() => app.object); + workspaceService = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IWorkspaceService), TypeMoq.It.isAny())).returns(() => workspaceService.object); moduleInstaller = TypeMoq.Mock.ofType(); // tslint:disable-next-line:no-any @@ -41,6 +53,8 @@ suite('Module Installer', () => { installationChannel.setup(i => i.getInstallationChannel(TypeMoq.It.isAny())).returns(() => Promise.resolve(moduleInstaller.object)); }); teardown(() => { + // This must be resolved, else all subsequent tests will fail (as this same promise will be used for other tests). + promptDeferred.resolve(); disposables.forEach(disposable => { if (disposable) { disposable.dispose(); @@ -92,6 +106,47 @@ suite('Module Installer', () => { moduleInstaller.verify(m => m.installModule(TypeMoq.It.isValue(moduleName), TypeMoq.It.isValue(resource)), TypeMoq.Times.once()); } }); + test(`Ensure the prompt is displayed only once, untill the prompt is closed, ${product.name} (${resource ? 'With a resource' : 'without a resource'})`, async function () { + if (product.value === Product.unittest) { + return this.skip(); + } + workspaceService.setup(w => w.getWorkspaceFolder(TypeMoq.It.isValue(resource!))) + .returns(() => TypeMoq.Mock.ofType().object) + .verifiable(TypeMoq.Times.exactly(resource ? 5 : 0)); + app.setup(a => a.showErrorMessage(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns(() => promptDeferred.promise) + .verifiable(TypeMoq.Times.once()); + + // Display first prompt. + installer.promptToInstall(product.value, resource).ignoreErrors(); + + // Display a few more prompts. + installer.promptToInstall(product.value, resource).ignoreErrors(); + installer.promptToInstall(product.value, resource).ignoreErrors(); + installer.promptToInstall(product.value, resource).ignoreErrors(); + installer.promptToInstall(product.value, resource).ignoreErrors(); + + app.verifyAll(); + workspaceService.verifyAll(); + }); + test(`Ensure the prompt is displayed again when previous prompt has been closed, ${product.name} (${resource ? 'With a resource' : 'without a resource'})`, async function () { + if (product.value === Product.unittest) { + return this.skip(); + } + workspaceService.setup(w => w.getWorkspaceFolder(TypeMoq.It.isValue(resource!))) + .returns(() => TypeMoq.Mock.ofType().object) + .verifiable(TypeMoq.Times.exactly(resource ? 3 : 0)); + app.setup(a => a.showErrorMessage(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns(() => Promise.resolve(undefined)) + .verifiable(TypeMoq.Times.exactly(3)); + + await installer.promptToInstall(product.value, resource); + await installer.promptToInstall(product.value, resource); + await installer.promptToInstall(product.value, resource); + + app.verifyAll(); + workspaceService.verifyAll(); + }); } } }); From b4d924cf5e566199ea478a2fb2946d205548e84c Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 21 May 2018 11:56:17 -0700 Subject: [PATCH 253/433] Fix getting display name and version when using windows registry (#1697) * Fixes #1660 * Fixes #1703 * :bug: fix getting display name and version when using windows registry * Git hook for tests * :memo: add news entry * Some more unit tests\ * Mock VS Code only inside the test runner * Singleton mocks for vscode namespcaes * Fix test --- .vscode/launch.json | 18 +- .vscode/tasks.json | 11 +- gulpfile.js | 9 +- news/3 Code Health/1703.md | 1 + package.json | 1 + src/client/common/platform/pathUtils.ts | 6 +- src/client/common/types.ts | 1 + .../services/windowsRegistryService.ts | 10 +- .../managers/testConfigurationManager.ts | 27 +- .../windowsRegistryService.test.ts | 293 ------------------ .../windowsRegistryService.unit.test.ts | 290 +++++++++++++++++ src/test/providers/completionSource.test.ts | 2 +- src/test/unittests.ts | 73 +++++ .../testConfigurationManager.unit.test.ts | 59 ++++ src/test/vscode-mock.ts | 84 +++++ 15 files changed, 573 insertions(+), 312 deletions(-) create mode 100644 news/3 Code Health/1703.md delete mode 100644 src/test/interpreters/windowsRegistryService.test.ts create mode 100644 src/test/interpreters/windowsRegistryService.unit.test.ts create mode 100644 src/test/unittests.ts create mode 100644 src/test/unittests/common/managers/testConfigurationManager.unit.test.ts create mode 100644 src/test/vscode-mock.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 35f13ae91111..415804510f82 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -69,6 +69,22 @@ ], "preLaunchTask": "Compile" }, + { + "name": "Debug Unit Tests", + "type": "node", + "request": "launch", + "program": "${workspaceFolder}/out/test/unittests.js", + "stopOnEntry": false, + "sourceMaps": true, + "args": [ + "timeout=60000", + "grep=" + ], + "outFiles": [ + "${workspaceFolder}/out/**/*.js" + ], + "preLaunchTask": "Compile" + }, { "name": "Launch Multiroot Tests", "type": "extensionHost", @@ -134,4 +150,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 826c669b3eeb..6165d465d00f 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -24,6 +24,15 @@ "isDefault": true } }, + { + "label": "Run Unit Tests", + "type": "npm", + "script": "test:unittests", + "group": { + "kind": "test", + "isDefault": true + } + }, { "label": "Hygiene", "type": "gulp", @@ -105,4 +114,4 @@ } } ] -} \ No newline at end of file +} diff --git a/gulpfile.js b/gulpfile.js index 5f28dc0ca69e..0f95060a1b9c 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -502,5 +502,12 @@ exports.hygiene = hygiene; // this allows us to run hygiene as a git pre-commit hook. if (require.main === module) { - run({ exitOnError: true, mode: 'staged' }); + const result = run({ exitOnError: true, mode: 'staged' }); + // Run unit tests and ensure they pass as well. + if (result && result.on) { + result.on('end', () => { + const main = require('./out/test/unittests'); + main.runTests(); + }) + } } diff --git a/news/3 Code Health/1703.md b/news/3 Code Health/1703.md new file mode 100644 index 000000000000..bfdd065f7e1d --- /dev/null +++ b/news/3 Code Health/1703.md @@ -0,0 +1 @@ +Run unit tests as a pre-commit hook. diff --git a/package.json b/package.json index ec57caec2beb..259c721a7854 100644 --- a/package.json +++ b/package.json @@ -1854,6 +1854,7 @@ "compile": "tsc -watch -p ./", "postinstall": "node ./node_modules/vscode/bin/install", "test": "node ./out/test/standardTest.js && node ./out/test/multiRootTest.js", + "test:unittests": "node ./out/test/unittests.js", "testDebugger": "node ./out/test/debuggerTest.js", "testSingleWorkspace": "node ./out/test/standardTest.js", "testMultiWorkspace": "node ./out/test/multiRootTest.js", diff --git a/src/client/common/platform/pathUtils.ts b/src/client/common/platform/pathUtils.ts index 8743cd51d88a..3fcd2838d29e 100644 --- a/src/client/common/platform/pathUtils.ts +++ b/src/client/common/platform/pathUtils.ts @@ -1,12 +1,16 @@ import { inject, injectable } from 'inversify'; +import * as path from 'path'; import { IPathUtils, IsWindows } from '../types'; import { NON_WINDOWS_PATH_VARIABLE_NAME, WINDOWS_PATH_VARIABLE_NAME } from './constants'; // TO DO: Deprecate in favor of IPlatformService @injectable() export class PathUtils implements IPathUtils { - constructor( @inject(IsWindows) private isWindows: boolean) { } + constructor(@inject(IsWindows) private isWindows: boolean) { } public getPathVariableName() { return this.isWindows ? WINDOWS_PATH_VARIABLE_NAME : NON_WINDOWS_PATH_VARIABLE_NAME; } + public basename(pathValue: string, ext?: string): string{ + return path.basename(pathValue, ext); + } } diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 2bec7bb18256..4ba1218fbc9f 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -84,6 +84,7 @@ export const IPathUtils = Symbol('IPathUtils'); export interface IPathUtils { getPathVariableName(): 'Path' | 'PATH'; + basename(pathValue: string, ext?: string): string; } export const ICurrentProcess = Symbol('ICurrentProcess'); diff --git a/src/client/interpreter/locators/services/windowsRegistryService.ts b/src/client/interpreter/locators/services/windowsRegistryService.ts index 2226fc3f387c..e4b48b4827a6 100644 --- a/src/client/interpreter/locators/services/windowsRegistryService.ts +++ b/src/client/interpreter/locators/services/windowsRegistryService.ts @@ -4,7 +4,7 @@ import * as _ from 'lodash'; import * as path from 'path'; import { Uri } from 'vscode'; import { Architecture, IRegistry, RegistryHive } from '../../../common/platform/types'; -import { Is64Bit } from '../../../common/types'; +import { IPathUtils, Is64Bit } from '../../../common/types'; import { IServiceContainer } from '../../../ioc/types'; import { IInterpreterHelper, InterpreterType, PythonInterpreter } from '../../contracts'; import { CacheableLocatorService } from './cacheableLocatorService'; @@ -27,10 +27,12 @@ type CompanyInterpreter = { @injectable() export class WindowsRegistryService extends CacheableLocatorService { + private readonly pathUtils: IPathUtils; constructor(@inject(IRegistry) private registry: IRegistry, @inject(Is64Bit) private is64Bit: boolean, @inject(IServiceContainer) serviceContainer: IServiceContainer) { super('WindowsRegistryService', serviceContainer); + this.pathUtils = serviceContainer.get(IPathUtils); } // tslint:disable-next-line:no-empty public dispose() { } @@ -72,7 +74,7 @@ export class WindowsRegistryService extends CacheableLocatorService { private async getCompanies(hive: RegistryHive, arch?: Architecture): Promise { return this.registry.getKeys('\\Software\\Python', hive, arch) .then(companyKeys => companyKeys - .filter(companyKey => CompaniesToIgnore.indexOf(path.basename(companyKey).toUpperCase()) === -1) + .filter(companyKey => CompaniesToIgnore.indexOf(this.pathUtils.basename(companyKey).toUpperCase()) === -1) .map(companyKey => { return { companyKey, hive, arch }; })); @@ -125,7 +127,7 @@ export class WindowsRegistryService extends CacheableLocatorService { if (!details) { return; } - const version = interpreterInfo.version ? path.basename(interpreterInfo.version) : details.version; + const version = interpreterInfo.version ? this.pathUtils.basename(interpreterInfo.version) : this.pathUtils.basename(tagKey); // tslint:disable-next-line:prefer-type-cast no-object-literal-type-assertion return { ...(details as PythonInterpreter), @@ -155,7 +157,7 @@ export class WindowsRegistryService extends CacheableLocatorService { if (displayName && displayName.length > 0) { return displayName; } - const company = path.basename(companyKey); + const company = this.pathUtils.basename(companyKey); return company.toUpperCase() === PythonCoreComany ? PythonCoreCompanyDisplayName : company; } } diff --git a/src/client/unittests/common/managers/testConfigurationManager.ts b/src/client/unittests/common/managers/testConfigurationManager.ts index 38586977016f..358b8b5705b6 100644 --- a/src/client/unittests/common/managers/testConfigurationManager.ts +++ b/src/client/unittests/common/managers/testConfigurationManager.ts @@ -1,39 +1,46 @@ import * as path from 'path'; -import * as vscode from 'vscode'; -import { Uri } from 'vscode'; +import { OutputChannel, QuickPickItem, Uri, window } from 'vscode'; import { createDeferred } from '../../../common/helpers'; -import { IInstaller } from '../../../common/types'; +import { IInstaller, Product } from '../../../common/types'; import { getSubDirectories } from '../../../common/utils'; import { ITestConfigSettingsService, UnitTestProduct } from './../types'; export abstract class TestConfigurationManager { constructor(protected workspace: Uri, protected product: UnitTestProduct, - protected readonly outputChannel: vscode.OutputChannel, + protected readonly outputChannel: OutputChannel, protected installer: IInstaller, protected testConfigSettingsService: ITestConfigSettingsService) { } // tslint:disable-next-line:no-any public abstract configure(wkspace: Uri): Promise; public async enable() { + // Disable other test frameworks. + const testProducsToDisable = [Product.pytest, Product.unittest, Product.nosetest] + .filter(item => item !== this.product) as UnitTestProduct[]; + + for (const prod of testProducsToDisable) { + await this.testConfigSettingsService.disable(this.workspace, prod); + } + return this.testConfigSettingsService.enable(this.workspace, this.product); } // tslint:disable-next-line:no-any public async disable() { return this.testConfigSettingsService.enable(this.workspace, this.product); } - protected selectTestDir(rootDir: string, subDirs: string[], customOptions: vscode.QuickPickItem[] = []): Promise { + protected selectTestDir(rootDir: string, subDirs: string[], customOptions: QuickPickItem[] = []): Promise { const options = { matchOnDescription: true, matchOnDetail: true, placeHolder: 'Select the directory containing the unit tests' }; - let items: vscode.QuickPickItem[] = subDirs + let items: QuickPickItem[] = subDirs .map(dir => { const dirName = path.relative(rootDir, dir); if (dirName.indexOf('.') === 0) { return; } - return { + return { label: dirName, description: '' }; @@ -44,7 +51,7 @@ export abstract class TestConfigurationManager { items = [{ label: '.', description: 'Root directory' }, ...items]; items = customOptions.concat(items); const def = createDeferred(); - vscode.window.showQuickPick(items, options).then(item => { + window.showQuickPick(items, options).then(item => { if (!item) { return def.resolve(); } @@ -61,7 +68,7 @@ export abstract class TestConfigurationManager { matchOnDetail: true, placeHolder: 'Select the pattern to identify test files' }; - const items: vscode.QuickPickItem[] = [ + const items: QuickPickItem[] = [ { label: '*test.py', description: 'Python Files ending with \'test\'' }, { label: '*_test.py', description: 'Python Files ending with \'_test\'' }, { label: 'test*.py', description: 'Python Files begining with \'test\'' }, @@ -70,7 +77,7 @@ export abstract class TestConfigurationManager { ]; const def = createDeferred(); - vscode.window.showQuickPick(items, options).then(item => { + window.showQuickPick(items, options).then(item => { if (!item) { return def.resolve(); } diff --git a/src/test/interpreters/windowsRegistryService.test.ts b/src/test/interpreters/windowsRegistryService.test.ts deleted file mode 100644 index c06563cd0c8d..000000000000 --- a/src/test/interpreters/windowsRegistryService.test.ts +++ /dev/null @@ -1,293 +0,0 @@ -import * as assert from 'assert'; -import * as path from 'path'; -import * as TypeMoq from 'typemoq'; -import { Architecture, RegistryHive } from '../../client/common/platform/types'; -import { IPersistentStateFactory } from '../../client/common/types'; -import { IS_WINDOWS } from '../../client/debugger/Common/Utils'; -import { IInterpreterHelper } from '../../client/interpreter/contracts'; -import { WindowsRegistryService } from '../../client/interpreter/locators/services/windowsRegistryService'; -import { IServiceContainer } from '../../client/ioc/types'; -import { initialize, initializeTest } from '../initialize'; -import { MockRegistry, MockState } from './mocks'; - -const environmentsPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'environments'); - -// tslint:disable-next-line:max-func-body-length -suite('Interpreters from Windows Registry', () => { - let serviceContainer: TypeMoq.IMock; - suiteSetup(initialize); - setup(() => { - serviceContainer = TypeMoq.Mock.ofType(); - const stateFactory = TypeMoq.Mock.ofType(); - const interpreterHelper = TypeMoq.Mock.ofType(); - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPersistentStateFactory))).returns(() => stateFactory.object); - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInterpreterHelper))).returns(() => interpreterHelper.object); - const state = new MockState(undefined); - // tslint:disable-next-line:no-empty no-any - interpreterHelper.setup(h => h.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({} as any)); - stateFactory.setup(s => s.createGlobalPersistentState(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => state); - return initializeTest(); - }); - if (IS_WINDOWS) { - test('Must return an empty list (x86)', async () => { - const registry = new MockRegistry([], []); - const winRegistry = new WindowsRegistryService(registry, false, serviceContainer.object); - - const interpreters = await winRegistry.getInterpreters(); - assert.equal(interpreters.length, 0, 'Incorrect number of entries'); - }); - test('Must return an empty list (x64)', async () => { - const registry = new MockRegistry([], []); - const winRegistry = new WindowsRegistryService(registry, true, serviceContainer.object); - - const interpreters = await winRegistry.getInterpreters(); - assert.equal(interpreters.length, 0, 'Incorrect number of entries'); - }); - test('Must return a single entry', async () => { - const registryKeys = [ - { key: '\\Software\\Python', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One'] }, - { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One\\Tag1'] } - ]; - const registryValues = [ - { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Display Name for Company One', name: 'DisplayName' }, - { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path1') }, - { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path1', 'one.exe'), name: 'ExecutablePath' }, - { key: '\\Software\\Python\\Company One\\Tag1', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Version.Tag1', name: 'SysVersion' }, - { key: '\\Software\\Python\\Company One\\Tag1', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'DisplayName.Tag1', name: 'DisplayName' } - ]; - const registry = new MockRegistry(registryKeys, registryValues); - const winRegistry = new WindowsRegistryService(registry, false, serviceContainer.object); - - const interpreters = await winRegistry.getInterpreters(); - - assert.equal(interpreters.length, 1, 'Incorrect number of entries'); - assert.equal(interpreters[0].architecture, Architecture.x86, 'Incorrect arhictecture'); - assert.equal(interpreters[0].companyDisplayName, 'Display Name for Company One', 'Incorrect company name'); - assert.equal(interpreters[0].displayName, 'DisplayName.Tag1', 'Incorrect display name'); - assert.equal(interpreters[0].path, path.join(environmentsPath, 'path1', 'one.exe'), 'Incorrect executable path'); - assert.equal(interpreters[0].version, 'Version.Tag1', 'Incorrect version'); - }); - test('Must default names for PythonCore and exe', async () => { - const registryKeys = [ - { key: '\\Software\\Python', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\PythonCore'] }, - { key: '\\Software\\Python\\PythonCore', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\PythonCore\\Tag1'] } - ]; - const registryValues = [ - { key: '\\Software\\Python\\PythonCore\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path1') } - ]; - const registry = new MockRegistry(registryKeys, registryValues); - const winRegistry = new WindowsRegistryService(registry, false, serviceContainer.object); - - const interpreters = await winRegistry.getInterpreters(); - - assert.equal(interpreters.length, 1, 'Incorrect number of entries'); - assert.equal(interpreters[0].architecture, Architecture.x86, 'Incorrect arhictecture'); - assert.equal(interpreters[0].companyDisplayName, 'Python Software Foundation', 'Incorrect company name'); - assert.equal(interpreters[0].displayName, undefined, 'Incorrect display name'); - assert.equal(interpreters[0].path, path.join(environmentsPath, 'path1', 'python.exe'), 'Incorrect path'); - assert.equal(interpreters[0].version, 'Tag1', 'Incorrect version'); - }); - test('Must ignore company \'PyLauncher\'', async () => { - const registryKeys = [ - { key: '\\Software\\Python', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\PyLauncher'] }, - { key: '\\Software\\Python\\PythonCore', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\PyLauncher\\Tag1'] } - ]; - const registryValues = [ - { key: '\\Software\\Python\\PyLauncher\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'c:/temp/Install Path Tag1' } - ]; - const registry = new MockRegistry(registryKeys, registryValues); - const winRegistry = new WindowsRegistryService(registry, false, serviceContainer.object); - - const interpreters = await winRegistry.getInterpreters(); - - assert.equal(interpreters.length, 0, 'Incorrect number of entries'); - }); - test('Must return a single entry and when registry contains only the InstallPath', async () => { - const registryKeys = [ - { key: '\\Software\\Python', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One'] }, - { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One\\Tag1'] } - ]; - const registryValues = [ - { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path1') } - ]; - const registry = new MockRegistry(registryKeys, registryValues); - const winRegistry = new WindowsRegistryService(registry, false, serviceContainer.object); - - const interpreters = await winRegistry.getInterpreters(); - - assert.equal(interpreters.length, 1, 'Incorrect number of entries'); - assert.equal(interpreters[0].architecture, Architecture.x86, 'Incorrect arhictecture'); - assert.equal(interpreters[0].companyDisplayName, 'Company One', 'Incorrect company name'); - assert.equal(interpreters[0].displayName, undefined, 'Incorrect display name'); - assert.equal(interpreters[0].path, path.join(environmentsPath, 'path1', 'python.exe'), 'Incorrect path'); - assert.equal(interpreters[0].version, 'Tag1', 'Incorrect version'); - }); - test('Must return multiple entries', async () => { - const registryKeys = [ - { key: '\\Software\\Python', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One', '\\Software\\Python\\Company Two', '\\Software\\Python\\Company Three'] }, - { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One\\Tag1', '\\Software\\Python\\Company One\\Tag2'] }, - { key: '\\Software\\Python\\Company Two', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Two\\Tag A', '\\Software\\Python\\Company Two\\Tag B', '\\Software\\Python\\Company Two\\Tag C'] }, - { key: '\\Software\\Python\\Company Three', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Three\\Tag !'] }, - { key: '\\Software\\Python', hive: RegistryHive.HKLM, arch: Architecture.x86, values: ['A'] }, - { key: '\\Software\\Python\\Company A', hive: RegistryHive.HKLM, arch: Architecture.x86, values: ['Another Tag'] } - ]; - const registryValues = [ - { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Display Name for Company One', name: 'DisplayName' }, - { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path1') }, - { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path1', 'python.exe'), name: 'ExecutablePath' }, - { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path2'), name: 'SysVersion' }, - { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'DisplayName.Tag1', name: 'DisplayName' }, - - { key: '\\Software\\Python\\Company One\\Tag2\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path2') }, - { key: '\\Software\\Python\\Company One\\Tag2\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path2', 'python.exe'), name: 'ExecutablePath' }, - - { key: '\\Software\\Python\\Company Two\\Tag A\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path3') }, - { key: '\\Software\\Python\\Company Two\\Tag A\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Version.Tag A', name: 'SysVersion' }, - - { key: '\\Software\\Python\\Company Two\\Tag B\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, - { key: '\\Software\\Python\\Company Two\\Tag B\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'DisplayName.Tag B', name: 'DisplayName' }, - { key: '\\Software\\Python\\Company Two\\Tag C\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'scipy') }, - - { key: '\\Software\\Python\\Company Three\\Tag !\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, - - { key: '\\Software\\Python\\Company A\\Another Tag\\InstallPath', hive: RegistryHive.HKLM, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'scipy', 'python.exe') } - ]; - const registry = new MockRegistry(registryKeys, registryValues); - const winRegistry = new WindowsRegistryService(registry, false, serviceContainer.object); - - const interpreters = await winRegistry.getInterpreters(); - - assert.equal(interpreters.length, 4, 'Incorrect number of entries'); - assert.equal(interpreters[0].architecture, Architecture.x86, 'Incorrect arhictecture'); - assert.equal(interpreters[0].companyDisplayName, 'Display Name for Company One', 'Incorrect company name'); - assert.equal(interpreters[0].displayName, undefined, 'Incorrect display name'); - assert.equal(interpreters[0].path, path.join(environmentsPath, 'path1', 'python.exe'), 'Incorrect path'); - assert.equal(interpreters[0].version, 'Tag1', 'Incorrect version'); - - assert.equal(interpreters[1].architecture, Architecture.x86, 'Incorrect arhictecture'); - assert.equal(interpreters[1].companyDisplayName, 'Display Name for Company One', 'Incorrect company name'); - assert.equal(interpreters[1].displayName, undefined, 'Incorrect display name'); - assert.equal(interpreters[1].path, path.join(environmentsPath, 'path2', 'python.exe'), 'Incorrect path'); - assert.equal(interpreters[1].version, 'Tag2', 'Incorrect version'); - - assert.equal(interpreters[2].architecture, Architecture.x86, 'Incorrect arhictecture'); - assert.equal(interpreters[2].companyDisplayName, 'Company Two', 'Incorrect company name'); - assert.equal(interpreters[2].displayName, undefined, 'Incorrect display name'); - assert.equal(interpreters[2].path, path.join(environmentsPath, 'conda', 'envs', 'numpy', 'python.exe'), 'Incorrect path'); - assert.equal(interpreters[2].version, 'Tag B', 'Incorrect version'); - }); - test('Must return multiple entries excluding the invalid registry items and duplicate paths', async () => { - const registryKeys = [ - { key: '\\Software\\Python', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One', '\\Software\\Python\\Company Two', '\\Software\\Python\\Company Three', '\\Software\\Python\\Company Four', '\\Software\\Python\\Company Five', 'Missing Tag'] }, - { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One\\Tag1', '\\Software\\Python\\Company One\\Tag2'] }, - { key: '\\Software\\Python\\Company Two', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Two\\Tag A', '\\Software\\Python\\Company Two\\Tag B', '\\Software\\Python\\Company Two\\Tag C'] }, - { key: '\\Software\\Python\\Company Three', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Three\\Tag !'] }, - { key: '\\Software\\Python\\Company Four', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Four\\Four !'] }, - { key: '\\Software\\Python\\Company Five', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Five\\Five !'] }, - { key: '\\Software\\Python', hive: RegistryHive.HKLM, arch: Architecture.x86, values: ['A'] }, - { key: '\\Software\\Python\\Company A', hive: RegistryHive.HKLM, arch: Architecture.x86, values: ['Another Tag'] } - ]; - const registryValues: { key: string; hive: RegistryHive; arch?: Architecture; value: string; name?: string }[] = [ - { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Display Name for Company One', name: 'DisplayName' }, - { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, - { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy', 'python.exe'), name: 'ExecutablePath' }, - { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Version.Tag1', name: 'SysVersion' }, - { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'DisplayName.Tag1', name: 'DisplayName' }, - - { key: '\\Software\\Python\\Company One\\Tag2\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'scipy') }, - { key: '\\Software\\Python\\Company One\\Tag2\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'scipy', 'python.exe'), name: 'ExecutablePath' }, - - { key: '\\Software\\Python\\Company Two\\Tag A\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path1') }, - { key: '\\Software\\Python\\Company Two\\Tag A\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Version.Tag A', name: 'SysVersion' }, - - { key: '\\Software\\Python\\Company Two\\Tag B\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path2') }, - { key: '\\Software\\Python\\Company Two\\Tag B\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'DisplayName.Tag B', name: 'DisplayName' }, - { key: '\\Software\\Python\\Company Two\\Tag C\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, - - // tslint:disable-next-line:no-any - { key: '\\Software\\Python\\Company Five\\Five !\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: undefined }, - - { key: '\\Software\\Python\\Company Three\\Tag !\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, - - { key: '\\Software\\Python\\Company A\\Another Tag\\InstallPath', hive: RegistryHive.HKLM, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') } - ]; - const registry = new MockRegistry(registryKeys, registryValues); - const winRegistry = new WindowsRegistryService(registry, false, serviceContainer.object); - - const interpreters = await winRegistry.getInterpreters(); - - assert.equal(interpreters.length, 4, 'Incorrect number of entries'); - assert.equal(interpreters[0].architecture, Architecture.x86, 'Incorrect arhictecture'); - assert.equal(interpreters[0].companyDisplayName, 'Display Name for Company One', 'Incorrect company name'); - assert.equal(interpreters[0].displayName, undefined, 'Incorrect display name'); - assert.equal(interpreters[0].path, path.join(environmentsPath, 'conda', 'envs', 'numpy', 'python.exe'), 'Incorrect path'); - assert.equal(interpreters[0].version, 'Tag1', 'Incorrect version'); - - assert.equal(interpreters[1].architecture, Architecture.x86, 'Incorrect arhictecture'); - assert.equal(interpreters[1].companyDisplayName, 'Display Name for Company One', 'Incorrect company name'); - assert.equal(interpreters[1].displayName, undefined, 'Incorrect display name'); - assert.equal(interpreters[1].path, path.join(environmentsPath, 'conda', 'envs', 'scipy', 'python.exe'), 'Incorrect path'); - assert.equal(interpreters[1].version, 'Tag2', 'Incorrect version'); - - assert.equal(interpreters[2].architecture, Architecture.x86, 'Incorrect arhictecture'); - assert.equal(interpreters[2].companyDisplayName, 'Company Two', 'Incorrect company name'); - assert.equal(interpreters[2].displayName, undefined, 'Incorrect display name'); - assert.equal(interpreters[2].path, path.join(environmentsPath, 'path1', 'python.exe'), 'Incorrect path'); - assert.equal(interpreters[2].version, 'Tag A', 'Incorrect version'); - }); - test('Must return multiple entries excluding the invalid registry items and nonexistent paths', async () => { - const registryKeys = [ - { key: '\\Software\\Python', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One', '\\Software\\Python\\Company Two', '\\Software\\Python\\Company Three', '\\Software\\Python\\Company Four', '\\Software\\Python\\Company Five', 'Missing Tag'] }, - { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One\\Tag1', '\\Software\\Python\\Company One\\Tag2'] }, - { key: '\\Software\\Python\\Company Two', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Two\\Tag A', '\\Software\\Python\\Company Two\\Tag B', '\\Software\\Python\\Company Two\\Tag C'] }, - { key: '\\Software\\Python\\Company Three', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Three\\Tag !'] }, - { key: '\\Software\\Python\\Company Four', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Four\\Four !'] }, - { key: '\\Software\\Python\\Company Five', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Five\\Five !'] }, - { key: '\\Software\\Python', hive: RegistryHive.HKLM, arch: Architecture.x86, values: ['A'] }, - { key: '\\Software\\Python\\Company A', hive: RegistryHive.HKLM, arch: Architecture.x86, values: ['Another Tag'] } - ]; - const registryValues: { key: string; hive: RegistryHive; arch?: Architecture; value: string; name?: string }[] = [ - { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Display Name for Company One', name: 'DisplayName' }, - { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, - { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy', 'python.exe'), name: 'ExecutablePath' }, - { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Version.Tag1', name: 'SysVersion' }, - { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'DisplayName.Tag1', name: 'DisplayName' }, - - { key: '\\Software\\Python\\Company One\\Tag2\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'non-existent-path', 'envs', 'scipy') }, - { key: '\\Software\\Python\\Company One\\Tag2\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'non-existent-path', 'envs', 'scipy', 'python.exe'), name: 'ExecutablePath' }, - - { key: '\\Software\\Python\\Company Two\\Tag A\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'non-existent-path') }, - { key: '\\Software\\Python\\Company Two\\Tag A\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Version.Tag A', name: 'SysVersion' }, - - { key: '\\Software\\Python\\Company Two\\Tag B\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path2') }, - { key: '\\Software\\Python\\Company Two\\Tag B\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'DisplayName.Tag B', name: 'DisplayName' }, - { key: '\\Software\\Python\\Company Two\\Tag C\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'non-existent-path', 'envs', 'numpy') }, - - // tslint:disable-next-line:no-any - { key: '\\Software\\Python\\Company Five\\Five !\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: undefined }, - - { key: '\\Software\\Python\\Company Three\\Tag !\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'non-existent-path', 'envs', 'numpy') }, - - { key: '\\Software\\Python\\Company A\\Another Tag\\InstallPath', hive: RegistryHive.HKLM, arch: Architecture.x86, value: path.join(environmentsPath, 'non-existent-path', 'envs', 'numpy') } - ]; - const registry = new MockRegistry(registryKeys, registryValues); - const winRegistry = new WindowsRegistryService(registry, false, serviceContainer.object); - - const interpreters = await winRegistry.getInterpreters(); - - assert.equal(interpreters.length, 2, 'Incorrect number of entries'); - - assert.equal(interpreters[0].architecture, Architecture.x86, '1. Incorrect arhictecture'); - assert.equal(interpreters[0].companyDisplayName, 'Display Name for Company One', '1. Incorrect company name'); - assert.equal(interpreters[0].displayName, undefined, '1. Incorrect display name'); - assert.equal(interpreters[0].path, path.join(environmentsPath, 'conda', 'envs', 'numpy', 'python.exe'), '1. Incorrect path'); - assert.equal(interpreters[0].version, 'Tag1', '1. Incorrect version'); - - assert.equal(interpreters[1].architecture, Architecture.x86, '2. Incorrect arhictecture'); - assert.equal(interpreters[1].companyDisplayName, 'Company Two', '2. Incorrect company name'); - assert.equal(interpreters[1].displayName, undefined, '2. Incorrect display name'); - assert.equal(interpreters[1].path, path.join(environmentsPath, 'path2', 'python.exe'), '2. Incorrect path'); - assert.equal(interpreters[1].version, 'Tag B', '2. Incorrect version'); - }); - } -}); diff --git a/src/test/interpreters/windowsRegistryService.unit.test.ts b/src/test/interpreters/windowsRegistryService.unit.test.ts new file mode 100644 index 000000000000..15a11aec7401 --- /dev/null +++ b/src/test/interpreters/windowsRegistryService.unit.test.ts @@ -0,0 +1,290 @@ +import * as assert from 'assert'; +import * as path from 'path'; +import * as TypeMoq from 'typemoq'; +import { Architecture, RegistryHive } from '../../client/common/platform/types'; +import { IPathUtils, IPersistentStateFactory } from '../../client/common/types'; +import { IInterpreterHelper } from '../../client/interpreter/contracts'; +import { WindowsRegistryService } from '../../client/interpreter/locators/services/windowsRegistryService'; +import { IServiceContainer } from '../../client/ioc/types'; +import { MockRegistry, MockState } from './mocks'; + +const environmentsPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'environments'); + +// tslint:disable-next-line:max-func-body-length +suite('Interpreters from Windows Registry (unit)', () => { + let serviceContainer: TypeMoq.IMock; + setup(() => { + serviceContainer = TypeMoq.Mock.ofType(); + const stateFactory = TypeMoq.Mock.ofType(); + const interpreterHelper = TypeMoq.Mock.ofType(); + const pathUtils = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPersistentStateFactory))).returns(() => stateFactory.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInterpreterHelper))).returns(() => interpreterHelper.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPathUtils))).returns(() => pathUtils.object); + pathUtils.setup(p => p.basename(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((p: string) => p.split(/[\\,\/]/).reverse()[0]); + const state = new MockState(undefined); + // tslint:disable-next-line:no-empty no-any + interpreterHelper.setup(h => h.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({} as any)); + stateFactory.setup(s => s.createGlobalPersistentState(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => state); + }); + test('Must return an empty list (x86)', async () => { + const registry = new MockRegistry([], []); + const winRegistry = new WindowsRegistryService(registry, false, serviceContainer.object); + + const interpreters = await winRegistry.getInterpreters(); + assert.equal(interpreters.length, 0, 'Incorrect number of entries'); + }); + test('Must return an empty list (x64)', async () => { + const registry = new MockRegistry([], []); + const winRegistry = new WindowsRegistryService(registry, true, serviceContainer.object); + + const interpreters = await winRegistry.getInterpreters(); + assert.equal(interpreters.length, 0, 'Incorrect number of entries'); + }); + test('Must return a single entry', async () => { + const registryKeys = [ + { key: '\\Software\\Python', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One'] }, + { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One\\Tag1'] } + ]; + const registryValues = [ + { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Display Name for Company One', name: 'DisplayName' }, + { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path1') }, + { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path1', 'one.exe'), name: 'ExecutablePath' }, + { key: '\\Software\\Python\\Company One\\Tag1', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Version.Tag1', name: 'SysVersion' }, + { key: '\\Software\\Python\\Company One\\Tag1', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'DisplayName.Tag1', name: 'DisplayName' } + ]; + const registry = new MockRegistry(registryKeys, registryValues); + const winRegistry = new WindowsRegistryService(registry, false, serviceContainer.object); + + const interpreters = await winRegistry.getInterpreters(); + + assert.equal(interpreters.length, 1, 'Incorrect number of entries'); + assert.equal(interpreters[0].architecture, Architecture.x86, 'Incorrect arhictecture'); + assert.equal(interpreters[0].companyDisplayName, 'Display Name for Company One', 'Incorrect company name'); + assert.equal(interpreters[0].displayName, 'DisplayName.Tag1', 'Incorrect display name'); + assert.equal(interpreters[0].path, path.join(environmentsPath, 'path1', 'one.exe'), 'Incorrect executable path'); + assert.equal(interpreters[0].version, 'Version.Tag1', 'Incorrect version'); + }); + test('Must default names for PythonCore and exe', async () => { + const registryKeys = [ + { key: '\\Software\\Python', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\PythonCore'] }, + { key: '\\Software\\Python\\PythonCore', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\PythonCore\\Tag1'] } + ]; + const registryValues = [ + { key: '\\Software\\Python\\PythonCore\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path1') } + ]; + const registry = new MockRegistry(registryKeys, registryValues); + const winRegistry = new WindowsRegistryService(registry, false, serviceContainer.object); + + const interpreters = await winRegistry.getInterpreters(); + + assert.equal(interpreters.length, 1, 'Incorrect number of entries'); + assert.equal(interpreters[0].architecture, Architecture.x86, 'Incorrect arhictecture'); + assert.equal(interpreters[0].companyDisplayName, 'Python Software Foundation', 'Incorrect company name'); + assert.equal(interpreters[0].displayName, undefined, 'Incorrect display name'); + assert.equal(interpreters[0].path, path.join(environmentsPath, 'path1', 'python.exe'), 'Incorrect path'); + assert.equal(interpreters[0].version, 'Tag1', 'Incorrect version'); + }); + test('Must ignore company \'PyLauncher\'', async () => { + const registryKeys = [ + { key: '\\Software\\Python', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\PyLauncher'] }, + { key: '\\Software\\Python\\PythonCore', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\PyLauncher\\Tag1'] } + ]; + const registryValues = [ + { key: '\\Software\\Python\\PyLauncher\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'c:/temp/Install Path Tag1' } + ]; + const registry = new MockRegistry(registryKeys, registryValues); + const winRegistry = new WindowsRegistryService(registry, false, serviceContainer.object); + + const interpreters = await winRegistry.getInterpreters(); + + assert.equal(interpreters.length, 0, 'Incorrect number of entries'); + }); + test('Must return a single entry and when registry contains only the InstallPath', async () => { + const registryKeys = [ + { key: '\\Software\\Python', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One'] }, + { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One\\Tag1'] } + ]; + const registryValues = [ + { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path1') } + ]; + const registry = new MockRegistry(registryKeys, registryValues); + const winRegistry = new WindowsRegistryService(registry, false, serviceContainer.object); + + const interpreters = await winRegistry.getInterpreters(); + + assert.equal(interpreters.length, 1, 'Incorrect number of entries'); + assert.equal(interpreters[0].architecture, Architecture.x86, 'Incorrect arhictecture'); + assert.equal(interpreters[0].companyDisplayName, 'Company One', 'Incorrect company name'); + assert.equal(interpreters[0].displayName, undefined, 'Incorrect display name'); + assert.equal(interpreters[0].path, path.join(environmentsPath, 'path1', 'python.exe'), 'Incorrect path'); + assert.equal(interpreters[0].version, 'Tag1', 'Incorrect version'); + }); + test('Must return multiple entries', async () => { + const registryKeys = [ + { key: '\\Software\\Python', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One', '\\Software\\Python\\Company Two', '\\Software\\Python\\Company Three'] }, + { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One\\Tag1', '\\Software\\Python\\Company One\\Tag2'] }, + { key: '\\Software\\Python\\Company Two', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Two\\Tag A', '\\Software\\Python\\Company Two\\Tag B', '\\Software\\Python\\Company Two\\Tag C'] }, + { key: '\\Software\\Python\\Company Three', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Three\\Tag !'] }, + { key: '\\Software\\Python', hive: RegistryHive.HKLM, arch: Architecture.x86, values: ['A'] }, + { key: '\\Software\\Python\\Company A', hive: RegistryHive.HKLM, arch: Architecture.x86, values: ['Another Tag'] } + ]; + const registryValues = [ + { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Display Name for Company One', name: 'DisplayName' }, + { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path1') }, + { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path1', 'python.exe'), name: 'ExecutablePath' }, + { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path2'), name: 'SysVersion' }, + { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'DisplayName.Tag1', name: 'DisplayName' }, + + { key: '\\Software\\Python\\Company One\\Tag2\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path2') }, + { key: '\\Software\\Python\\Company One\\Tag2\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path2', 'python.exe'), name: 'ExecutablePath' }, + + { key: '\\Software\\Python\\Company Two\\Tag A\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path3') }, + { key: '\\Software\\Python\\Company Two\\Tag A\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Version.Tag A', name: 'SysVersion' }, + + { key: '\\Software\\Python\\Company Two\\Tag B\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, + { key: '\\Software\\Python\\Company Two\\Tag B\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'DisplayName.Tag B', name: 'DisplayName' }, + { key: '\\Software\\Python\\Company Two\\Tag C\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'scipy') }, + + { key: '\\Software\\Python\\Company Three\\Tag !\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, + + { key: '\\Software\\Python\\Company A\\Another Tag\\InstallPath', hive: RegistryHive.HKLM, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'scipy', 'python.exe') } + ]; + const registry = new MockRegistry(registryKeys, registryValues); + const winRegistry = new WindowsRegistryService(registry, false, serviceContainer.object); + + const interpreters = await winRegistry.getInterpreters(); + + assert.equal(interpreters.length, 4, 'Incorrect number of entries'); + assert.equal(interpreters[0].architecture, Architecture.x86, 'Incorrect arhictecture'); + assert.equal(interpreters[0].companyDisplayName, 'Display Name for Company One', 'Incorrect company name'); + assert.equal(interpreters[0].displayName, undefined, 'Incorrect display name'); + assert.equal(interpreters[0].path, path.join(environmentsPath, 'path1', 'python.exe'), 'Incorrect path'); + assert.equal(interpreters[0].version, 'Tag1', 'Incorrect version'); + + assert.equal(interpreters[1].architecture, Architecture.x86, 'Incorrect arhictecture'); + assert.equal(interpreters[1].companyDisplayName, 'Display Name for Company One', 'Incorrect company name'); + assert.equal(interpreters[1].displayName, undefined, 'Incorrect display name'); + assert.equal(interpreters[1].path, path.join(environmentsPath, 'path2', 'python.exe'), 'Incorrect path'); + assert.equal(interpreters[1].version, 'Tag2', 'Incorrect version'); + + assert.equal(interpreters[2].architecture, Architecture.x86, 'Incorrect arhictecture'); + assert.equal(interpreters[2].companyDisplayName, 'Company Two', 'Incorrect company name'); + assert.equal(interpreters[2].displayName, undefined, 'Incorrect display name'); + assert.equal(interpreters[2].path, path.join(environmentsPath, 'conda', 'envs', 'numpy', 'python.exe'), 'Incorrect path'); + assert.equal(interpreters[2].version, 'Tag B', 'Incorrect version'); + }); + test('Must return multiple entries excluding the invalid registry items and duplicate paths', async () => { + const registryKeys = [ + { key: '\\Software\\Python', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One', '\\Software\\Python\\Company Two', '\\Software\\Python\\Company Three', '\\Software\\Python\\Company Four', '\\Software\\Python\\Company Five', 'Missing Tag'] }, + { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One\\Tag1', '\\Software\\Python\\Company One\\Tag2'] }, + { key: '\\Software\\Python\\Company Two', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Two\\Tag A', '\\Software\\Python\\Company Two\\Tag B', '\\Software\\Python\\Company Two\\Tag C'] }, + { key: '\\Software\\Python\\Company Three', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Three\\Tag !'] }, + { key: '\\Software\\Python\\Company Four', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Four\\Four !'] }, + { key: '\\Software\\Python\\Company Five', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Five\\Five !'] }, + { key: '\\Software\\Python', hive: RegistryHive.HKLM, arch: Architecture.x86, values: ['A'] }, + { key: '\\Software\\Python\\Company A', hive: RegistryHive.HKLM, arch: Architecture.x86, values: ['Another Tag'] } + ]; + const registryValues: { key: string; hive: RegistryHive; arch?: Architecture; value: string; name?: string }[] = [ + { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Display Name for Company One', name: 'DisplayName' }, + { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, + { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy', 'python.exe'), name: 'ExecutablePath' }, + { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Version.Tag1', name: 'SysVersion' }, + { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'DisplayName.Tag1', name: 'DisplayName' }, + + { key: '\\Software\\Python\\Company One\\Tag2\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'scipy') }, + { key: '\\Software\\Python\\Company One\\Tag2\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'scipy', 'python.exe'), name: 'ExecutablePath' }, + + { key: '\\Software\\Python\\Company Two\\Tag A\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path1') }, + { key: '\\Software\\Python\\Company Two\\Tag A\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Version.Tag A', name: 'SysVersion' }, + + { key: '\\Software\\Python\\Company Two\\Tag B\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path2') }, + { key: '\\Software\\Python\\Company Two\\Tag B\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'DisplayName.Tag B', name: 'DisplayName' }, + { key: '\\Software\\Python\\Company Two\\Tag C\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, + + // tslint:disable-next-line:no-any + { key: '\\Software\\Python\\Company Five\\Five !\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: undefined }, + + { key: '\\Software\\Python\\Company Three\\Tag !\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, + + { key: '\\Software\\Python\\Company A\\Another Tag\\InstallPath', hive: RegistryHive.HKLM, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') } + ]; + const registry = new MockRegistry(registryKeys, registryValues); + const winRegistry = new WindowsRegistryService(registry, false, serviceContainer.object); + + const interpreters = await winRegistry.getInterpreters(); + + assert.equal(interpreters.length, 4, 'Incorrect number of entries'); + assert.equal(interpreters[0].architecture, Architecture.x86, 'Incorrect arhictecture'); + assert.equal(interpreters[0].companyDisplayName, 'Display Name for Company One', 'Incorrect company name'); + assert.equal(interpreters[0].displayName, undefined, 'Incorrect display name'); + assert.equal(interpreters[0].path, path.join(environmentsPath, 'conda', 'envs', 'numpy', 'python.exe'), 'Incorrect path'); + assert.equal(interpreters[0].version, 'Tag1', 'Incorrect version'); + + assert.equal(interpreters[1].architecture, Architecture.x86, 'Incorrect arhictecture'); + assert.equal(interpreters[1].companyDisplayName, 'Display Name for Company One', 'Incorrect company name'); + assert.equal(interpreters[1].displayName, undefined, 'Incorrect display name'); + assert.equal(interpreters[1].path, path.join(environmentsPath, 'conda', 'envs', 'scipy', 'python.exe'), 'Incorrect path'); + assert.equal(interpreters[1].version, 'Tag2', 'Incorrect version'); + + assert.equal(interpreters[2].architecture, Architecture.x86, 'Incorrect arhictecture'); + assert.equal(interpreters[2].companyDisplayName, 'Company Two', 'Incorrect company name'); + assert.equal(interpreters[2].displayName, undefined, 'Incorrect display name'); + assert.equal(interpreters[2].path, path.join(environmentsPath, 'path1', 'python.exe'), 'Incorrect path'); + assert.equal(interpreters[2].version, 'Tag A', 'Incorrect version'); + }); + test('Must return multiple entries excluding the invalid registry items and nonexistent paths', async () => { + const registryKeys = [ + { key: '\\Software\\Python', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One', '\\Software\\Python\\Company Two', '\\Software\\Python\\Company Three', '\\Software\\Python\\Company Four', '\\Software\\Python\\Company Five', 'Missing Tag'] }, + { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company One\\Tag1', '\\Software\\Python\\Company One\\Tag2'] }, + { key: '\\Software\\Python\\Company Two', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Two\\Tag A', '\\Software\\Python\\Company Two\\Tag B', '\\Software\\Python\\Company Two\\Tag C'] }, + { key: '\\Software\\Python\\Company Three', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Three\\Tag !'] }, + { key: '\\Software\\Python\\Company Four', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Four\\Four !'] }, + { key: '\\Software\\Python\\Company Five', hive: RegistryHive.HKCU, arch: Architecture.x86, values: ['\\Software\\Python\\Company Five\\Five !'] }, + { key: '\\Software\\Python', hive: RegistryHive.HKLM, arch: Architecture.x86, values: ['A'] }, + { key: '\\Software\\Python\\Company A', hive: RegistryHive.HKLM, arch: Architecture.x86, values: ['Another Tag'] } + ]; + const registryValues: { key: string; hive: RegistryHive; arch?: Architecture; value: string; name?: string }[] = [ + { key: '\\Software\\Python\\Company One', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Display Name for Company One', name: 'DisplayName' }, + { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy') }, + { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'conda', 'envs', 'numpy', 'python.exe'), name: 'ExecutablePath' }, + { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Version.Tag1', name: 'SysVersion' }, + { key: '\\Software\\Python\\Company One\\Tag1\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'DisplayName.Tag1', name: 'DisplayName' }, + + { key: '\\Software\\Python\\Company One\\Tag2\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'non-existent-path', 'envs', 'scipy') }, + { key: '\\Software\\Python\\Company One\\Tag2\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'non-existent-path', 'envs', 'scipy', 'python.exe'), name: 'ExecutablePath' }, + + { key: '\\Software\\Python\\Company Two\\Tag A\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'non-existent-path') }, + { key: '\\Software\\Python\\Company Two\\Tag A\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'Version.Tag A', name: 'SysVersion' }, + + { key: '\\Software\\Python\\Company Two\\Tag B\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'path2') }, + { key: '\\Software\\Python\\Company Two\\Tag B\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: 'DisplayName.Tag B', name: 'DisplayName' }, + { key: '\\Software\\Python\\Company Two\\Tag C\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'non-existent-path', 'envs', 'numpy') }, + + // tslint:disable-next-line:no-any + { key: '\\Software\\Python\\Company Five\\Five !\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: undefined }, + + { key: '\\Software\\Python\\Company Three\\Tag !\\InstallPath', hive: RegistryHive.HKCU, arch: Architecture.x86, value: path.join(environmentsPath, 'non-existent-path', 'envs', 'numpy') }, + + { key: '\\Software\\Python\\Company A\\Another Tag\\InstallPath', hive: RegistryHive.HKLM, arch: Architecture.x86, value: path.join(environmentsPath, 'non-existent-path', 'envs', 'numpy') } + ]; + const registry = new MockRegistry(registryKeys, registryValues); + const winRegistry = new WindowsRegistryService(registry, false, serviceContainer.object); + + const interpreters = await winRegistry.getInterpreters(); + + assert.equal(interpreters.length, 2, 'Incorrect number of entries'); + + assert.equal(interpreters[0].architecture, Architecture.x86, '1. Incorrect arhictecture'); + assert.equal(interpreters[0].companyDisplayName, 'Display Name for Company One', '1. Incorrect company name'); + assert.equal(interpreters[0].displayName, undefined, '1. Incorrect display name'); + assert.equal(interpreters[0].path, path.join(environmentsPath, 'conda', 'envs', 'numpy', 'python.exe'), '1. Incorrect path'); + assert.equal(interpreters[0].version, 'Tag1', '1. Incorrect version'); + + assert.equal(interpreters[1].architecture, Architecture.x86, '2. Incorrect arhictecture'); + assert.equal(interpreters[1].companyDisplayName, 'Company Two', '2. Incorrect company name'); + assert.equal(interpreters[1].displayName, undefined, '2. Incorrect display name'); + assert.equal(interpreters[1].path, path.join(environmentsPath, 'path2', 'python.exe'), '2. Incorrect path'); + assert.equal(interpreters[1].version, 'Tag B', '2. Incorrect version'); + }); +}); diff --git a/src/test/providers/completionSource.test.ts b/src/test/providers/completionSource.test.ts index 0c64677849c8..cb6f3578aaf4 100644 --- a/src/test/providers/completionSource.test.ts +++ b/src/test/providers/completionSource.test.ts @@ -66,7 +66,7 @@ suite('Completion Provider', () => { return Promise.resolve(completionResult.object); }); - const expectedSource = `${source}${autoCompleteItems[0].text}${addBrackets ? '($)' : ''}`; + const expectedSource = `${source}${autoCompleteItems[0].text}${addBrackets ? '($1)' : ''}`; itemInfoSource.setup(i => i.getItemInfoFromText(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), expectedSource, TypeMoq.It.isAny())) .returns(() => Promise.resolve(undefined)) diff --git a/src/test/unittests.ts b/src/test/unittests.ts new file mode 100644 index 000000000000..1b6a6b6f0f6d --- /dev/null +++ b/src/test/unittests.ts @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:no-any no-require-imports no-var-requires + +if ((Reflect as any).metadata === undefined) { + require('reflect-metadata'); +} +import * as glob from 'glob'; +import * as Mocha from 'mocha'; +import * as path from 'path'; +import { MochaSetupOptions } from 'vscode/lib/testrunner'; +import * as vscodeMoscks from './vscode-mock'; + +export function runTests(testOptions?: { grep?: string; timeout?: number }) { + vscodeMoscks.initialize(); + + const grep: string | undefined = testOptions ? testOptions.grep : undefined; + const timeout: number | undefined = testOptions ? testOptions.timeout : undefined; + const options: MochaSetupOptions = { + ui: 'tdd', + useColors: true, + timeout, + grep + }; + const mocha = new Mocha(options); + require('source-map-support').install(); + const testsRoot = __dirname; + glob('**/**.unit.test.js', { cwd: testsRoot }, (error, files) => { + if (error) { + return reportErrors(error); + } + try { + files.forEach(file => mocha.addFile(path.join(testsRoot, file))); + mocha.run(failures => { + if (failures === 0) { + return; + } + reportErrors(undefined, failures); + }); + } catch (error) { + reportErrors(error); + } + }); +} +function reportErrors(error?: Error, failures?: number) { + let failed = false; + if (error) { + console.error(error); + failed = true; + } + if (failures && failures >= 0) { + console.error(`${failures} failed tests 👎.`); + failed = true; + } + if (failed) { + process.exit(1); + } +} +// this allows us to run hygiene as a git pre-commit hook or via debugger. +if (require.main === module) { + // When running from debugger, allow custom args. + const args = process.argv0.length > 2 ? process.argv.slice(2) : []; + const timeoutArgIndex = args.findIndex(arg => arg.startsWith('timeout=')); + const grepArgIndex = args.findIndex(arg => arg.startsWith('grep=')); + const timeout: number | undefined = timeoutArgIndex >= 0 ? parseInt(args[timeoutArgIndex].split('=')[1].trim(), 10) : undefined; + let grep: string | undefined = timeoutArgIndex >= 0 ? args[grepArgIndex].split('=')[1].trim() : undefined; + grep = grep && grep.length > 0 ? grep : undefined; + + runTests({ grep, timeout }); +} diff --git a/src/test/unittests/common/managers/testConfigurationManager.unit.test.ts b/src/test/unittests/common/managers/testConfigurationManager.unit.test.ts new file mode 100644 index 000000000000..fac9e17b5235 --- /dev/null +++ b/src/test/unittests/common/managers/testConfigurationManager.unit.test.ts @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:no-any + +import * as TypeMoq from 'typemoq'; +import { OutputChannel } from 'vscode'; +import { EnumEx } from '../../../../client/common/enumUtils'; +import { IInstaller, Product } from '../../../../client/common/types'; +import { TestConfigurationManager } from '../../../../client/unittests/common/managers/testConfigurationManager'; +import { ITestConfigSettingsService, UnitTestProduct } from '../../../../client/unittests/common/types'; +import { Uri } from '../../../vscode-mock'; + +class MockTestConfigurationManager extends TestConfigurationManager { + public configure(wkspace: any): Promise { + throw new Error('Method not implemented.'); + } +} + +suite('Unit Test Configuration Manager (unit)', () => { + [Product.pytest, Product.unittest, Product.nosetest].forEach(product => { + const prods = EnumEx.getNamesAndValues(Product); + const productName = prods.filter(item => item.value === product)[0]; + suite(productName.name, () => { + const workspaceUri = Uri.file(__dirname); + let manager: TestConfigurationManager; + let configService: TypeMoq.IMock; + + setup(() => { + configService = TypeMoq.Mock.ofType(); + const outputChannel = TypeMoq.Mock.ofType().object; + const installer = TypeMoq.Mock.ofType().object; + + manager = new MockTestConfigurationManager(workspaceUri, product as UnitTestProduct, + outputChannel, installer, configService.object); + }); + + test('Enabling a test product shoud disable other products', async () => { + const testProducsToDisable = [Product.pytest, Product.unittest, Product.nosetest] + .filter(item => item !== product) as UnitTestProduct[]; + testProducsToDisable.forEach(productToDisable => { + configService.setup(c => c.disable(TypeMoq.It.isValue(workspaceUri), + TypeMoq.It.isValue(productToDisable))) + .returns(() => Promise.resolve(undefined)) + .verifiable(TypeMoq.Times.once()); + }); + configService.setup(c => c.enable(TypeMoq.It.isValue(workspaceUri), + TypeMoq.It.isValue(product as UnitTestProduct))) + .returns(() => Promise.resolve(undefined)) + .verifiable(TypeMoq.Times.once()); + + await manager.enable(); + configService.verifyAll(); + }); + }); + }); +}); diff --git a/src/test/vscode-mock.ts b/src/test/vscode-mock.ts new file mode 100644 index 000000000000..0e150c2aef1b --- /dev/null +++ b/src/test/vscode-mock.ts @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:no-invalid-this no-require-imports no-var-requires no-any + +import * as TypeMoq from 'typemoq'; +import * as vscode from 'vscode'; +const Module = require('module'); + +type VSCode = typeof vscode; + +const mockedVSCode: Partial = {}; +const mockedVSCodeNamespaces: { [P in keyof VSCode]?: TypeMoq.IMock } = {}; +const originalLoad = Module._load; + +generateMock('workspace'); +generateMock('window'); +generateMock('commands'); +generateMock('languages'); +generateMock('env'); +generateMock('debug'); +generateMock('extensions'); +generateMock('scm'); + +function generateMock(name: K): void { + const mockedObj = TypeMoq.Mock.ofType(); + mockedVSCode[name] = mockedObj.object; + mockedVSCodeNamespaces[name] = mockedObj as any; +} + +export function initialize() { + Module._load = function (request, parent) { + if (request === 'vscode') { + return mockedVSCode; + } + return originalLoad.apply(this, arguments); + }; +} + +/** + * Gets the mocked VS Code namespaces/classes. + * For VS Code namespaces, always return pre-mocked objects, else create a new mock object. + * @export + * @template K + * @param {K} name + * @returns {TypeMoq.IMock} + */ +export function mock(name: K): TypeMoq.IMock { + if (mockedVSCodeNamespaces[name] === undefined) { + return TypeMoq.Mock.ofType(); + } + // When re-using, always reset (other tests could have used this same instance). + const mockObj = mockedVSCodeNamespaces[name]!; + mockObj.reset(); + return mockObj as any as TypeMoq.IMock; +} + +// This is one of the very few classes that we need in our unit tests. +// It is constructed in a number of places, and this is required for verification. +// Using mocked objects for verfications does not work in typemoq. +export class Uri implements vscode.Uri { + private constructor(public readonly scheme: string, public readonly authority: string, + public readonly path: string, public readonly query: string, + public readonly fragment: string, public readonly fsPath) { + + } + public static file(path: string): Uri { + return new Uri('file', '', path, '', '', path); + } + public static parse(value: string): Uri { + return new Uri('http', '', value, '', '', value); + } + public with(change: { scheme?: string; authority?: string; path?: string; query?: string; fragment?: string }): vscode.Uri { + throw new Error('Not implemented'); + } + public toString(skipEncoding?: boolean): string { + throw new Error('Not implemented'); + } + public toJSON(): any { + return this.fsPath; + } +} From 53deb237bac8e577636875a774bfbc8c276a88ea Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Mon, 21 May 2018 15:11:07 -0700 Subject: [PATCH 254/433] Changes to IntelliCode and engine downloads (#1715) * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * #1096 The if statement is automatically formatted incorrectly * Merge fix * Add more tests * More tests * Typo * Test * Also better handle multiline arguments * Add a couple missing periods [skip ci] * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * Merge fix * #1257 On type formatting errors for args and kwargs * Handle f-strings * Stop importing from test code * #1308 Single line statements leading to an indentation on the next line * #726 editing python after inline if statement invalid indent * Undo change * Move constant * Harden LS startup error checks * #1364 Intellisense doesn't work after specific const string * Telemetry for the analysis enging * PR feedback * Fix typo * Test baseline update * Jedi 0.12 * Priority to goto_defition * News * Replace unzip * Linux flavors + test * Grammar check * Grammar test * Test baselines * Add news * Pin dependency [skip ci] * Specify markdown as preferable format * Improve function argument detection * Specify markdown * Pythia setting * Baseline updates * Baseline update * Improve startup * Handle missing interpreter better * Handle interpreter change * Delete old file * Fix LS startup time reporting * Remove Async suffix from IFileSystem * Remove Pythia * Remove pre-packaged MSIL * Exe name on Unix * Plain linux * Fix casing * Fix message * Update PTVS engine activation steps --- CONTRIBUTING - PYTHON_ANALYSIS.md | 1 + package.json | 6 -- src/client/activation/analysis.ts | 41 +++----------- src/client/activation/analysisEngineHashes.ts | 8 +-- src/client/activation/downloader.ts | 15 +++-- src/client/activation/platformData.ts | 56 ++----------------- src/client/common/configSettings.ts | 6 +- src/client/common/types.ts | 3 +- 8 files changed, 27 insertions(+), 109 deletions(-) diff --git a/CONTRIBUTING - PYTHON_ANALYSIS.md b/CONTRIBUTING - PYTHON_ANALYSIS.md index 03563dac7efd..0e8ce38c8c9a 100644 --- a/CONTRIBUTING - PYTHON_ANALYSIS.md +++ b/CONTRIBUTING - PYTHON_ANALYSIS.md @@ -32,6 +32,7 @@ Visual Studio 2017: 3. Binaries arrive in *Python/BuildOutput/VsCode/raw* 4. Delete contents of the *analysis* folder in the Python Extension folder 5. Copy *.dll, *.pdb, *.json fron *Python/BuildOutput/VsCode/raw* to *analysis* +6. In VS Code set setting *python.downloadCodeAnalysis* to *false* ### Debugging code in Python Extension to VS Code Folow regular TypeScript debugging steps diff --git a/package.json b/package.json index 259c721a7854..38ab9f5db6d0 100644 --- a/package.json +++ b/package.json @@ -1232,12 +1232,6 @@ "description": "Whether to install Python modules globally when not using an environment.", "scope": "resource" }, - "python.pythiaEnabled": { - "type": "boolean", - "default": true, - "description": "Enables AI-driven additions to the completion list. Does not apply to Jedi.", - "scope": "resource" - }, "python.jediEnabled": { "type": "boolean", "default": true, diff --git a/src/client/activation/analysis.ts b/src/client/activation/analysis.ts index 8418b2c20c77..8072e99d32af 100644 --- a/src/client/activation/analysis.ts +++ b/src/client/activation/analysis.ts @@ -9,7 +9,6 @@ import { IApplicationShell } from '../common/application/types'; import { isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; import { createDeferred, Deferred } from '../common/helpers'; import { IFileSystem, IPlatformService } from '../common/platform/types'; -import { IProcessServiceFactory } from '../common/process/types'; import { StopWatch } from '../common/stopWatch'; import { IConfigurationService, IOutputChannel, IPythonSettings } from '../common/types'; import { IEnvironmentVariablesProvider } from '../common/variables/types'; @@ -103,32 +102,19 @@ export class AnalysisExtensionActivator implements IExtensionActivator { // Determine if we are running MSIL/Universal via dotnet or self-contained app. const mscorlib = path.join(context.extensionPath, analysisEngineFolder, 'mscorlib.dll'); const downloader = new AnalysisEngineDownloader(this.services, analysisEngineFolder); - let downloadPackage = false; const reporter = getTelemetryReporter(); reporter.sendTelemetryEvent(PYTHON_ANALYSIS_ENGINE_ENABLED); - await this.checkPythiaModel(context, downloader); - - if (!await this.fs.fileExists(mscorlib)) { - // Depends on .NET Runtime or SDK + const settings = this.configuration.getSettings(); + if (!settings.downloadCodeAnalysis) { + // Depends on .NET Runtime or SDK. Typically development-only case. this.languageClient = this.createSimpleLanguageClient(context, clientOptions); - try { - await this.tryStartLanguageClient(context, this.languageClient); - return true; - } catch (ex) { - if (await this.isDotNetInstalled()) { - this.appShell.showErrorMessage(`.NET Runtime appears to be installed but the language server did not start. Error ${ex}`); - reporter.sendTelemetryEvent(PYTHON_ANALYSIS_ENGINE_ERROR, { error: 'Failed to start (MSIL)' }); - return false; - } - // No .NET Runtime, no mscorlib - need to download self-contained package. - downloadPackage = true; - } + await this.tryStartLanguageClient(context, this.languageClient); + return true; } - if (downloadPackage) { - this.appShell.showWarningMessage('.NET Runtime is not found, platform-specific Python Analysis Engine will be downloaded.'); + if (!await this.fs.fileExists(mscorlib)) { await downloader.downloadAnalysisEngine(context); reporter.sendTelemetryEvent(PYTHON_ANALYSIS_ENGINE_DOWNLOADED); } @@ -254,22 +240,9 @@ export class AnalysisExtensionActivator implements IExtensionActivator { maxDocumentationTextLength: 0 }, asyncStartup: true, - pythiaEnabled: settings.pythiaEnabled, + intelliCodeEnabled: settings.intelliCodeEnabled, testEnvironment: isTestExecution() } }; } - - private async isDotNetInstalled(): Promise { - const ps = await this.services.get(IProcessServiceFactory).create(); - const result = await ps.exec('dotnet', ['--version']).catch(() => { return { stdout: '' }; }); - return result.stdout.trim().startsWith('2.'); - } - - private async checkPythiaModel(context: ExtensionContext, downloader: AnalysisEngineDownloader): Promise { - const settings = this.configuration.getSettings(); - if (settings.pythiaEnabled) { - await downloader.downloadPythiaModel(context); - } - } } diff --git a/src/client/activation/analysisEngineHashes.ts b/src/client/activation/analysisEngineHashes.ts index 2f9123a46c59..c4b1c30af6de 100644 --- a/src/client/activation/analysisEngineHashes.ts +++ b/src/client/activation/analysisEngineHashes.ts @@ -7,10 +7,4 @@ export const analysis_engine_win_x86_sha512 = 'win-x86'; export const analysis_engine_win_x64_sha512 = 'win-x64'; export const analysis_engine_osx_x64_sha512 = 'osx-x64'; -export const analysis_engine_centos_x64_sha512 = 'centos-x64'; -export const analysis_engine_debian_x64_sha512 = 'debian-x64'; -export const analysis_engine_fedora_x64_sha512 = 'fedora-x64'; -export const analysis_engine_ol_x64_sha512 = 'ol-x64'; -export const analysis_engine_opensuse_x64_sha512 = 'opensuse-x64'; -export const analysis_engine_rhel_x64_sha512 = 'rhel-x64'; -export const analysis_engine_ubuntu_x64_sha512 = 'ubuntu-x64'; +export const analysis_engine_linux_x64_sha512 = 'linux-x64'; diff --git a/src/client/activation/downloader.ts b/src/client/activation/downloader.ts index 52e9136a4951..f6d7036ad12a 100644 --- a/src/client/activation/downloader.ts +++ b/src/client/activation/downloader.ts @@ -18,10 +18,10 @@ import { PlatformData } from './platformData'; const StreamZip = require('node-stream-zip'); const downloadUriPrefix = 'https://pvsc.blob.core.windows.net/python-analysis'; -const downloadBaseFileName = 'python-analysis-vscode'; +const downloadBaseFileName = 'Python-Analysis-VSCode'; const downloadVersion = '0.1.0'; const downloadFileExtension = '.nupkg'; -const pythiaModelName = 'model-sequence.json.gz'; +const modelName = 'model-sequence.json.gz'; export class AnalysisEngineDownloader { private readonly output: OutputChannel; @@ -56,16 +56,16 @@ export class AnalysisEngineDownloader { } } - public async downloadPythiaModel(context: ExtensionContext): Promise { + public async downloadIntelliCodeModel(context: ExtensionContext): Promise { const modelFolder = path.join(context.extensionPath, 'analysis', 'Pythia', 'model'); - const localPath = path.join(modelFolder, pythiaModelName); + const localPath = path.join(modelFolder, modelName); if (await this.fs.fileExists(localPath)) { return; } let localTempFilePath = ''; try { - localTempFilePath = await this.downloadFile(downloadUriPrefix, pythiaModelName, 'Downloading IntelliSense Model File... '); + localTempFilePath = await this.downloadFile(downloadUriPrefix, modelName, 'Downloading IntelliCode Model File... '); await this.fs.createDirectory(modelFolder); await this.fs.copyFile(localTempFilePath, localPath); } catch (err) { @@ -129,11 +129,10 @@ export class AnalysisEngineDownloader { if (!await verifier.verifyHash(filePath, platformString, await this.platformData.getExpectedHash())) { throw new Error('Hash of the downloaded file does not match.'); } - this.output.append('valid.'); + this.output.appendLine('valid.'); } private async unpackArchive(extensionPath: string, tempFilePath: string): Promise { - this.output.appendLine(''); this.output.append('Unpacking archive... '); const installFolder = path.join(extensionPath, this.engineFolder); @@ -170,12 +169,12 @@ export class AnalysisEngineDownloader { }); return deferred.promise; }); - this.output.append('done.'); // Set file to executable if (!this.platform.isWindows) { const executablePath = path.join(installFolder, this.platformData.getEngineExecutableName()); fileSystem.chmodSync(executablePath, '0764'); // -rwxrw-r-- } + this.output.appendLine('done.'); } } diff --git a/src/client/activation/platformData.ts b/src/client/activation/platformData.ts index 2a1cb29da461..466955c496e5 100644 --- a/src/client/activation/platformData.ts +++ b/src/client/activation/platformData.ts @@ -3,31 +3,14 @@ import { IFileSystem, IPlatformService } from '../common/platform/types'; import { - analysis_engine_centos_x64_sha512, - analysis_engine_debian_x64_sha512, - analysis_engine_fedora_x64_sha512, - analysis_engine_ol_x64_sha512, - analysis_engine_opensuse_x64_sha512, + analysis_engine_linux_x64_sha512, analysis_engine_osx_x64_sha512, - analysis_engine_rhel_x64_sha512, - analysis_engine_ubuntu_x64_sha512, analysis_engine_win_x64_sha512, analysis_engine_win_x86_sha512 } from './analysisEngineHashes'; -// '/etc/os-release', ID=flavor -const supportedLinuxFlavors = [ - 'centos', - 'debian', - 'fedora', - 'ol', - 'opensuse', - 'rhel', - 'ubuntu' -]; - export class PlatformData { - constructor(private platform: IPlatformService, private fs: IFileSystem) { } + constructor(private platform: IPlatformService, fs: IFileSystem) { } public async getPlatformName(): Promise { if (this.platform.isWindows) { return this.platform.is64bit ? 'win-x64' : 'win-x86'; @@ -39,14 +22,7 @@ export class PlatformData { if (!this.platform.is64bit) { throw new Error('Python Analysis Engine does not support 32-bit Linux.'); } - const linuxFlavor = await this.getLinuxFlavor(); - if (linuxFlavor.length === 0) { - throw new Error('Unable to determine Linux flavor from /etc/os-release.'); - } - if (supportedLinuxFlavors.indexOf(linuxFlavor) < 0) { - throw new Error(`${linuxFlavor} is not supported.`); - } - return `${linuxFlavor}-x64`; + return 'linux-x64'; } throw new Error('Unknown OS platform.'); } @@ -58,7 +34,7 @@ export class PlatformData { public getEngineExecutableName(): string { return this.platform.isWindows ? 'Microsoft.PythonTools.VsCode.exe' - : 'Microsoft.PythonTools.VsCode'; + : 'Microsoft.PythonTools.VsCode.VsCode'; } public async getExpectedHash(): Promise { @@ -69,30 +45,8 @@ export class PlatformData { return analysis_engine_osx_x64_sha512; } if (this.platform.isLinux && this.platform.is64bit) { - const linuxFlavor = await this.getLinuxFlavor(); - // tslint:disable-next-line:switch-default - switch (linuxFlavor) { - case 'centos': return analysis_engine_centos_x64_sha512; - case 'debian': return analysis_engine_debian_x64_sha512; - case 'fedora': return analysis_engine_fedora_x64_sha512; - case 'ol': return analysis_engine_ol_x64_sha512; - case 'opensuse': return analysis_engine_opensuse_x64_sha512; - case 'rhel': return analysis_engine_rhel_x64_sha512; - case 'ubuntu': return analysis_engine_ubuntu_x64_sha512; - } + return analysis_engine_linux_x64_sha512; } throw new Error('Unknown platform.'); } - - private async getLinuxFlavor(): Promise { - const verFile = '/etc/os-release'; - const data = await this.fs.readFile(verFile); - if (data) { - const res = /ID=(.*)/.exec(data); - if (res && res.length > 1) { - return res[1]; - } - } - return ''; - } } diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index e659fe1f0a2f..63408c7c5638 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -25,7 +25,8 @@ export const IS_WINDOWS = /^win/.test(process.platform); // tslint:disable-next-line:completed-docs export class PythonSettings extends EventEmitter implements IPythonSettings { private static pythonSettings: Map = new Map(); - public pythiaEnabled = true; + public intelliCodeEnabled = true; + public downloadCodeAnalysis = true; public jediEnabled = true; public jediPath = ''; public jediMemoryLimit = 1024; @@ -115,6 +116,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { this.venvPath = systemVariables.resolveAny(pythonSettings.get('venvPath'))!; this.venvFolders = systemVariables.resolveAny(pythonSettings.get('venvFolders'))!; + this.downloadCodeAnalysis = systemVariables.resolveAny(pythonSettings.get('downloadCodeAnalysis', true))!; this.jediEnabled = systemVariables.resolveAny(pythonSettings.get('jediEnabled', true))!; if (this.jediEnabled) { // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion @@ -126,7 +128,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { } this.jediMemoryLimit = pythonSettings.get('jediMemoryLimit')!; } else { - this.pythiaEnabled = systemVariables.resolveAny(pythonSettings.get('pythiaEnabled', true))!; + this.intelliCodeEnabled = systemVariables.resolveAny(pythonSettings.get('intelliCodeEnabled', true))!; } // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 4ba1218fbc9f..05a03ee04cf7 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -100,7 +100,8 @@ export interface IPythonSettings { readonly pythonPath: string; readonly venvPath: string; readonly venvFolders: string[]; - readonly pythiaEnabled: boolean; + readonly intelliCodeEnabled: boolean; + readonly downloadCodeAnalysis: boolean; readonly jediEnabled: boolean; readonly jediPath: string; readonly jediMemoryLimit: number; From d4bdd4eb96c99cce3248b42134fdad8db9abb2b8 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 22 May 2018 22:11:21 +0200 Subject: [PATCH 255/433] Update debug capabilities to add support for the setting supportTerminateDebuggee Fixes #1719 --- news/3 Code Health/1719.md | 1 + src/client/debugger/mainV2.ts | 1 + 2 files changed, 2 insertions(+) create mode 100644 news/3 Code Health/1719.md diff --git a/news/3 Code Health/1719.md b/news/3 Code Health/1719.md new file mode 100644 index 000000000000..1650be424b90 --- /dev/null +++ b/news/3 Code Health/1719.md @@ -0,0 +1 @@ +Update debug capabilities to add support for the setting `supportTerminateDebuggee` due to an upstream update from [PTVSD](https://github.com/Microsoft/ptvsd/issues). diff --git a/src/client/debugger/mainV2.ts b/src/client/debugger/mainV2.ts index 8393a7bd5fbc..2349dee97445 100644 --- a/src/client/debugger/mainV2.ts +++ b/src/client/debugger/mainV2.ts @@ -70,6 +70,7 @@ export class PythonDebugger extends DebugSession { body.supportsHitConditionalBreakpoints = true; body.supportsSetExpression = true; body.supportsLogPoints = true; + body.supportTerminateDebuggee = true; body.exceptionBreakpointFilters = [ { filter: 'raised', From 71e94698fc7b4e6537ee803d0f07fb605ecd80fd Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 22 May 2018 22:57:29 +0200 Subject: [PATCH 256/433] Refactor Python Unit Test functionality to improve unit testing (#1713) Fixes #1068 --- news/3 Code Health/1068.md | 1 + src/client/activation/classic.ts | 11 +- .../common/managers/baseTestManager.ts | 38 +- .../managers/testConfigurationManager.ts | 22 +- .../common/services/configSettingService.ts | 56 +- src/client/unittests/common/testUtils.ts | 28 +- src/client/unittests/common/types.ts | 9 +- src/client/unittests/configuration.ts | 232 +++----- src/client/unittests/configurationFactory.ts | 35 ++ src/client/unittests/display/main.ts | 75 +-- src/client/unittests/display/picker.ts | 51 +- src/client/unittests/main.ts | 537 +++++++++--------- .../nosetest/testConfigurationManager.ts | 35 +- .../pytest/testConfigurationManager.ts | 41 +- src/client/unittests/serviceRegistry.ts | 20 +- src/client/unittests/types.ts | 69 +++ .../unittest/testConfigurationManager.ts | 15 +- src/test/common.ts | 8 +- src/test/common/installer.test.ts | 3 +- src/test/core.ts | 10 + .../testConfigurationManager.unit.test.ts | 18 +- .../configSettingService.unit.test.ts | 196 +++++++ src/test/unittests/configuration.unit.test.ts | 340 +++++++++++ .../configurationFactory.unit.test.ts | 47 ++ src/test/unittests/display/main.test.ts | 366 ++++++++++++ src/test/vscode-mock.ts | 25 +- 26 files changed, 1697 insertions(+), 591 deletions(-) create mode 100644 news/3 Code Health/1068.md create mode 100644 src/client/unittests/configurationFactory.ts create mode 100644 src/client/unittests/types.ts create mode 100644 src/test/core.ts create mode 100644 src/test/unittests/common/services/configSettingService.unit.test.ts create mode 100644 src/test/unittests/configuration.unit.test.ts create mode 100644 src/test/unittests/configurationFactory.unit.test.ts create mode 100644 src/test/unittests/display/main.test.ts diff --git a/news/3 Code Health/1068.md b/news/3 Code Health/1068.md new file mode 100644 index 000000000000..82f60782ccd9 --- /dev/null +++ b/news/3 Code Health/1068.md @@ -0,0 +1 @@ +Refactor unit testing functionality to improve testability of individual components. diff --git a/src/client/activation/classic.ts b/src/client/activation/classic.ts index 0574765f6336..76a25a426415 100644 --- a/src/client/activation/classic.ts +++ b/src/client/activation/classic.ts @@ -3,7 +3,7 @@ import { DocumentFilter, ExtensionContext, languages, OutputChannel } from 'vscode'; import { STANDARD_OUTPUT_CHANNEL } from '../common/constants'; -import { IOutputChannel, IPythonSettings } from '../common/types'; +import { ILogger, IOutputChannel, IPythonSettings } from '../common/types'; import { IShebangCodeLensProvider } from '../interpreter/contracts'; import { IServiceManager } from '../ioc/types'; import { JediFactory } from '../languageServices/jediProxyFactory'; @@ -16,8 +16,7 @@ import { PythonRenameProvider } from '../providers/renameProvider'; import { PythonSignatureProvider } from '../providers/signatureProvider'; import { activateSimplePythonRefactorProvider } from '../providers/simpleRefactorProvider'; import { PythonSymbolProvider } from '../providers/symbolProvider'; -import { TEST_OUTPUT_CHANNEL } from '../unittests/common/constants'; -import * as tests from '../unittests/main'; +import { IUnitTestManagementService } from '../unittests/types'; import { IExtensionActivator } from './types'; export class ClassicExtensionActivator implements IExtensionActivator { @@ -49,8 +48,10 @@ export class ClassicExtensionActivator implements IExtensionActivator { context.subscriptions.push(languages.registerSignatureHelpProvider(this.documentSelector, new PythonSignatureProvider(jediFactory), '(', ',')); } - const unitTestOutChannel = this.serviceManager.get(IOutputChannel, TEST_OUTPUT_CHANNEL); - tests.activate(context, unitTestOutChannel, symbolProvider, this.serviceManager); + const testManagementService = this.serviceManager.get(IUnitTestManagementService); + testManagementService.activate() + .then(() => testManagementService.activateCodeLenses(symbolProvider)) + .catch(ex => this.serviceManager.get(ILogger).logError('Failed to activate Unit Tests', ex)); return true; } diff --git a/src/client/unittests/common/managers/baseTestManager.ts b/src/client/unittests/common/managers/baseTestManager.ts index 266d269148c9..437044aa94e3 100644 --- a/src/client/unittests/common/managers/baseTestManager.ts +++ b/src/client/unittests/common/managers/baseTestManager.ts @@ -1,17 +1,13 @@ -import * as vscode from 'vscode'; -import { Disposable, OutputChannel, Uri, workspace } from 'vscode'; +import { CancellationToken, CancellationTokenSource, Disposable, OutputChannel, Uri, workspace } from 'vscode'; import { PythonSettings } from '../../../common/configSettings'; import { isNotInstalledError } from '../../../common/helpers'; -import { IPythonSettings } from '../../../common/types'; -import { IDisposableRegistry, IInstaller, IOutputChannel, Product } from '../../../common/types'; +import { IDisposableRegistry, IInstaller, IOutputChannel, IPythonSettings, Product } from '../../../common/types'; import { IServiceContainer } from '../../../ioc/types'; import { UNITTEST_DISCOVER, UNITTEST_RUN } from '../../../telemetry/constants'; import { sendTelemetryEvent } from '../../../telemetry/index'; import { TestDiscoverytTelemetry, TestRunTelemetry } from '../../../telemetry/types'; import { CANCELLATION_REASON, CommandSource, TEST_OUTPUT_CHANNEL } from './../constants'; -import { displayTestErrorMessage } from './../testUtils'; -import { ITestCollectionStorageService, ITestDiscoveryService, ITestManager, ITestResultsService } from './../types'; -import { TestDiscoveryOptions, TestProvider, Tests, TestStatus, TestsToRun } from './../types'; +import { ITestCollectionStorageService, ITestDiscoveryService, ITestManager, ITestResultsService, ITestsHelper, TestDiscoveryOptions, TestProvider, Tests, TestStatus, TestsToRun } from './../types'; enum CancellationTokenType { testDiscovery, @@ -33,9 +29,9 @@ export abstract class BaseTestManager implements ITestManager { private tests?: Tests; // tslint:disable-next-line:variable-name private _status: TestStatus = TestStatus.Unknown; - private testDiscoveryCancellationTokenSource?: vscode.CancellationTokenSource; - private testRunnerCancellationTokenSource?: vscode.CancellationTokenSource; - private _installer: IInstaller; + private testDiscoveryCancellationTokenSource?: CancellationTokenSource; + private testRunnerCancellationTokenSource?: CancellationTokenSource; + private _installer!: IInstaller; private discoverTestsPromise?: Promise; private get installer(): IInstaller { if (!this._installer) { @@ -53,10 +49,10 @@ export abstract class BaseTestManager implements ITestManager { this.testCollectionStorage = this.serviceContainer.get(ITestCollectionStorageService); this._testResultsService = this.serviceContainer.get(ITestResultsService); } - protected get testDiscoveryCancellationToken(): vscode.CancellationToken | undefined { + protected get testDiscoveryCancellationToken(): CancellationToken | undefined { return this.testDiscoveryCancellationTokenSource ? this.testDiscoveryCancellationTokenSource.token : undefined; } - protected get testRunnerCancellationToken(): vscode.CancellationToken | undefined { + protected get testRunnerCancellationToken(): CancellationToken | undefined { return this.testRunnerCancellationTokenSource ? this.testRunnerCancellationTokenSource.token : undefined; } public dispose() { @@ -66,7 +62,7 @@ export abstract class BaseTestManager implements ITestManager { return this._status; } public get workingDirectory(): string { - const settings = PythonSettings.getInstance(vscode.Uri.file(this.rootDirectory)); + const settings = PythonSettings.getInstance(Uri.file(this.rootDirectory)); return settings.unitTest.cwd && settings.unitTest.cwd.length > 0 ? settings.unitTest.cwd : this.rootDirectory; } public stop() { @@ -133,9 +129,10 @@ export abstract class BaseTestManager implements ITestManager { } }); if (haveErrorsInDiscovering && !quietMode) { - displayTestErrorMessage('There were some errors in discovering unit tests'); + const testsHelper = this.serviceContainer.get(ITestsHelper); + testsHelper.displayTestErrorMessage('There were some errors in discovering unit tests'); } - const wkspace = workspace.getWorkspaceFolder(vscode.Uri.file(this.rootDirectory))!.uri; + const wkspace = workspace.getWorkspaceFolder(Uri.file(this.rootDirectory))!.uri; this.testCollectionStorage.storeTests(wkspace, tests); this.disposeCancellationToken(CancellationTokenType.testDiscovery); sendTelemetryEvent(UNITTEST_DISCOVER, undefined, telementryProperties); @@ -159,7 +156,7 @@ export abstract class BaseTestManager implements ITestManager { // tslint:disable-next-line:prefer-template this.outputChannel.appendLine(reason.toString()); } - const wkspace = workspace.getWorkspaceFolder(vscode.Uri.file(this.rootDirectory))!.uri; + const wkspace = workspace.getWorkspaceFolder(Uri.file(this.rootDirectory))!.uri; this.testCollectionStorage.storeTests(wkspace, null); this.disposeCancellationToken(CancellationTokenType.testDiscovery); return Promise.reject(reason); @@ -217,8 +214,9 @@ export abstract class BaseTestManager implements ITestManager { if (this.testDiscoveryCancellationToken && this.testDiscoveryCancellationToken.isCancellationRequested) { return Promise.reject(reason); } - displayTestErrorMessage('Errors in discovering tests, continuing with tests'); - return { + const testsHelper = this.serviceContainer.get(ITestsHelper); + testsHelper.displayTestErrorMessage('Errors in discovering tests, continuing with tests'); + return { rootTestFolders: [], testFiles: [], testFolders: [], testFunctions: [], testSuites: [], summary: { errors: 0, failures: 0, passed: 0, skipped: 0 } }; @@ -250,9 +248,9 @@ export abstract class BaseTestManager implements ITestManager { private createCancellationToken(tokenType: CancellationTokenType) { this.disposeCancellationToken(tokenType); if (tokenType === CancellationTokenType.testDiscovery) { - this.testDiscoveryCancellationTokenSource = new vscode.CancellationTokenSource(); + this.testDiscoveryCancellationTokenSource = new CancellationTokenSource(); } else { - this.testRunnerCancellationTokenSource = new vscode.CancellationTokenSource(); + this.testRunnerCancellationTokenSource = new CancellationTokenSource(); } } private disposeCancellationToken(tokenType: CancellationTokenType) { diff --git a/src/client/unittests/common/managers/testConfigurationManager.ts b/src/client/unittests/common/managers/testConfigurationManager.ts index 358b8b5705b6..b6351c6bb335 100644 --- a/src/client/unittests/common/managers/testConfigurationManager.ts +++ b/src/client/unittests/common/managers/testConfigurationManager.ts @@ -1,18 +1,26 @@ import * as path from 'path'; import { OutputChannel, QuickPickItem, Uri, window } from 'vscode'; import { createDeferred } from '../../../common/helpers'; -import { IInstaller, Product } from '../../../common/types'; +import { IInstaller, IOutputChannel, Product } from '../../../common/types'; import { getSubDirectories } from '../../../common/utils'; +import { IServiceContainer } from '../../../ioc/types'; +import { ITestConfigurationManager } from '../../types'; +import { TEST_OUTPUT_CHANNEL } from '../constants'; import { ITestConfigSettingsService, UnitTestProduct } from './../types'; -export abstract class TestConfigurationManager { +export abstract class TestConfigurationManager implements ITestConfigurationManager { + protected readonly outputChannel: OutputChannel; + protected readonly installer: IInstaller; + protected readonly testConfigSettingsService: ITestConfigSettingsService; constructor(protected workspace: Uri, protected product: UnitTestProduct, - protected readonly outputChannel: OutputChannel, - protected installer: IInstaller, - protected testConfigSettingsService: ITestConfigSettingsService) { } - // tslint:disable-next-line:no-any - public abstract configure(wkspace: Uri): Promise; + protected readonly serviceContainer: IServiceContainer) { + this.outputChannel = serviceContainer.get(IOutputChannel, TEST_OUTPUT_CHANNEL); + this.installer = serviceContainer.get(IInstaller); + this.testConfigSettingsService = serviceContainer.get(ITestConfigSettingsService); + } + public abstract configure(wkspace: Uri): Promise; + public abstract requiresUserToConfigure(wkspace: Uri): Promise; public async enable() { // Disable other test frameworks. const testProducsToDisable = [Product.pytest, Product.unittest, Product.nosetest] diff --git a/src/client/unittests/common/services/configSettingService.ts b/src/client/unittests/common/services/configSettingService.ts index d7dd923e9f10..483ffa35058b 100644 --- a/src/client/unittests/common/services/configSettingService.ts +++ b/src/client/unittests/common/services/configSettingService.ts @@ -1,9 +1,31 @@ -import { Uri, workspace, WorkspaceConfiguration } from 'vscode'; +import { inject, injectable } from 'inversify'; +import { Uri, WorkspaceConfiguration } from 'vscode'; +import { IWorkspaceService } from '../../../common/application/types'; import { Product } from '../../../common/types'; +import { IServiceContainer } from '../../../ioc/types'; import { ITestConfigSettingsService, UnitTestProduct } from './../types'; +@injectable() export class TestConfigSettingsService implements ITestConfigSettingsService { - private static getTestArgSetting(product: UnitTestProduct) { + private readonly workspaceService: IWorkspaceService; + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { + this.workspaceService = serviceContainer.get(IWorkspaceService); + } + public async updateTestArgs(testDirectory: string | Uri, product: UnitTestProduct, args: string[]) { + const setting = this.getTestArgSetting(product); + return this.updateSetting(testDirectory, setting, args); + } + + public async enable(testDirectory: string | Uri, product: UnitTestProduct): Promise { + const setting = this.getTestEnablingSetting(product); + return this.updateSetting(testDirectory, setting, true); + } + + public async disable(testDirectory: string | Uri, product: UnitTestProduct): Promise { + const setting = this.getTestEnablingSetting(product); + return this.updateSetting(testDirectory, setting, false); + } + private getTestArgSetting(product: UnitTestProduct) { switch (product) { case Product.unittest: return 'unitTest.unittestArgs'; @@ -15,7 +37,7 @@ export class TestConfigSettingsService implements ITestConfigSettingsService { throw new Error('Invalid Test Product'); } } - private static getTestEnablingSetting(product: UnitTestProduct) { + private getTestEnablingSetting(product: UnitTestProduct) { switch (product) { case Product.unittest: return 'unitTest.unittestEnabled'; @@ -28,36 +50,22 @@ export class TestConfigSettingsService implements ITestConfigSettingsService { } } // tslint:disable-next-line:no-any - private static async updateSetting(testDirectory: string | Uri, setting: string, value: any) { + private async updateSetting(testDirectory: string | Uri, setting: string, value: any) { let pythonConfig: WorkspaceConfiguration; const resource = typeof testDirectory === 'string' ? Uri.file(testDirectory) : testDirectory; - if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0) { - pythonConfig = workspace.getConfiguration('python'); - } else if (workspace.workspaceFolders.length === 1) { - pythonConfig = workspace.getConfiguration('python', workspace.workspaceFolders[0].uri); + if (!this.workspaceService.hasWorkspaceFolders) { + pythonConfig = this.workspaceService.getConfiguration('python'); + } else if (this.workspaceService.workspaceFolders!.length === 1) { + pythonConfig = this.workspaceService.getConfiguration('python', this.workspaceService.workspaceFolders![0].uri); } else { - const workspaceFolder = workspace.getWorkspaceFolder(resource); + const workspaceFolder = this.workspaceService.getWorkspaceFolder(resource); if (!workspaceFolder) { throw new Error(`Test directory does not belong to any workspace (${testDirectory})`); } // tslint:disable-next-line:no-non-null-assertion - pythonConfig = workspace.getConfiguration('python', workspaceFolder!.uri); + pythonConfig = this.workspaceService.getConfiguration('python', workspaceFolder!.uri); } return pythonConfig.update(setting, value); } - public async updateTestArgs(testDirectory: string | Uri, product: UnitTestProduct, args: string[]) { - const setting = TestConfigSettingsService.getTestArgSetting(product); - return TestConfigSettingsService.updateSetting(testDirectory, setting, args); - } - - public async enable(testDirectory: string | Uri, product: UnitTestProduct): Promise { - const setting = TestConfigSettingsService.getTestEnablingSetting(product); - return TestConfigSettingsService.updateSetting(testDirectory, setting, true); - } - - public async disable(testDirectory: string | Uri, product: UnitTestProduct): Promise { - const setting = TestConfigSettingsService.getTestEnablingSetting(product); - return TestConfigSettingsService.updateSetting(testDirectory, setting, false); - } } diff --git a/src/client/unittests/common/testUtils.ts b/src/client/unittests/common/testUtils.ts index c2eb115c924b..535f6c28c1a7 100644 --- a/src/client/unittests/common/testUtils.ts +++ b/src/client/unittests/common/testUtils.ts @@ -1,8 +1,10 @@ import { inject, injectable, named } from 'inversify'; import * as path from 'path'; -import { commands, Uri, window, workspace } from 'vscode'; +import { Uri, window, workspace } from 'vscode'; +import { IApplicationShell, ICommandManager } from '../../common/application/types'; import * as constants from '../../common/constants'; import { IUnitTestSettings, Product } from '../../common/types'; +import { IServiceContainer } from '../../ioc/types'; import { CommandSource } from './constants'; import { TestFlatteningVisitor } from './testVisitors/flatteningVisitor'; import { ITestsHelper, ITestVisitor, TestFile, TestFolder, TestProvider, Tests, TestSettingsPropertyNames, TestsToRun, UnitTestProduct } from './types'; @@ -19,15 +21,6 @@ export async function selectTestWorkspace(): Promise { } } -export function displayTestErrorMessage(message: string) { - window.showErrorMessage(message, constants.Button_Text_Tests_View_Output).then(action => { - if (action === constants.Button_Text_Tests_View_Output) { - commands.executeCommand(constants.Commands.Tests_ViewOutput, undefined, CommandSource.ui); - } - }); - -} - export function extractBetweenDelimiters(content: string, startDelimiter: string, endDelimiter: string): string { content = content.substring(content.indexOf(startDelimiter) + startDelimiter.length); return content.substring(0, content.lastIndexOf(endDelimiter)); @@ -40,7 +33,13 @@ export function convertFileToPackage(filePath: string): string { @injectable() export class TestsHelper implements ITestsHelper { - constructor(@inject(ITestVisitor) @named('TestFlatteningVisitor') private flatteningVisitor: TestFlatteningVisitor) { } + private readonly appShell: IApplicationShell; + private readonly commandManager: ICommandManager; + constructor(@inject(ITestVisitor) @named('TestFlatteningVisitor') private flatteningVisitor: TestFlatteningVisitor, + @inject(IServiceContainer) serviceContainer: IServiceContainer) { + this.appShell = serviceContainer.get(IApplicationShell); + this.commandManager = serviceContainer.get(ICommandManager); + } public parseProviderName(product: UnitTestProduct): TestProvider { switch (product) { case Product.nosetest: return 'nosetest'; @@ -165,4 +164,11 @@ export class TestsHelper implements ITestsHelper { // tslint:disable-next-line:no-object-literal-type-assertion return { testFile: [{ name: name, nameToRun: name, functions: [], suites: [], xmlName: name, fullPath: '', time: 0 }] }; } + public displayTestErrorMessage(message: string) { + this.appShell.showErrorMessage(message, constants.Button_Text_Tests_View_Output).then(action => { + if (action === constants.Button_Text_Tests_View_Output) { + this.commandManager.executeCommand(constants.Commands.Tests_ViewOutput, undefined, CommandSource.ui); + } + }); + } } diff --git a/src/client/unittests/common/types.ts b/src/client/unittests/common/types.ts index 2b5bc9af48b6..e5ad14fa6fd7 100644 --- a/src/client/unittests/common/types.ts +++ b/src/client/unittests/common/types.ts @@ -1,6 +1,5 @@ import { CancellationToken, Disposable, OutputChannel, Uri } from 'vscode'; -import { IUnitTestSettings } from '../../common/types'; -import { Product } from '../../common/types'; +import { IUnitTestSettings, Product } from '../../common/types'; import { CommandSource } from './constants'; export type TestProvider = 'nosetest' | 'pytest' | 'unittest'; @@ -126,8 +125,9 @@ export type TestsToRun = { export type UnitTestProduct = Product.nosetest | Product.pytest | Product.unittest; +export const ITestConfigSettingsService = Symbol('ITestConfigSettingsService'); export interface ITestConfigSettingsService { - updateTestArgs(testDirectory: string, product: UnitTestProduct, args: string[]): Promise; + updateTestArgs(testDirectory: string | Uri, product: UnitTestProduct, args: string[]): Promise; enable(testDirectory: string | Uri, product: UnitTestProduct): Promise; disable(testDirectory: string | Uri, product: UnitTestProduct): Promise; } @@ -160,6 +160,7 @@ export interface ITestsHelper { getSettingsPropertyNames(product: Product): TestSettingsPropertyNames; flattenTestFiles(testFiles: TestFile[]): Tests; placeTestFilesIntoFolders(tests: Tests): void; + displayTestErrorMessage(message: string): void; } export const ITestVisitor = Symbol('ITestVisitor'); @@ -240,6 +241,6 @@ export type ParserOptions = TestDiscoveryOptions; export const IUnitTestSocketServer = Symbol('IUnitTestSocketServer'); export interface IUnitTestSocketServer extends Disposable { on(event: string | symbol, listener: Function): this; - start(options?: { port?: number, host?: string }): Promise; + start(options?: { port?: number; host?: string }): Promise; stop(): void; } diff --git a/src/client/unittests/configuration.ts b/src/client/unittests/configuration.ts index 850a39ae42cf..3772a90241a3 100644 --- a/src/client/unittests/configuration.ts +++ b/src/client/unittests/configuration.ts @@ -1,154 +1,110 @@ 'use strict'; -import * as path from 'path'; -import * as vscode from 'vscode'; + +import { inject, injectable } from 'inversify'; import { OutputChannel, Uri } from 'vscode'; -import { PythonSettings } from '../common/configSettings'; -import { IInstaller, Product } from '../common/types'; -import { getSubDirectories } from '../common/utils'; -import { TestConfigurationManager } from './common/managers/testConfigurationManager'; -import { TestConfigSettingsService } from './common/services/configSettingService'; +import { IApplicationShell, IWorkspaceService } from '../common/application/types'; +import { IConfigurationService, IInstaller, IOutputChannel, Product } from '../common/types'; +import { IServiceContainer } from '../ioc/types'; +import { TEST_OUTPUT_CHANNEL } from './common/constants'; import { UnitTestProduct } from './common/types'; -import { ConfigurationManager } from './nosetest/testConfigurationManager'; -import * as nose from './nosetest/testConfigurationManager'; -import * as pytest from './pytest/testConfigurationManager'; -import * as unittest from './unittest/testConfigurationManager'; +import { ITestConfigurationManagerFactory, IUnitTestConfigurationService } from './types'; -// tslint:disable-next-line:no-any -async function promptToEnableAndConfigureTestFramework(wkspace: Uri, installer: IInstaller, outputChannel: vscode.OutputChannel, messageToDisplay: string = 'Select a test framework/tool to enable', enableOnly: boolean = false) { - const selectedTestRunner = await selectTestRunner(messageToDisplay); - if (typeof selectedTestRunner !== 'number') { - return Promise.reject(null); - } - const configMgr: TestConfigurationManager = createTestConfigurationManager(wkspace, selectedTestRunner, outputChannel, installer); - if (enableOnly) { - // Ensure others are disabled - [Product.unittest, Product.pytest, Product.nosetest] - .filter(prod => selectedTestRunner !== prod) - .forEach(prod => { - createTestConfigurationManager(wkspace, prod, outputChannel, installer).disable() - .catch(ex => console.error('Python Extension: createTestConfigurationManager.disable', ex)); - }); - return configMgr.enable(); +@injectable() +export class UnitTestConfigurationService implements IUnitTestConfigurationService { + private readonly configurationService: IConfigurationService; + private readonly appShell: IApplicationShell; + private readonly installer: IInstaller; + private readonly outputChannel: OutputChannel; + private readonly workspaceService: IWorkspaceService; + constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { + this.configurationService = serviceContainer.get(IConfigurationService); + this.appShell = serviceContainer.get(IApplicationShell); + this.installer = serviceContainer.get(IInstaller); + this.outputChannel = serviceContainer.get(IOutputChannel, TEST_OUTPUT_CHANNEL); + this.workspaceService = serviceContainer.get(IWorkspaceService); } - - return configMgr.configure(wkspace).then(() => { - return enableTest(wkspace, configMgr); - }).catch(reason => { - return enableTest(wkspace, configMgr).then(() => Promise.reject(reason)); - }); -} -export function displayTestFrameworkError(wkspace: Uri, outputChannel: vscode.OutputChannel, installer: IInstaller) { - const settings = PythonSettings.getInstance(); - let enabledCount = settings.unitTest.pyTestEnabled ? 1 : 0; - enabledCount += settings.unitTest.nosetestsEnabled ? 1 : 0; - enabledCount += settings.unitTest.unittestEnabled ? 1 : 0; - if (enabledCount > 1) { - return promptToEnableAndConfigureTestFramework(wkspace, installer, outputChannel, 'Enable only one of the test frameworks (unittest, pytest or nosetest).', true); - } else { - const option = 'Enable and configure a Test Framework'; - return vscode.window.showInformationMessage('No test framework configured (unittest, pytest or nosetest)', option).then(item => { + public async displayTestFrameworkError(wkspace: Uri): Promise { + const settings = this.configurationService.getSettings(wkspace); + let enabledCount = settings.unitTest.pyTestEnabled ? 1 : 0; + enabledCount += settings.unitTest.nosetestsEnabled ? 1 : 0; + enabledCount += settings.unitTest.unittestEnabled ? 1 : 0; + if (enabledCount > 1) { + return this.promptToEnableAndConfigureTestFramework(wkspace, this.installer, this.outputChannel, 'Enable only one of the test frameworks (unittest, pytest or nosetest).', true); + } else { + const option = 'Enable and configure a Test Framework'; + const item = await this.appShell.showInformationMessage('No test framework configured (unittest, pytest or nosetest)', option); if (item === option) { - return promptToEnableAndConfigureTestFramework(wkspace, installer, outputChannel); + return this.promptToEnableAndConfigureTestFramework(wkspace, this.installer, this.outputChannel); } return Promise.reject(null); - }); - } -} -export async function displayPromptToEnableTests(rootDir: string, outputChannel: vscode.OutputChannel, installer: IInstaller) { - const settings = PythonSettings.getInstance(vscode.Uri.file(rootDir)); - if (settings.unitTest.pyTestEnabled || - settings.unitTest.nosetestsEnabled || - settings.unitTest.unittestEnabled) { - return; - } - - if (!settings.unitTest.promptToConfigure) { - return; - } - - const yes = 'Yes'; - const no = 'Later'; - const noNotAgain = 'No, don\'t ask again'; - - const hasTests = checkForExistenceOfTests(rootDir); - if (!hasTests) { - return; + } } - const item = await vscode.window.showInformationMessage('You seem to have tests, would you like to enable a test framework?', yes, no, noNotAgain); - if (!item || item === no) { - return; + public async selectTestRunner(placeHolderMessage: string): Promise { + const items = [{ + label: 'unittest', + product: Product.unittest, + description: 'Standard Python test framework', + detail: 'https://docs.python.org/3/library/unittest.html' + }, + { + label: 'pytest', + product: Product.pytest, + description: 'Can run unittest (including trial) and nose test suites out of the box', + // tslint:disable-next-line:no-http-string + detail: 'http://docs.pytest.org/' + }, + { + label: 'nose', + product: Product.nosetest, + description: 'nose framework', + detail: 'https://nose.readthedocs.io/' + }]; + const options = { + matchOnDescription: true, + matchOnDetail: true, + placeHolder: placeHolderMessage + }; + const selectedTestRunner = await this.appShell.showQuickPick(items, options); + // tslint:disable-next-line:prefer-type-cast + return selectedTestRunner ? selectedTestRunner.product as UnitTestProduct : undefined; } - if (item === yes) { - await promptToEnableAndConfigureTestFramework(vscode.workspace.getWorkspaceFolder(vscode.Uri.file(rootDir))!.uri, installer, outputChannel); - } else { - const pythonConfig = vscode.workspace.getConfiguration('python'); - await pythonConfig.update('unitTest.promptToConfigure', false); + public enableTest(wkspace: Uri, product: UnitTestProduct) { + const factory = this.serviceContainer.get(ITestConfigurationManagerFactory); + const configMgr = factory.create(wkspace, product); + const pythonConfig = this.workspaceService.getConfiguration('python', wkspace); + if (pythonConfig.get('unitTest.promptToConfigure')) { + return configMgr.enable(); + } + return pythonConfig.update('unitTest.promptToConfigure', undefined).then(() => { + return configMgr.enable(); + }, reason => { + return configMgr.enable().then(() => Promise.reject(reason)); + }); } -} -// Configure everything before enabling. -// Cuz we don't want the test engine (in main.ts file - tests get discovered when config changes are detected) -// to start discovering tests when tests haven't been configured properly. -function enableTest(wkspace: Uri, configMgr: ConfigurationManager) { - const pythonConfig = vscode.workspace.getConfiguration('python', wkspace); - // tslint:disable-next-line:no-backbone-get-set-outside-model - if (pythonConfig.get('unitTest.promptToConfigure')) { - return configMgr.enable(); - } - return pythonConfig.update('unitTest.promptToConfigure', undefined).then(() => { - return configMgr.enable(); - }, reason => { - return configMgr.enable().then(() => Promise.reject(reason)); - }); -} -function checkForExistenceOfTests(rootDir: string): Promise { - return getSubDirectories(rootDir).then(subDirs => { - return subDirs.map(dir => path.relative(rootDir, dir)).filter(dir => dir.match(/test/i)).length > 0; - }); -} -function createTestConfigurationManager(wkspace: Uri, product: Product, outputChannel: OutputChannel, installer: IInstaller) { - const configSettingService = new TestConfigSettingsService(); - switch (product) { - case Product.unittest: { - return new unittest.ConfigurationManager(wkspace, outputChannel, installer, configSettingService); - } - case Product.pytest: { - return new pytest.ConfigurationManager(wkspace, outputChannel, installer, configSettingService); - } - case Product.nosetest: { - return new nose.ConfigurationManager(wkspace, outputChannel, installer, configSettingService); + private async promptToEnableAndConfigureTestFramework(wkspace: Uri, installer: IInstaller, outputChannel: OutputChannel, messageToDisplay: string = 'Select a test framework/tool to enable', enableOnly: boolean = false) { + const selectedTestRunner = await this.selectTestRunner(messageToDisplay); + if (typeof selectedTestRunner !== 'number') { + return Promise.reject(null); } - default: { - throw new Error('Invalid test configuration'); + const factory = this.serviceContainer.get(ITestConfigurationManagerFactory); + const configMgr = factory.create(wkspace, selectedTestRunner); + if (enableOnly) { + // Ensure others are disabled + [Product.unittest, Product.pytest, Product.nosetest] + .filter(prod => selectedTestRunner !== prod) + .forEach(prod => { + factory.create(wkspace, prod).disable() + .catch(ex => console.error('Python Extension: createTestConfigurationManager.disable', ex)); + }); + return configMgr.enable(); } + + // Configure everything before enabling. + // Cuz we don't want the test engine (in main.ts file - tests get discovered when config changes are detected) + // to start discovering tests when tests haven't been configured properly. + return configMgr.configure(wkspace) + .then(() => this.enableTest(wkspace, selectedTestRunner)) + .catch(reason => { return this.enableTest(wkspace, selectedTestRunner).then(() => Promise.reject(reason)); }); } } -async function selectTestRunner(placeHolderMessage: string): Promise { - const items = [{ - label: 'unittest', - product: Product.unittest, - description: 'Standard Python test framework', - detail: 'https://docs.python.org/3/library/unittest.html' - }, - { - label: 'pytest', - product: Product.pytest, - description: 'Can run unittest (including trial) and nose test suites out of the box', - // tslint:disable-next-line:no-http-string - detail: 'http://docs.pytest.org/' - }, - { - label: 'nose', - product: Product.nosetest, - description: 'nose framework', - detail: 'https://nose.readthedocs.io/' - }]; - const options = { - matchOnDescription: true, - matchOnDetail: true, - placeHolder: placeHolderMessage - }; - const selectedTestRunner = await vscode.window.showQuickPick(items, options); - // tslint:disable-next-line:prefer-type-cast - return selectedTestRunner ? selectedTestRunner.product as UnitTestProduct : undefined; -} diff --git a/src/client/unittests/configurationFactory.ts b/src/client/unittests/configurationFactory.ts new file mode 100644 index 000000000000..ee29d6d8c1d6 --- /dev/null +++ b/src/client/unittests/configurationFactory.ts @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { Uri } from 'vscode'; +import { Product } from '../common/types'; +import { IServiceContainer } from '../ioc/types'; +import * as nose from './nosetest/testConfigurationManager'; +import * as pytest from './pytest/testConfigurationManager'; +import { ITestConfigurationManagerFactory } from './types'; +import * as unittest from './unittest/testConfigurationManager'; + +@injectable() +export class TestConfigurationManagerFactory implements ITestConfigurationManagerFactory { + constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { } + public create(wkspace: Uri, product: Product) { + switch (product) { + case Product.unittest: { + return new unittest.ConfigurationManager(wkspace, this.serviceContainer); + } + case Product.pytest: { + return new pytest.ConfigurationManager(wkspace, this.serviceContainer); + } + case Product.nosetest: { + return new nose.ConfigurationManager(wkspace, this.serviceContainer); + } + default: { + throw new Error('Invalid test configuration'); + } + } + } + +} diff --git a/src/client/unittests/display/main.ts b/src/client/unittests/display/main.ts index aec0139ac2cb..dc2a130e1064 100644 --- a/src/client/unittests/display/main.ts +++ b/src/client/unittests/display/main.ts @@ -1,25 +1,46 @@ 'use strict'; -import * as vscode from 'vscode'; +import { inject, injectable } from 'inversify'; +import { Event, EventEmitter, StatusBarAlignment, StatusBarItem } from 'vscode'; +import { IApplicationShell } from '../../common/application/types'; import * as constants from '../../common/constants'; -import { createDeferred, isNotInstalledError } from '../../common/helpers'; +import { noop } from '../../common/core.utils'; +import { isNotInstalledError } from '../../common/helpers'; +import { IConfigurationService } from '../../common/types'; +import { IServiceContainer } from '../../ioc/types'; import { CANCELLATION_REASON } from '../common/constants'; -import { displayTestErrorMessage } from '../common/testUtils'; -import { Tests } from '../common/types'; +import { ITestsHelper, Tests } from '../common/types'; +import { ITestResultDisplay } from '../types'; -export class TestResultDisplay { - private statusBar: vscode.StatusBarItem; +@injectable() +export class TestResultDisplay implements ITestResultDisplay { + private statusBar: StatusBarItem; private discoverCounter = 0; private ticker = ['|', '/', '-', '|', '/', '-', '\\']; private progressTimeout; + private _enabled: boolean = false; private progressPrefix!: string; + private readonly didChange = new EventEmitter(); + private readonly appShell: IApplicationShell; + private readonly testsHelper: ITestsHelper; + public get onDidChange(): Event { + return this.didChange.event; + } + // tslint:disable-next-line:no-any - constructor(private onDidChange?: vscode.EventEmitter) { - this.statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left); + constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { + this.appShell = serviceContainer.get(IApplicationShell); + this.statusBar = this.appShell.createStatusBarItem(StatusBarAlignment.Left); + this.testsHelper = serviceContainer.get(ITestsHelper); } public dispose() { + this.clearProgressTicker(); this.statusBar.dispose(); } + public get enabled() { + return this._enabled; + } public set enabled(enable: boolean) { + this._enabled = enable; if (enable) { this.statusBar.show(); } else { @@ -32,11 +53,10 @@ export class TestResultDisplay { .then(tests => this.updateTestRunWithSuccess(tests, debug)) .catch(this.updateTestRunWithFailure.bind(this)) // We don't care about any other exceptions returned by updateTestRunWithFailure - // tslint:disable-next-line:no-empty - .catch(() => { }); + .catch(noop); } public displayDiscoverStatus(testDiscovery: Promise, quietMode: boolean = false) { - this.displayProgress('Discovering Tests', 'Discovering Tests (Click to Stop)', constants.Commands.Tests_Ask_To_Stop_Discovery); + this.displayProgress('Discovering Tests', 'Discovering tests (click to stop)', constants.Commands.Tests_Ask_To_Stop_Discovery); return testDiscovery.then(tests => { this.updateWithDiscoverSuccess(tests, quietMode); return tests; @@ -78,11 +98,9 @@ export class TestResultDisplay { this.statusBar.text = statusText.length === 0 ? 'No Tests Ran' : statusText.join(' '); this.statusBar.color = foreColor; this.statusBar.command = constants.Commands.Tests_View_UI; - if (this.onDidChange) { - this.onDidChange.fire(); - } + this.didChange.fire(); if (statusText.length === 0 && !debug) { - vscode.window.showWarningMessage('No tests ran, please check the configuration settings for the tests.'); + this.appShell.showWarningMessage('No tests ran, please check the configuration settings for the tests.'); } return tests; } @@ -97,7 +115,7 @@ export class TestResultDisplay { } else { this.statusBar.text = '$(alert) Tests Failed'; this.statusBar.tooltip = 'Running Tests Failed'; - displayTestErrorMessage('There was an error in running the tests.'); + this.testsHelper.displayTestErrorMessage('There was an error in running the tests.'); } return Promise.reject(reason); } @@ -124,23 +142,14 @@ export class TestResultDisplay { } // tslint:disable-next-line:no-any - private disableTests(): Promise { - // tslint:disable-next-line:no-any - const def = createDeferred(); - const pythonConfig = vscode.workspace.getConfiguration('python'); + private async disableTests(): Promise { + const configurationService = this.serviceContainer.get(IConfigurationService); const settingsToDisable = ['unitTest.promptToConfigure', 'unitTest.pyTestEnabled', 'unitTest.unittestEnabled', 'unitTest.nosetestsEnabled']; - function disableTest() { - if (settingsToDisable.length === 0) { - return def.resolve(); - } - pythonConfig.update(settingsToDisable.shift()!, false) - .then(disableTest.bind(this), disableTest.bind(this)); + for (const setting of settingsToDisable) { + await configurationService.updateSettingAsync(setting, false).catch(noop); } - - disableTest(); - return def.promise; } private updateWithDiscoverSuccess(tests: Tests, quietMode: boolean = false) { @@ -150,12 +159,12 @@ export class TestResultDisplay { this.statusBar.tooltip = 'Run Tests'; this.statusBar.command = constants.Commands.Tests_View_UI; this.statusBar.show(); - if (this.onDidChange) { - this.onDidChange.fire(); + if (this.didChange) { + this.didChange.fire(); } if (!haveTests && !quietMode) { - vscode.window.showInformationMessage('No tests discovered, please check the configuration settings for the tests.', 'Disable Tests').then(item => { + this.appShell.showInformationMessage('No tests discovered, please check the configuration settings for the tests.', 'Disable Tests').then(item => { if (item === 'Disable Tests') { this.disableTests() .catch(ex => console.error('Python Extension: disableTests', ex)); @@ -181,7 +190,7 @@ export class TestResultDisplay { // tslint:disable-next-line:no-suspicious-comment // TODO: show an option that will invoke a command 'python.test.configureTest' or similar. // This will be hanlded by main.ts that will capture input from user and configure the tests. - vscode.window.showErrorMessage('There was an error in discovering tests, please check the configuration settings for the tests.'); + this.appShell.showErrorMessage('Test discovery error, please check the configuration settings for the tests.'); } } } diff --git a/src/client/unittests/display/picker.ts b/src/client/unittests/display/picker.ts index 7c054b88a06e..4544609322af 100644 --- a/src/client/unittests/display/picker.ts +++ b/src/client/unittests/display/picker.ts @@ -1,13 +1,24 @@ +import { inject, injectable } from 'inversify'; import * as path from 'path'; -import { commands, QuickPickItem, Uri, window } from 'vscode'; +import { commands, QuickPickItem, Uri } from 'vscode'; +import { IApplicationShell } from '../../common/application/types'; import * as constants from '../../common/constants'; +import { noop } from '../../common/core.utils'; +import { IServiceContainer } from '../../ioc/types'; import { CommandSource } from '../common/constants'; import { FlattenedTestFunction, ITestCollectionStorageService, TestFile, TestFunction, Tests, TestStatus, TestsToRun } from '../common/types'; +import { ITestDisplay } from '../types'; -export class TestDisplay { - constructor(private testCollectionStorage: ITestCollectionStorageService) { } +@injectable() +export class TestDisplay implements ITestDisplay { + private readonly testCollectionStorage: ITestCollectionStorageService; + private readonly appShell: IApplicationShell; + constructor(@inject(IServiceContainer) serviceRegistry: IServiceContainer) { + this.testCollectionStorage = serviceRegistry.get(ITestCollectionStorageService); + this.appShell = serviceRegistry.get(IApplicationShell); + } public displayStopTestUI(workspace: Uri, message: string) { - window.showQuickPick([message]).then(item => { + this.appShell.showQuickPick([message]).then(item => { if (item === message) { commands.executeCommand(constants.Commands.Tests_Stop, undefined, workspace); } @@ -15,12 +26,12 @@ export class TestDisplay { } public displayTestUI(cmdSource: CommandSource, wkspace: Uri) { const tests = this.testCollectionStorage.getTests(wkspace); - window.showQuickPick(buildItems(tests), { matchOnDescription: true, matchOnDetail: true }) - .then(item => onItemSelected(cmdSource, wkspace, item, false)); + this.appShell.showQuickPick(buildItems(tests), { matchOnDescription: true, matchOnDetail: true }) + .then(item => item ? onItemSelected(cmdSource, wkspace, item, false) : noop()); } public selectTestFunction(rootDirectory: string, tests: Tests): Promise { return new Promise((resolve, reject) => { - window.showQuickPick(buildItemsForFunctions(rootDirectory, tests.testFunctions), { matchOnDescription: true, matchOnDetail: true }) + this.appShell.showQuickPick(buildItemsForFunctions(rootDirectory, tests.testFunctions), { matchOnDescription: true, matchOnDetail: true }) .then(item => { if (item && item.fn) { return resolve(item.fn); @@ -31,7 +42,7 @@ export class TestDisplay { } public selectTestFile(rootDirectory: string, tests: Tests): Promise { return new Promise((resolve, reject) => { - window.showQuickPick(buildItemsForTestFiles(rootDirectory, tests.testFiles), { matchOnDescription: true, matchOnDetail: true }) + this.appShell.showQuickPick(buildItemsForTestFiles(rootDirectory, tests.testFiles), { matchOnDescription: true, matchOnDetail: true }) .then(item => { if (item && item.testFile) { return resolve(item.testFile); @@ -55,10 +66,9 @@ export class TestDisplay { testFunctions.some(testFunc => testFunc.nameToRun === fn.testFunction.nameToRun); }); - window.showQuickPick(buildItemsForFunctions(rootDirectory, flattenedFunctions, undefined, undefined, debug), - { matchOnDescription: true, matchOnDetail: true }).then(testItem => { - return onItemSelected(cmdSource, wkspace, testItem, debug); - }); + this.appShell.showQuickPick(buildItemsForFunctions(rootDirectory, flattenedFunctions, undefined, undefined, debug), + { matchOnDescription: true, matchOnDetail: true }) + .then(testItem => testItem ? onItemSelected(cmdSource, wkspace, testItem, debug) : noop()); } } @@ -95,7 +105,7 @@ function getSummary(tests?: Tests) { if (!tests || !tests.summary) { return ''; } - const statusText = []; + const statusText: string[] = []; if (tests.summary.passed > 0) { statusText.push(`${constants.Octicons.Test_Pass} ${tests.summary.passed} Passed`); } @@ -137,7 +147,7 @@ function buildItemsForFunctions(rootDirectory: string, tests: FlattenedTestFunct const functionItems: TestItem[] = []; tests.forEach(fn => { let icon = ''; - if (displayStatusIcons && statusIconMapping.has(fn.testFunction.status)) { + if (displayStatusIcons && fn.testFunction.status && statusIconMapping.has(fn.testFunction.status)) { icon = `${statusIconMapping.get(fn.testFunction.status)} `; } @@ -152,7 +162,7 @@ function buildItemsForFunctions(rootDirectory: string, tests: FlattenedTestFunct functionItems.sort((a, b) => { let sortAPrefix = '5-'; let sortBPrefix = '5-'; - if (sortBasedOnResults) { + if (sortBasedOnResults && a.fn && a.fn.testFunction.status && b.fn && b.fn.testFunction.status) { sortAPrefix = statusSortPrefix[a.fn.testFunction.status] ? statusSortPrefix[a.fn.testFunction.status] : sortAPrefix; sortBPrefix = statusSortPrefix[b.fn.testFunction.status] ? statusSortPrefix[b.fn.testFunction.status] : sortBPrefix; } @@ -177,10 +187,13 @@ function buildItemsForTestFiles(rootDirectory: string, testFiles: TestFile[]): T }; }); fileItems.sort((a, b) => { - if (a.detail < b.detail) { + if (!a.detail && !b.detail) { + return 0; + } + if (!a.detail || a.detail < b.detail!) { return -1; } - if (a.detail > b.detail) { + if (!b.detail || a.detail! > b.detail) { return 1; } return 0; @@ -221,13 +234,13 @@ function onItemSelected(cmdSource: CommandSource, wkspace: Uri, selection: TestI case Type.RunMethod: { cmd = constants.Commands.Tests_Run; // tslint:disable-next-line:prefer-type-cast no-object-literal-type-assertion - args.push({ testFunction: [selection.fn.testFunction] } as TestsToRun); + args.push({ testFunction: [selection.fn!.testFunction] } as TestsToRun); break; } case Type.DebugMethod: { cmd = constants.Commands.Tests_Debug; // tslint:disable-next-line:prefer-type-cast no-object-literal-type-assertion - args.push({ testFunction: [selection.fn.testFunction] } as TestsToRun); + args.push({ testFunction: [selection.fn!.testFunction] } as TestsToRun); args.push(true); break; } diff --git a/src/client/unittests/main.ts b/src/client/unittests/main.ts index 81b8ad6a9946..4dd1f63cfe3c 100644 --- a/src/client/unittests/main.ts +++ b/src/client/unittests/main.ts @@ -1,307 +1,322 @@ 'use strict'; + +// tslint:disable:no-duplicate-imports no-unnecessary-callback-wrapper + +import { inject, injectable } from 'inversify'; +import { ConfigurationChangeEvent, Disposable, OutputChannel, TextDocument, Uri } from 'vscode'; import * as vscode from 'vscode'; -// tslint:disable-next-line:no-duplicate-imports -import { Disposable, Uri, window, workspace } from 'vscode'; -import { PythonSettings } from '../common/configSettings'; +import { ICommandManager, IDocumentManager, IWorkspaceService } from '../common/application/types'; import * as constants from '../common/constants'; -import { IInstaller } from '../common/types'; +import { IConfigurationService, IDisposableRegistry, ILogger, IOutputChannel } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { PythonSymbolProvider } from '../providers/symbolProvider'; import { UNITTEST_STOP, UNITTEST_VIEW_OUTPUT } from '../telemetry/constants'; import { sendTelemetryEvent } from '../telemetry/index'; import { activateCodeLenses } from './codeLenses/main'; -import { CANCELLATION_REASON, CommandSource } from './common/constants'; +import { CANCELLATION_REASON, CommandSource, TEST_OUTPUT_CHANNEL } from './common/constants'; import { selectTestWorkspace } from './common/testUtils'; import { ITestCollectionStorageService, ITestManager, IWorkspaceTestManagerService, TestFile, TestFunction, TestStatus, TestsToRun } from './common/types'; -import { displayTestFrameworkError } from './configuration'; -import { TestResultDisplay } from './display/main'; -import { TestDisplay } from './display/picker'; - -let workspaceTestManagerService: IWorkspaceTestManagerService; -let testResultDisplay: TestResultDisplay; -let testDisplay: TestDisplay; -let outChannel: vscode.OutputChannel; -const onDidChange: vscode.EventEmitter = new vscode.EventEmitter(); -let testCollectionStorage: ITestCollectionStorageService; -let _serviceContaner: IServiceContainer; +import { ITestDisplay, ITestResultDisplay, IUnitTestConfigurationService, IUnitTestManagementService } from './types'; -export function activate(context: vscode.ExtensionContext, outputChannel: vscode.OutputChannel, symboldProvider: PythonSymbolProvider, serviceContainer: IServiceContainer) { - _serviceContaner = serviceContainer; +@injectable() +export class UnitTestManagementService implements IUnitTestManagementService, Disposable { + private readonly outputChannel: vscode.OutputChannel; + private readonly disposableRegistry: Disposable[]; + private workspaceTestManagerService?: IWorkspaceTestManagerService; + private documentManager: IDocumentManager; + private workspaceService: IWorkspaceService; + private testResultDisplay?: ITestResultDisplay; + private autoDiscoverTimer?: NodeJS.Timer; + private configChangedTimer?: NodeJS.Timer; + private readonly onDidChange: vscode.EventEmitter = new vscode.EventEmitter(); - context.subscriptions.push({ dispose: dispose }); - outChannel = outputChannel; - const disposables = registerCommands(); - context.subscriptions.push(...disposables); + constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { + this.disposableRegistry = serviceContainer.get(IDisposableRegistry); + this.outputChannel = serviceContainer.get(IOutputChannel, TEST_OUTPUT_CHANNEL); + this.workspaceService = serviceContainer.get(IWorkspaceService); + this.documentManager = serviceContainer.get(IDocumentManager); - testCollectionStorage = serviceContainer.get(ITestCollectionStorageService); - workspaceTestManagerService = serviceContainer.get(IWorkspaceTestManagerService); - - context.subscriptions.push(autoResetTests()); - context.subscriptions.push(activateCodeLenses(onDidChange, symboldProvider, testCollectionStorage)); - context.subscriptions.push(vscode.workspace.onDidSaveTextDocument(onDocumentSaved)); - - autoDiscoverTests(); -} -async function getTestManager(displayTestNotConfiguredMessage: boolean, resource?: Uri): Promise { - let wkspace: Uri | undefined; - if (resource) { - const wkspaceFolder = workspace.getWorkspaceFolder(resource); - wkspace = wkspaceFolder ? wkspaceFolder.uri : undefined; - } else { - wkspace = await selectTestWorkspace(); - } - if (!wkspace) { - return; - } - const testManager = workspaceTestManagerService.getTestManager(wkspace); - if (testManager) { - return testManager; - } - if (displayTestNotConfiguredMessage) { - await displayTestFrameworkError(wkspace, outChannel, _serviceContaner.get(IInstaller)); - } -} -let timeoutId: NodeJS.Timer; -async function onDocumentSaved(doc: vscode.TextDocument): Promise { - const testManager = await getTestManager(false, doc.uri); - if (!testManager) { - return; - } - const tests = await testManager.discoverTests(CommandSource.auto, false, true); - if (!tests || !Array.isArray(tests.testFiles) || tests.testFiles.length === 0) { - return; + this.disposableRegistry.push(this); } - if (tests.testFiles.findIndex((f: TestFile) => f.fullPath === doc.uri.fsPath) === -1) { - return; + public dispose() { + if (this.workspaceTestManagerService) { + this.workspaceTestManagerService.dispose(); + } } + public async activate(): Promise { + this.workspaceTestManagerService = this.serviceContainer.get(IWorkspaceTestManagerService); - if (timeoutId) { - clearTimeout(timeoutId); + this.registerHandlers(); + this.registerCommands(); + this.autoDiscoverTests() + .catch(ex => this.serviceContainer.get(ILogger).logError('Failed to auto discover tests upon activation', ex)); + } + public async activateCodeLenses(symboldProvider: PythonSymbolProvider): Promise { + const testCollectionStorage = this.serviceContainer.get(ITestCollectionStorageService); + this.disposableRegistry.push(activateCodeLenses(this.onDidChange, symboldProvider, testCollectionStorage)); + } + public async getTestManager(displayTestNotConfiguredMessage: boolean, resource?: Uri): Promise { + let wkspace: Uri | undefined; + if (resource) { + const wkspaceFolder = this.workspaceService.getWorkspaceFolder(resource); + wkspace = wkspaceFolder ? wkspaceFolder.uri : undefined; + } else { + wkspace = await selectTestWorkspace(); + } + if (!wkspace) { + return; + } + const testManager = this.workspaceTestManagerService!.getTestManager(wkspace); + if (testManager) { + return testManager; + } + if (displayTestNotConfiguredMessage) { + const configurationService = this.serviceContainer.get(IUnitTestConfigurationService); + await configurationService.displayTestFrameworkError(wkspace); + } } - timeoutId = setTimeout(() => discoverTests(CommandSource.auto, doc.uri, true, false, true), 1000); -} + public async configurationChangeHandler(e: ConfigurationChangeEvent) { + // If there's one workspace, then stop the tests and restart, + // else let the user do this manually. + if (!this.workspaceService.hasWorkspaceFolders || this.workspaceService.workspaceFolders!.length > 1) { + return; + } -function dispose() { - workspaceTestManagerService.dispose(); - testCollectionStorage.dispose(); -} -function registerCommands(): vscode.Disposable[] { - const disposables: Disposable[] = []; - disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Discover, (_, cmdSource: CommandSource = CommandSource.commandPalette, resource?: Uri) => { - // Ignore the exceptions returned. - // This command will be invoked else where in the extension. - // tslint:disable-next-line:no-empty - discoverTests(cmdSource, resource, true, true).catch(() => { }); - })); - disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Run_Failed, (_, cmdSource: CommandSource = CommandSource.commandPalette, resource: Uri) => runTestsImpl(cmdSource, resource, undefined, true))); - // tslint:disable-next-line:no-unnecessary-callback-wrapper - disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Run, (_, cmdSource: CommandSource = CommandSource.commandPalette, file: Uri, testToRun?: TestsToRun) => runTestsImpl(cmdSource, file, testToRun))); - disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Debug, (_, cmdSource: CommandSource = CommandSource.commandPalette, file: Uri, testToRun: TestsToRun) => runTestsImpl(cmdSource, file, testToRun, false, true))); - // tslint:disable-next-line:no-unnecessary-callback-wrapper - disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_View_UI, () => displayUI(CommandSource.commandPalette))); - // tslint:disable-next-line:no-unnecessary-callback-wrapper - disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Picker_UI, (_, cmdSource: CommandSource = CommandSource.commandPalette, file: Uri, testFunctions: TestFunction[]) => displayPickerUI(cmdSource, file, testFunctions))); - disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Picker_UI_Debug, (_, cmdSource: CommandSource = CommandSource.commandPalette, file: Uri, testFunctions: TestFunction[]) => displayPickerUI(cmdSource, file, testFunctions, true))); - // tslint:disable-next-line:no-unnecessary-callback-wrapper - disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Stop, (_, resource: Uri) => stopTests(resource))); - // tslint:disable-next-line:no-unnecessary-callback-wrapper - disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_ViewOutput, (_, cmdSource: CommandSource = CommandSource.commandPalette) => viewOutput(cmdSource))); - disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Ask_To_Stop_Discovery, () => displayStopUI('Stop discovering tests'))); - disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Ask_To_Stop_Test, () => displayStopUI('Stop running tests'))); - // tslint:disable-next-line:no-unnecessary-callback-wrapper - disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Select_And_Run_Method, (_, cmdSource: CommandSource = CommandSource.commandPalette, resource: Uri) => selectAndRunTestMethod(cmdSource, resource))); - disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Select_And_Debug_Method, (_, cmdSource: CommandSource = CommandSource.commandPalette, resource: Uri) => selectAndRunTestMethod(cmdSource, resource, true))); - // tslint:disable-next-line:no-unnecessary-callback-wrapper - disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Select_And_Run_File, (_, cmdSource: CommandSource = CommandSource.commandPalette) => selectAndRunTestFile(cmdSource))); - // tslint:disable-next-line:no-unnecessary-callback-wrapper - disposables.push(vscode.commands.registerCommand(constants.Commands.Tests_Run_Current_File, (_, cmdSource: CommandSource = CommandSource.commandPalette) => runCurrentTestFile(cmdSource))); + const workspaceUri = this.workspaceService.workspaceFolders![0].uri; + if (!e.affectsConfiguration('python.unitTest', workspaceUri)) { + return; + } + const settings = this.serviceContainer.get(IConfigurationService).getSettings(workspaceUri); + if (!settings.unitTest.nosetestsEnabled && !settings.unitTest.pyTestEnabled && !settings.unitTest.unittestEnabled) { + if (this.testResultDisplay) { + this.testResultDisplay.enabled = false; + } + // TODO: Why are we disposing, what happens when tests are enabled. + if (this.workspaceTestManagerService) { + this.workspaceTestManagerService.dispose(); + } + return; + } + if (this.testResultDisplay) { + this.testResultDisplay.enabled = true; + } + this.autoDiscoverTests() + .catch(ex => this.serviceContainer.get(ILogger).logError('Failed to auto discover tests upon activation', ex)); + } - return disposables; -} + public async discoverTestsForDocument(doc: TextDocument): Promise { + const testManager = await this.getTestManager(false, doc.uri); + if (!testManager) { + return; + } + const tests = await testManager.discoverTests(CommandSource.auto, false, true); + if (!tests || !Array.isArray(tests.testFiles) || tests.testFiles.length === 0) { + return; + } + if (tests.testFiles.findIndex((f: TestFile) => f.fullPath === doc.uri.fsPath) === -1) { + return; + } -function viewOutput(cmdSource: CommandSource) { - sendTelemetryEvent(UNITTEST_VIEW_OUTPUT); - outChannel.show(); -} -async function displayUI(cmdSource: CommandSource) { - const testManager = await getTestManager(true); - if (!testManager) { - return; + if (this.autoDiscoverTimer) { + clearTimeout(this.autoDiscoverTimer); + } + this.autoDiscoverTimer = setTimeout(() => this.discoverTests(CommandSource.auto, doc.uri, true, false, true), 1000); } + public async autoDiscoverTests() { + if (!this.workspaceService.hasWorkspaceFolders) { + return; + } + const configurationService = this.serviceContainer.get(IConfigurationService); + const settings = configurationService.getSettings(); + if (!settings.unitTest.nosetestsEnabled && !settings.unitTest.pyTestEnabled && !settings.unitTest.unittestEnabled) { + return; + } - testDisplay = testDisplay ? testDisplay : new TestDisplay(testCollectionStorage); - testDisplay.displayTestUI(cmdSource, testManager.workspaceFolder); -} -async function displayPickerUI(cmdSource: CommandSource, file: Uri, testFunctions: TestFunction[], debug?: boolean) { - const testManager = await getTestManager(true, file); - if (!testManager) { - return; + // No need to display errors. + // tslint:disable-next-line:no-empty + this.discoverTests(CommandSource.auto, this.workspaceService.workspaceFolders![0].uri, true).catch(() => { }); } + public async discoverTests(cmdSource: CommandSource, resource?: Uri, ignoreCache?: boolean, userInitiated?: boolean, quietMode?: boolean) { + const testManager = await this.getTestManager(true, resource); + if (!testManager) { + return; + } - testDisplay = testDisplay ? testDisplay : new TestDisplay(testCollectionStorage); - testDisplay.displayFunctionTestPickerUI(cmdSource, testManager.workspaceFolder, testManager.workingDirectory, file, testFunctions, debug); -} -async function selectAndRunTestMethod(cmdSource: CommandSource, resource: Uri, debug?: boolean) { - const testManager = await getTestManager(true, resource); - if (!testManager) { - return; - } - try { - await testManager.discoverTests(cmdSource, true, true, true); - } catch (ex) { - return; - } + if (testManager.status === TestStatus.Discovering || testManager.status === TestStatus.Running) { + return; + } - const tests = testCollectionStorage.getTests(testManager.workspaceFolder)!; - testDisplay = testDisplay ? testDisplay : new TestDisplay(testCollectionStorage); - const selectedTestFn = await testDisplay.selectTestFunction(testManager.workspaceFolder.fsPath, tests); - if (!selectedTestFn) { - return; - } - // tslint:disable-next-line:prefer-type-cast no-object-literal-type-assertion - await runTestsImpl(cmdSource, testManager.workspaceFolder, { testFunction: [selectedTestFn.testFunction] } as TestsToRun, false, debug); -} -async function selectAndRunTestFile(cmdSource: CommandSource) { - const testManager = await getTestManager(true); - if (!testManager) { - return; + if (!this.testResultDisplay) { + this.testResultDisplay = this.serviceContainer.get(ITestResultDisplay); + this.testResultDisplay.onDidChange(() => this.onDidChange.fire()); + } + const discoveryPromise = testManager.discoverTests(cmdSource, ignoreCache, quietMode, userInitiated); + this.testResultDisplay.displayDiscoverStatus(discoveryPromise, quietMode) + .catch(ex => console.error('Python Extension: displayDiscoverStatus', ex)); + await discoveryPromise; } - try { - await testManager.discoverTests(cmdSource, true, true, true); - } catch (ex) { - return; + public async stopTests(resource: Uri) { + sendTelemetryEvent(UNITTEST_STOP); + const testManager = await this.getTestManager(true, resource); + if (testManager) { + testManager.stop(); + } } + public async displayStopUI(message: string): Promise { + const testManager = await this.getTestManager(true); + if (!testManager) { + return; + } - const tests = testCollectionStorage.getTests(testManager.workspaceFolder)!; - testDisplay = testDisplay ? testDisplay : new TestDisplay(testCollectionStorage); - const selectedFile = await testDisplay.selectTestFile(testManager.workspaceFolder.fsPath, tests); - if (!selectedFile) { - return; - } - // tslint:disable-next-line:prefer-type-cast no-object-literal-type-assertion - await runTestsImpl(cmdSource, testManager.workspaceFolder, { testFile: [selectedFile] } as TestsToRun); -} -async function runCurrentTestFile(cmdSource: CommandSource) { - if (!window.activeTextEditor) { - return; - } - const testManager = await getTestManager(true, window.activeTextEditor.document.uri); - if (!testManager) { - return; + const testDisplay = this.serviceContainer.get(ITestDisplay); + testDisplay.displayStopTestUI(testManager.workspaceFolder, message); } - try { - await testManager.discoverTests(cmdSource, true, true, true); - } catch (ex) { - return; - } - const tests = testCollectionStorage.getTests(testManager.workspaceFolder)!; - const testFiles = tests.testFiles.filter(testFile => { - return testFile.fullPath === window.activeTextEditor!.document.uri.fsPath; - }); - if (testFiles.length < 1) { - return; - } - // tslint:disable-next-line:prefer-type-cast no-object-literal-type-assertion - await runTestsImpl(cmdSource, testManager.workspaceFolder, { testFile: [testFiles[0]] } as TestsToRun); -} -async function displayStopUI(message: string) { - const testManager = await getTestManager(true); - if (!testManager) { - return; - } - - testDisplay = testDisplay ? testDisplay : new TestDisplay(testCollectionStorage); - testDisplay.displayStopTestUI(testManager.workspaceFolder, message); -} + public async displayUI(cmdSource: CommandSource) { + const testManager = await this.getTestManager(true); + if (!testManager) { + return; + } -let uniTestSettingsString: string; -function autoResetTests() { - if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length > 1) { - // tslint:disable-next-line:no-empty - return { dispose: () => { } }; + const testDisplay = this.serviceContainer.get(ITestDisplay); + testDisplay.displayTestUI(cmdSource, testManager.workspaceFolder); } + public async displayPickerUI(cmdSource: CommandSource, file: Uri, testFunctions: TestFunction[], debug?: boolean) { + const testManager = await this.getTestManager(true, file); + if (!testManager) { + return; + } - const settings = PythonSettings.getInstance(); - uniTestSettingsString = JSON.stringify(settings.unitTest); - return workspace.onDidChangeConfiguration(() => setTimeout(onConfigChanged, 1000)); -} -function onConfigChanged() { - // If there's one workspace, then stop the tests and restart, - // else let the user do this manually. - if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length > 1) { - return; + const testDisplay = this.serviceContainer.get(ITestDisplay); + testDisplay.displayFunctionTestPickerUI(cmdSource, testManager.workspaceFolder, testManager.workingDirectory, file, testFunctions, debug); } - const settings = PythonSettings.getInstance(); - - // Possible that a test framework has been enabled or some settings have changed. - // Meaning we need to re-load the discovered tests (as something could have changed). - const newSettings = JSON.stringify(settings.unitTest); - if (uniTestSettingsString === newSettings) { - return; + public viewOutput(cmdSource: CommandSource) { + sendTelemetryEvent(UNITTEST_VIEW_OUTPUT); + this.outputChannel.show(); } + public async selectAndRunTestMethod(cmdSource: CommandSource, resource: Uri, debug?: boolean) { + const testManager = await this.getTestManager(true, resource); + if (!testManager) { + return; + } + try { + await testManager.discoverTests(cmdSource, true, true, true); + } catch (ex) { + return; + } - uniTestSettingsString = newSettings; - if (!settings.unitTest.nosetestsEnabled && !settings.unitTest.pyTestEnabled && !settings.unitTest.unittestEnabled) { - if (testResultDisplay) { - testResultDisplay.enabled = false; + const testCollectionStorage = this.serviceContainer.get(ITestCollectionStorageService); + const tests = testCollectionStorage.getTests(testManager.workspaceFolder)!; + const testDisplay = this.serviceContainer.get(ITestDisplay); + const selectedTestFn = await testDisplay.selectTestFunction(testManager.workspaceFolder.fsPath, tests); + if (!selectedTestFn) { + return; } - workspaceTestManagerService.dispose(); - return; - } - if (testResultDisplay) { - testResultDisplay.enabled = true; - } - autoDiscoverTests(); -} -function autoDiscoverTests() { - if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length > 1) { - return; - } - const settings = PythonSettings.getInstance(); - if (!settings.unitTest.nosetestsEnabled && !settings.unitTest.pyTestEnabled && !settings.unitTest.unittestEnabled) { - return; + // tslint:disable-next-line:prefer-type-cast no-object-literal-type-assertion + await this.runTestsImpl(cmdSource, testManager.workspaceFolder, { testFunction: [selectedTestFn.testFunction] } as TestsToRun, false, debug); } + public async selectAndRunTestFile(cmdSource: CommandSource) { + const testManager = await this.getTestManager(true); + if (!testManager) { + return; + } + try { + await testManager.discoverTests(cmdSource, true, true, true); + } catch (ex) { + return; + } - // No need to display errors. - // tslint:disable-next-line:no-empty - discoverTests(CommandSource.auto, workspace.workspaceFolders[0].uri, true).catch(() => { }); -} -async function stopTests(resource: Uri) { - sendTelemetryEvent(UNITTEST_STOP); - const testManager = await getTestManager(true, resource); - if (testManager) { - testManager.stop(); + const testCollectionStorage = this.serviceContainer.get(ITestCollectionStorageService); + const tests = testCollectionStorage.getTests(testManager.workspaceFolder)!; + const testDisplay = this.serviceContainer.get(ITestDisplay); + const selectedFile = await testDisplay.selectTestFile(testManager.workspaceFolder.fsPath, tests); + if (!selectedFile) { + return; + } + await this.runTestsImpl(cmdSource, testManager.workspaceFolder, { testFile: [selectedFile] }); } -} -async function discoverTests(cmdSource: CommandSource, resource?: Uri, ignoreCache?: boolean, userInitiated?: boolean, quietMode?: boolean) { - const testManager = await getTestManager(true, resource); - if (!testManager) { - return; + public async runCurrentTestFile(cmdSource: CommandSource) { + if (!this.documentManager.activeTextEditor) { + return; + } + const testManager = await this.getTestManager(true, this.documentManager.activeTextEditor.document.uri); + if (!testManager) { + return; + } + try { + await testManager.discoverTests(cmdSource, true, true, true); + } catch (ex) { + return; + } + const testCollectionStorage = this.serviceContainer.get(ITestCollectionStorageService); + const tests = testCollectionStorage.getTests(testManager.workspaceFolder)!; + const testFiles = tests.testFiles.filter(testFile => { + return testFile.fullPath === this.documentManager.activeTextEditor!.document.uri.fsPath; + }); + if (testFiles.length < 1) { + return; + } + await this.runTestsImpl(cmdSource, testManager.workspaceFolder, { testFile: [testFiles[0]] }); } - if (testManager && (testManager.status !== TestStatus.Discovering && testManager.status !== TestStatus.Running)) { - testResultDisplay = testResultDisplay ? testResultDisplay : new TestResultDisplay(onDidChange); - const discoveryPromise = testManager.discoverTests(cmdSource, ignoreCache, quietMode, userInitiated); - testResultDisplay.displayDiscoverStatus(discoveryPromise, quietMode) - .catch(ex => console.error('Python Extension: displayDiscoverStatus', ex)); - await discoveryPromise; + public async runTestsImpl(cmdSource: CommandSource, resource?: Uri, testsToRun?: TestsToRun, runFailedTests?: boolean, debug: boolean = false) { + const testManager = await this.getTestManager(true, resource); + if (!testManager) { + return; + } + + if (!this.testResultDisplay) { + this.testResultDisplay = this.serviceContainer.get(ITestResultDisplay); + this.testResultDisplay.onDidChange(() => this.onDidChange.fire()); + } + + const promise = testManager.runTest(cmdSource, testsToRun, runFailedTests, debug) + .catch(reason => { + if (reason !== CANCELLATION_REASON) { + this.outputChannel.appendLine(`Error: ${reason}`); + } + return Promise.reject(reason); + }); + + this.testResultDisplay.displayProgressStatus(promise, debug); + await promise; } -} -async function runTestsImpl(cmdSource: CommandSource, resource?: Uri, testsToRun?: TestsToRun, runFailedTests?: boolean, debug: boolean = false) { - const testManager = await getTestManager(true, resource); - if (!testManager) { - return; + private registerCommands(): void { + const disposablesRegistry = this.serviceContainer.get(IDisposableRegistry); + const commandManager = this.serviceContainer.get(ICommandManager); + + const disposables = [ + commandManager.registerCommand(constants.Commands.Tests_Discover, (_, cmdSource: CommandSource = CommandSource.commandPalette, resource?: Uri) => { + // Ignore the exceptions returned. + // This command will be invoked from other places of the extension. + this.discoverTests(cmdSource, resource, true, true).ignoreErrors(); + }), + commandManager.registerCommand(constants.Commands.Tests_Run_Failed, (_, cmdSource: CommandSource = CommandSource.commandPalette, resource: Uri) => this.runTestsImpl(cmdSource, resource, undefined, true)), + commandManager.registerCommand(constants.Commands.Tests_Run, (_, cmdSource: CommandSource = CommandSource.commandPalette, file: Uri, testToRun?: TestsToRun) => this.runTestsImpl(cmdSource, file, testToRun)), + commandManager.registerCommand(constants.Commands.Tests_Debug, (_, cmdSource: CommandSource = CommandSource.commandPalette, file: Uri, testToRun: TestsToRun) => this.runTestsImpl(cmdSource, file, testToRun, false, true)), + commandManager.registerCommand(constants.Commands.Tests_View_UI, () => this.displayUI(CommandSource.commandPalette)), + commandManager.registerCommand(constants.Commands.Tests_Picker_UI, (_, cmdSource: CommandSource = CommandSource.commandPalette, file: Uri, testFunctions: TestFunction[]) => this.displayPickerUI(cmdSource, file, testFunctions)), + commandManager.registerCommand(constants.Commands.Tests_Picker_UI_Debug, (_, cmdSource: CommandSource = CommandSource.commandPalette, file: Uri, testFunctions: TestFunction[]) => this.displayPickerUI(cmdSource, file, testFunctions, true)), + commandManager.registerCommand(constants.Commands.Tests_Stop, (_, resource: Uri) => this.stopTests(resource)), + commandManager.registerCommand(constants.Commands.Tests_ViewOutput, (_, cmdSource: CommandSource = CommandSource.commandPalette) => this.viewOutput(cmdSource)), + commandManager.registerCommand(constants.Commands.Tests_Ask_To_Stop_Discovery, () => this.displayStopUI('Stop discovering tests')), + commandManager.registerCommand(constants.Commands.Tests_Ask_To_Stop_Test, () => this.displayStopUI('Stop running tests')), + commandManager.registerCommand(constants.Commands.Tests_Select_And_Run_Method, (_, cmdSource: CommandSource = CommandSource.commandPalette, resource: Uri) => this.selectAndRunTestMethod(cmdSource, resource)), + commandManager.registerCommand(constants.Commands.Tests_Select_And_Debug_Method, (_, cmdSource: CommandSource = CommandSource.commandPalette, resource: Uri) => this.selectAndRunTestMethod(cmdSource, resource, true)), + commandManager.registerCommand(constants.Commands.Tests_Select_And_Run_File, (_, cmdSource: CommandSource = CommandSource.commandPalette) => this.selectAndRunTestFile(cmdSource)), + commandManager.registerCommand(constants.Commands.Tests_Run_Current_File, (_, cmdSource: CommandSource = CommandSource.commandPalette) => this.runCurrentTestFile(cmdSource)) + ]; + + disposablesRegistry.push(...disposables); } + private registerHandlers() { + const documentManager = this.serviceContainer.get(IDocumentManager); - testResultDisplay = testResultDisplay ? testResultDisplay : new TestResultDisplay(onDidChange); - const promise = testManager.runTest(cmdSource, testsToRun, runFailedTests, debug) - .catch(reason => { - if (reason !== CANCELLATION_REASON) { - outChannel.appendLine(`Error: ${reason}`); + this.disposableRegistry.push(documentManager.onDidSaveTextDocument(this.discoverTestsForDocument.bind(this))); + this.disposableRegistry.push(this.workspaceService.onDidChangeConfiguration(e => { + if (this.configChangedTimer) { + clearTimeout(this.configChangedTimer); } - return Promise.reject(reason); - }); - - testResultDisplay.displayProgressStatus(promise, debug); - await promise; + this.configChangedTimer = setTimeout(() => this.configurationChangeHandler(e), 1000); + })); + } } diff --git a/src/client/unittests/nosetest/testConfigurationManager.ts b/src/client/unittests/nosetest/testConfigurationManager.ts index c98e043920c9..3003e42aa1db 100644 --- a/src/client/unittests/nosetest/testConfigurationManager.ts +++ b/src/client/unittests/nosetest/testConfigurationManager.ts @@ -1,35 +1,30 @@ -import * as fs from 'fs'; import * as path from 'path'; -import * as vscode from 'vscode'; import { Uri } from 'vscode'; -import { IInstaller, Product } from '../../common/types'; +import { IFileSystem } from '../../common/platform/types'; +import { Product } from '../../common/types'; +import { IServiceContainer } from '../../ioc/types'; import { TestConfigurationManager } from '../common/managers/testConfigurationManager'; -import { ITestConfigSettingsService } from '../common/types'; export class ConfigurationManager extends TestConfigurationManager { - constructor(workspace: Uri, outputChannel: vscode.OutputChannel, - installer: IInstaller, testConfigSettingsService: ITestConfigSettingsService) { - super(workspace, Product.nosetest, outputChannel, installer, testConfigSettingsService); + constructor(workspace: Uri, serviceContainer: IServiceContainer) { + super(workspace, Product.nosetest, serviceContainer); } - private static async configFilesExist(rootDir: string): Promise { - const promises = ['.noserc', 'nose.cfg'].map(cfg => { - return new Promise(resolve => { - fs.exists(path.join(rootDir, cfg), exists => { resolve(exists ? cfg : ''); }); - }); - }); - const values = await Promise.all(promises); - return values.filter(exists => exists.length > 0); + public async requiresUserToConfigure(wkspace: Uri): Promise { + const fs = this.serviceContainer.get(IFileSystem); + for (const cfg of ['.noserc', 'nose.cfg']) { + if (await fs.fileExists(path.join(wkspace.fsPath, cfg))) { + return true; + } + } + return false; } - // tslint:disable-next-line:no-any - public async configure(wkspace: Uri): Promise { + public async configure(wkspace: Uri): Promise { const args: string[] = []; const configFileOptionLabel = 'Use existing config file'; - const configFiles = await ConfigurationManager.configFilesExist(wkspace.fsPath); // If a config file exits, there's nothing to be configured. - if (configFiles.length > 0) { + if (await this.requiresUserToConfigure(wkspace)) { return; } - const subDirs = await this.getTestDirs(wkspace.fsPath); const testDir = await this.selectTestDir(wkspace.fsPath, subDirs); if (typeof testDir === 'string' && testDir !== configFileOptionLabel) { diff --git a/src/client/unittests/pytest/testConfigurationManager.ts b/src/client/unittests/pytest/testConfigurationManager.ts index f61398ae3431..54b68718661c 100644 --- a/src/client/unittests/pytest/testConfigurationManager.ts +++ b/src/client/unittests/pytest/testConfigurationManager.ts @@ -1,31 +1,27 @@ -import * as fs from 'fs'; import * as path from 'path'; -import * as vscode from 'vscode'; -import { Uri } from 'vscode'; -import { IInstaller, Product } from '../../common/types'; +import { QuickPickItem, Uri } from 'vscode'; +import { IFileSystem } from '../../common/platform/types'; +import { Product } from '../../common/types'; +import { IServiceContainer } from '../../ioc/types'; import { TestConfigurationManager } from '../common/managers/testConfigurationManager'; -import { ITestConfigSettingsService } from '../common/types'; export class ConfigurationManager extends TestConfigurationManager { - constructor(workspace: Uri, outputChannel: vscode.OutputChannel, - installer: IInstaller, testConfigSettingsService: ITestConfigSettingsService) { - super(workspace, Product.pytest, outputChannel, installer, testConfigSettingsService); + constructor(workspace: Uri, serviceContainer: IServiceContainer) { + super(workspace, Product.pytest, serviceContainer); } - private static async configFilesExist(rootDir: string): Promise { - const promises = ['pytest.ini', 'tox.ini', 'setup.cfg'].map(cfg => { - return new Promise(resolve => { - fs.exists(path.join(rootDir, cfg), exists => { resolve(exists ? cfg : ''); }); - }); - }); - const values = await Promise.all(promises); - return values.filter(exists => exists.length > 0); + public async requiresUserToConfigure(wkspace: Uri): Promise { + const configFiles = await this.getConfigFiles(wkspace.fsPath); + // If a config file exits, there's nothing to be configured. + if (configFiles.length > 0 && configFiles.length !== 1 && configFiles[0] !== 'setup.cfg') { + return false; + } + return true; } - // tslint:disable-next-line:no-any public async configure(wkspace: Uri) { const args: string[] = []; const configFileOptionLabel = 'Use existing config file'; - const options: vscode.QuickPickItem[] = []; - const configFiles = await ConfigurationManager.configFilesExist(wkspace.fsPath); + const options: QuickPickItem[] = []; + const configFiles = await this.getConfigFiles(wkspace.fsPath); // If a config file exits, there's nothing to be configured. if (configFiles.length > 0 && configFiles.length !== 1 && configFiles[0] !== 'setup.cfg') { return; @@ -48,4 +44,11 @@ export class ConfigurationManager extends TestConfigurationManager { } await this.testConfigSettingsService.updateTestArgs(wkspace.fsPath, Product.pytest, args); } + private async getConfigFiles(rootDir: string): Promise { + const fs = this.serviceContainer.get(IFileSystem); + const promises = ['pytest.ini', 'tox.ini', 'setup.cfg'] + .map(async cfg => await fs.fileExists(path.join(rootDir, cfg)) ? cfg : ''); + const values = await Promise.all(promises); + return values.filter(exists => exists.length > 0); + } } diff --git a/src/client/unittests/serviceRegistry.ts b/src/client/unittests/serviceRegistry.ts index d500c67070dd..92a4f1b5f6e7 100644 --- a/src/client/unittests/serviceRegistry.ts +++ b/src/client/unittests/serviceRegistry.ts @@ -5,6 +5,7 @@ import { Uri } from 'vscode'; import { IServiceContainer, IServiceManager } from '../ioc/types'; import { NOSETEST_PROVIDER, PYTEST_PROVIDER, UNITTEST_PROVIDER } from './common/constants'; import { DebugLauncher } from './common/debugLauncher'; +import { TestConfigSettingsService } from './common/services/configSettingService'; import { TestCollectionStorageService } from './common/services/storageService'; import { TestManagerService } from './common/services/testManagerService'; import { TestResultsService } from './common/services/testResultsService'; @@ -13,14 +14,22 @@ import { TestsHelper } from './common/testUtils'; import { TestFlatteningVisitor } from './common/testVisitors/flatteningVisitor'; import { TestFolderGenerationVisitor } from './common/testVisitors/folderGenerationVisitor'; import { TestResultResetVisitor } from './common/testVisitors/resultResetVisitor'; -import { ITestCollectionStorageService, ITestDebugLauncher, ITestDiscoveryService, ITestManager, ITestManagerFactory, ITestManagerService, ITestManagerServiceFactory, IUnitTestSocketServer } from './common/types'; -import { ITestResultsService, ITestsHelper, ITestsParser, ITestVisitor, IWorkspaceTestManagerService, TestProvider } from './common/types'; +import { + ITestCollectionStorageService, ITestConfigSettingsService, ITestDebugLauncher, ITestDiscoveryService, ITestManager, ITestManagerFactory, ITestManagerService, ITestManagerServiceFactory, + ITestResultsService, ITestsHelper, ITestsParser, ITestVisitor, IUnitTestSocketServer, IWorkspaceTestManagerService, TestProvider +} from './common/types'; +import { UnitTestConfigurationService } from './configuration'; +import { TestConfigurationManagerFactory } from './configurationFactory'; +import { TestResultDisplay } from './display/main'; +import { TestDisplay } from './display/picker'; +import { UnitTestManagementService } from './main'; import { TestManager as NoseTestManager } from './nosetest/main'; import { TestDiscoveryService as NoseTestDiscoveryService } from './nosetest/services/discoveryService'; import { TestsParser as NoseTestTestsParser } from './nosetest/services/parserService'; import { TestManager as PyTestTestManager } from './pytest/main'; import { TestDiscoveryService as PytestTestDiscoveryService } from './pytest/services/discoveryService'; import { TestsParser as PytestTestsParser } from './pytest/services/parserService'; +import { ITestConfigurationManagerFactory, ITestDisplay, ITestResultDisplay, IUnitTestConfigurationService, IUnitTestManagementService } from './types'; import { TestManager as UnitTestTestManager } from './unittest/main'; import { TestDiscoveryService as UnitTestTestDiscoveryService } from './unittest/services/discoveryService'; import { TestsParser as UnitTestTestsParser } from './unittest/services/parserService'; @@ -48,6 +57,13 @@ export function registerTypes(serviceManager: IServiceManager) { serviceManager.add(ITestDiscoveryService, PytestTestDiscoveryService, PYTEST_PROVIDER); serviceManager.add(ITestDiscoveryService, NoseTestDiscoveryService, NOSETEST_PROVIDER); + serviceManager.addSingleton(IUnitTestConfigurationService, UnitTestConfigurationService); + serviceManager.addSingleton(IUnitTestManagementService, UnitTestManagementService); + serviceManager.addSingleton(ITestResultDisplay, TestResultDisplay); + serviceManager.addSingleton(ITestDisplay, TestDisplay); + serviceManager.addSingleton(ITestConfigSettingsService, TestConfigSettingsService); + serviceManager.addSingleton(ITestConfigurationManagerFactory, TestConfigurationManagerFactory); + serviceManager.addFactory(ITestManagerFactory, (context) => { return (testProvider: TestProvider, workspaceFolder: Uri, rootDirectory: string) => { const serviceContainer = context.container.get(IServiceContainer); diff --git a/src/client/unittests/types.ts b/src/client/unittests/types.ts new file mode 100644 index 000000000000..ebf063581354 --- /dev/null +++ b/src/client/unittests/types.ts @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { Disposable, Event, TextDocument, Uri } from 'vscode'; +import { Product } from '../common/types'; +import { PythonSymbolProvider } from '../providers/symbolProvider'; +import { CommandSource } from './common/constants'; +import { FlattenedTestFunction, ITestManager, TestFile, TestFunction, Tests, TestsToRun, UnitTestProduct } from './common/types'; + +export const IUnitTestConfigurationService = Symbol('IUnitTestConfigurationService'); +export interface IUnitTestConfigurationService { + displayTestFrameworkError(wkspace: Uri): Promise; + selectTestRunner(placeHolderMessage: string): Promise; + enableTest(wkspace: Uri, product: UnitTestProduct); +} + +export const ITestResultDisplay = Symbol('ITestResultDisplay'); + +export interface ITestResultDisplay extends Disposable { + enabled: boolean; + readonly onDidChange: Event; + displayProgressStatus(testRunResult: Promise, debug?: boolean): void; + displayDiscoverStatus(testDiscovery: Promise, quietMode?: boolean): Promise; +} + +export const ITestDisplay = Symbol('ITestDisplay'); +export interface ITestDisplay { + displayStopTestUI(workspace: Uri, message: string): void; + displayTestUI(cmdSource: CommandSource, wkspace: Uri): void; + selectTestFunction(rootDirectory: string, tests: Tests): Promise; + selectTestFile(rootDirectory: string, tests: Tests): Promise; + displayFunctionTestPickerUI(cmdSource: CommandSource, wkspace: Uri, rootDirectory: string, file: Uri, testFunctions: TestFunction[], debug?: boolean): void; +} + +export const IUnitTestManagementService = Symbol('IUnitTestManagementService'); +export interface IUnitTestManagementService { + activate(): Promise; + activateCodeLenses(symboldProvider: PythonSymbolProvider): Promise; + getTestManager(displayTestNotConfiguredMessage: boolean, resource?: Uri): Promise; + discoverTestsForDocument(doc: TextDocument): Promise; + autoDiscoverTests(): Promise; + discoverTests(cmdSource: CommandSource, resource?: Uri, ignoreCache?: boolean, userInitiated?: boolean, quietMode?: boolean): Promise; + stopTests(resource: Uri): Promise; + displayStopUI(message: string): Promise; + displayUI(cmdSource: CommandSource): Promise; + displayPickerUI(cmdSource: CommandSource, file: Uri, testFunctions: TestFunction[], debug?: boolean): Promise; + runTestsImpl(cmdSource: CommandSource, resource?: Uri, testsToRun?: TestsToRun, runFailedTests?: boolean, debug?: boolean): Promise; + runCurrentTestFile(cmdSource: CommandSource): Promise; + + selectAndRunTestFile(cmdSource: CommandSource): Promise; + + selectAndRunTestMethod(cmdSource: CommandSource, resource: Uri, debug?: boolean): Promise; + + viewOutput(cmdSource: CommandSource): void; +} + +export interface ITestConfigurationManager { + requiresUserToConfigure(wkspace: Uri): Promise; + configure(wkspace: Uri): Promise; + enable(): Promise; + disable(): Promise; +} + +export const ITestConfigurationManagerFactory = Symbol('ITestConfigurationManagerFactory'); +export interface ITestConfigurationManagerFactory { + create(wkspace: Uri, product: Product): ITestConfigurationManager; +} diff --git a/src/client/unittests/unittest/testConfigurationManager.ts b/src/client/unittests/unittest/testConfigurationManager.ts index 6993b0e0f980..54416559ca30 100644 --- a/src/client/unittests/unittest/testConfigurationManager.ts +++ b/src/client/unittests/unittest/testConfigurationManager.ts @@ -1,14 +1,15 @@ -import { OutputChannel, Uri } from 'vscode'; -import { IInstaller, Product } from '../../common/types'; +import { Uri } from 'vscode'; +import { Product } from '../../common/types'; +import { IServiceContainer } from '../../ioc/types'; import { TestConfigurationManager } from '../common/managers/testConfigurationManager'; -import { ITestConfigSettingsService } from '../common/types'; export class ConfigurationManager extends TestConfigurationManager { - constructor(workspace: Uri, outputChannel: OutputChannel, - installer: IInstaller, testConfigSettingsService: ITestConfigSettingsService) { - super(workspace, Product.unittest, outputChannel, installer, testConfigSettingsService); + constructor(workspace: Uri, serviceContainer: IServiceContainer) { + super(workspace, Product.unittest, serviceContainer); + } + public async requiresUserToConfigure(_wkspace: Uri): Promise { + return true; } - // tslint:disable-next-line:no-any public async configure(wkspace: Uri) { const args = ['-v']; const subDirs = await this.getTestDirs(wkspace.fsPath); diff --git a/src/test/common.ts b/src/test/common.ts index 5f6d6640e709..15b600e7d43b 100644 --- a/src/test/common.ts +++ b/src/test/common.ts @@ -2,8 +2,11 @@ import * as fs from 'fs-extra'; import * as path from 'path'; import { ConfigurationTarget, Uri, workspace } from 'vscode'; import { PythonSettings } from '../client/common/configSettings'; +import { sleep } from './core'; import { IS_MULTI_ROOT_TEST } from './initialize'; +export * from './core'; + const fileInNonRootWorkspace = path.join(__dirname, '..', '..', 'src', 'test', 'pythonFiles', 'dummy.py'); export const rootWorkspaceUri = getWorkspaceRoot(); @@ -30,6 +33,7 @@ export async function updateSetting(setting: PythonSettingKeys, value: {} | unde } // tslint:disable-next-line:await-promise await settings.update(setting, value, configTarget); + await sleep(2000); PythonSettings.dispose(); } @@ -111,10 +115,6 @@ export async function deleteFile(file: string) { } } -export async function sleep(milliseconds: number) { - return new Promise(resolve => setTimeout(resolve, milliseconds)); -} - // tslint:disable-next-line:no-non-null-assertion const globalPythonPathSetting = workspace.getConfiguration('python').inspect('pythonPath')!.globalValue; export const clearPythonPathInWorkspaceFolder = async (resource: string | Uri) => retryAsync(setPythonPathInWorkspace)(resource, ConfigurationTarget.WorkspaceFolder); diff --git a/src/test/common/installer.test.ts b/src/test/common/installer.test.ts index ea1fd200b983..1238dcd6f52e 100644 --- a/src/test/common/installer.test.ts +++ b/src/test/common/installer.test.ts @@ -1,7 +1,7 @@ import * as path from 'path'; import * as TypeMoq from 'typemoq'; import { ConfigurationTarget, Uri } from 'vscode'; -import { IApplicationShell, IWorkspaceService } from '../../client/common/application/types'; +import { IApplicationShell, ICommandManager, IWorkspaceService } from '../../client/common/application/types'; import { ConfigurationService } from '../../client/common/configuration/service'; import { EnumEx } from '../../client/common/enumUtils'; import { createDeferred } from '../../client/common/helpers'; @@ -54,6 +54,7 @@ suite('Installer', () => { ioc.serviceManager.addSingleton(IPathUtils, PathUtils); ioc.serviceManager.addSingleton(ICurrentProcess, CurrentProcess); ioc.serviceManager.addSingleton(IInstallationChannelManager, InstallationChannelManager); + ioc.serviceManager.addSingletonInstance(ICommandManager, TypeMoq.Mock.ofType().object); ioc.serviceManager.addSingletonInstance(IApplicationShell, TypeMoq.Mock.ofType().object); ioc.serviceManager.addSingleton(IConfigurationService, ConfigurationService); diff --git a/src/test/core.ts b/src/test/core.ts new file mode 100644 index 000000000000..3e67d5543829 --- /dev/null +++ b/src/test/core.ts @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// File without any dependencies on VS Code. + +export async function sleep(milliseconds: number) { + return new Promise(resolve => setTimeout(resolve, milliseconds)); +} diff --git a/src/test/unittests/common/managers/testConfigurationManager.unit.test.ts b/src/test/unittests/common/managers/testConfigurationManager.unit.test.ts index fac9e17b5235..5c7e7ac54691 100644 --- a/src/test/unittests/common/managers/testConfigurationManager.unit.test.ts +++ b/src/test/unittests/common/managers/testConfigurationManager.unit.test.ts @@ -6,14 +6,18 @@ // tslint:disable:no-any import * as TypeMoq from 'typemoq'; -import { OutputChannel } from 'vscode'; +import { OutputChannel, Uri } from 'vscode'; import { EnumEx } from '../../../../client/common/enumUtils'; -import { IInstaller, Product } from '../../../../client/common/types'; +import { IInstaller, IOutputChannel, Product } from '../../../../client/common/types'; +import { IServiceContainer } from '../../../../client/ioc/types'; +import { TEST_OUTPUT_CHANNEL } from '../../../../client/unittests/common/constants'; import { TestConfigurationManager } from '../../../../client/unittests/common/managers/testConfigurationManager'; import { ITestConfigSettingsService, UnitTestProduct } from '../../../../client/unittests/common/types'; -import { Uri } from '../../../vscode-mock'; class MockTestConfigurationManager extends TestConfigurationManager { + public requiresUserToConfigure(wkspace: Uri): Promise { + throw new Error('Method not implemented.'); + } public configure(wkspace: any): Promise { throw new Error('Method not implemented.'); } @@ -32,9 +36,11 @@ suite('Unit Test Configuration Manager (unit)', () => { configService = TypeMoq.Mock.ofType(); const outputChannel = TypeMoq.Mock.ofType().object; const installer = TypeMoq.Mock.ofType().object; - - manager = new MockTestConfigurationManager(workspaceUri, product as UnitTestProduct, - outputChannel, installer, configService.object); + const serviceContainer = TypeMoq.Mock.ofType(); + serviceContainer.setup(s => s.get(TypeMoq.It.isValue(IOutputChannel), TypeMoq.It.isValue(TEST_OUTPUT_CHANNEL))).returns(() => outputChannel); + serviceContainer.setup(s => s.get(TypeMoq.It.isValue(ITestConfigSettingsService))).returns(() => configService.object); + serviceContainer.setup(s => s.get(TypeMoq.It.isValue(IInstaller))).returns(() => installer); + manager = new MockTestConfigurationManager(workspaceUri, product as UnitTestProduct, serviceContainer.object); }); test('Enabling a test product shoud disable other products', async () => { diff --git a/src/test/unittests/common/services/configSettingService.unit.test.ts b/src/test/unittests/common/services/configSettingService.unit.test.ts new file mode 100644 index 000000000000..722847efce2f --- /dev/null +++ b/src/test/unittests/common/services/configSettingService.unit.test.ts @@ -0,0 +1,196 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:max-func-body-length no-any + +import { expect, use } from 'chai'; +import * as chaiPromise from 'chai-as-promised'; +import * as typeMoq from 'typemoq'; +import { Uri, WorkspaceConfiguration, WorkspaceFolder } from 'vscode'; +import { IWorkspaceService } from '../../../../client/common/application/types'; +import { EnumEx } from '../../../../client/common/enumUtils'; +import { Product } from '../../../../client/common/types'; +import { IServiceContainer } from '../../../../client/ioc/types'; +import { TestConfigSettingsService } from '../../../../client/unittests/common/services/configSettingService'; +import { ITestConfigSettingsService, UnitTestProduct } from '../../../../client/unittests/common/types'; + +use(chaiPromise); + +const updateMethods: (keyof ITestConfigSettingsService)[] = ['updateTestArgs', 'disable', 'enable']; + +suite('Unit Tests - ConfigSettingsService', () => { + [Product.pytest, Product.unittest, Product.nosetest].forEach(prodItem => { + const product = prodItem as any as UnitTestProduct; + const prods = EnumEx.getNamesAndValues(Product); + const productName = prods.filter(item => item.value === product)[0]; + const workspaceUri = Uri.file(__filename); + updateMethods.forEach(updateMethod => { + suite(`Test '${updateMethod}' method with ${productName.name}`, () => { + let testConfigSettingsService: ITestConfigSettingsService; + let workspaceService: typeMoq.IMock; + setup(() => { + const serviceContainer = typeMoq.Mock.ofType(); + workspaceService = typeMoq.Mock.ofType(); + + serviceContainer.setup(c => c.get(typeMoq.It.isValue(IWorkspaceService))).returns(() => workspaceService.object); + testConfigSettingsService = new TestConfigSettingsService(serviceContainer.object); + }); + function getTestArgSetting(prod: UnitTestProduct) { + switch (prod) { + case Product.unittest: + return 'unitTest.unittestArgs'; + case Product.pytest: + return 'unitTest.pyTestArgs'; + case Product.nosetest: + return 'unitTest.nosetestArgs'; + default: + throw new Error('Invalid Test Product'); + } + } + function getTestEnablingSetting(prod: UnitTestProduct) { + switch (prod) { + case Product.unittest: + return 'unitTest.unittestEnabled'; + case Product.pytest: + return 'unitTest.pyTestEnabled'; + case Product.nosetest: + return 'unitTest.nosetestsEnabled'; + default: + throw new Error('Invalid Test Product'); + } + } + function getExpectedValueAndSettings(): { configValue: any; configName: string } { + switch (updateMethod) { + case 'disable': { + return { configValue: false, configName: getTestEnablingSetting(product) }; + } + case 'enable': { + return { configValue: true, configName: getTestEnablingSetting(product) }; + } + case 'updateTestArgs': { + return { configValue: ['one', 'two', 'three'], configName: getTestArgSetting(product) }; + } + default: { + throw new Error('Invalid Method'); + } + } + } + test('Update Test Arguments with workspace Uri without workspaces', async () => { + workspaceService.setup(w => w.hasWorkspaceFolders) + .returns(() => false) + .verifiable(typeMoq.Times.atLeastOnce()); + + const pythonConfig = typeMoq.Mock.ofType(); + workspaceService.setup(w => w.getConfiguration(typeMoq.It.isValue('python'))) + .returns(() => pythonConfig.object) + .verifiable(typeMoq.Times.once()); + + const { configValue, configName } = getExpectedValueAndSettings(); + + pythonConfig.setup(p => p.update(typeMoq.It.isValue(configName), typeMoq.It.isValue(configValue))) + .returns(() => Promise.resolve()) + .verifiable(typeMoq.Times.once()); + + if (updateMethod === 'updateTestArgs') { + await testConfigSettingsService.updateTestArgs(workspaceUri, product, configValue); + } else { + await testConfigSettingsService[updateMethod](workspaceUri, product); + } + workspaceService.verifyAll(); + pythonConfig.verifyAll(); + }); + test('Update Test Arguments with workspace Uri with one workspace', async () => { + workspaceService.setup(w => w.hasWorkspaceFolders) + .returns(() => true) + .verifiable(typeMoq.Times.atLeastOnce()); + + const workspaceFolder = typeMoq.Mock.ofType(); + workspaceFolder.setup(w => w.uri) + .returns(() => workspaceUri) + .verifiable(typeMoq.Times.atLeastOnce()); + workspaceService.setup(w => w.workspaceFolders) + .returns(() => [workspaceFolder.object]) + .verifiable(typeMoq.Times.atLeastOnce()); + + const pythonConfig = typeMoq.Mock.ofType(); + workspaceService.setup(w => w.getConfiguration(typeMoq.It.isValue('python'), typeMoq.It.isValue(workspaceUri))) + .returns(() => pythonConfig.object) + .verifiable(typeMoq.Times.once()); + + const { configValue, configName } = getExpectedValueAndSettings(); + pythonConfig.setup(p => p.update(typeMoq.It.isValue(configName), typeMoq.It.isValue(configValue))) + .returns(() => Promise.resolve()) + .verifiable(typeMoq.Times.once()); + + if (updateMethod === 'updateTestArgs') { + await testConfigSettingsService.updateTestArgs(workspaceUri, product, configValue); + } else { + await testConfigSettingsService[updateMethod](workspaceUri, product); + } + + workspaceService.verifyAll(); + pythonConfig.verifyAll(); + }); + test('Update Test Arguments with workspace Uri with more than one workspace and uri belongs to a workspace', async () => { + workspaceService.setup(w => w.hasWorkspaceFolders) + .returns(() => true) + .verifiable(typeMoq.Times.atLeastOnce()); + + const workspaceFolder = typeMoq.Mock.ofType(); + workspaceFolder.setup(w => w.uri) + .returns(() => workspaceUri) + .verifiable(typeMoq.Times.atLeastOnce()); + workspaceService.setup(w => w.workspaceFolders) + .returns(() => [workspaceFolder.object, workspaceFolder.object]) + .verifiable(typeMoq.Times.atLeastOnce()); + workspaceService.setup(w => w.getWorkspaceFolder(typeMoq.It.isValue(workspaceUri))) + .returns(() => workspaceFolder.object) + .verifiable(typeMoq.Times.once()); + + const pythonConfig = typeMoq.Mock.ofType(); + workspaceService.setup(w => w.getConfiguration(typeMoq.It.isValue('python'), typeMoq.It.isValue(workspaceUri))) + .returns(() => pythonConfig.object) + .verifiable(typeMoq.Times.once()); + + const { configValue, configName } = getExpectedValueAndSettings(); + pythonConfig.setup(p => p.update(typeMoq.It.isValue(configName), typeMoq.It.isValue(configValue))) + .returns(() => Promise.resolve()) + .verifiable(typeMoq.Times.once()); + + if (updateMethod === 'updateTestArgs') { + await testConfigSettingsService.updateTestArgs(workspaceUri, product, configValue); + } else { + await testConfigSettingsService[updateMethod](workspaceUri, product); + } + + workspaceService.verifyAll(); + pythonConfig.verifyAll(); + }); + test('Expect an exception when updating Test Arguments with workspace Uri with more than one workspace and uri does not belong to a workspace', async () => { + workspaceService.setup(w => w.hasWorkspaceFolders) + .returns(() => true) + .verifiable(typeMoq.Times.atLeastOnce()); + + const workspaceFolder = typeMoq.Mock.ofType(); + workspaceFolder.setup(w => w.uri) + .returns(() => workspaceUri) + .verifiable(typeMoq.Times.atLeastOnce()); + workspaceService.setup(w => w.workspaceFolders) + .returns(() => [workspaceFolder.object, workspaceFolder.object]) + .verifiable(typeMoq.Times.atLeastOnce()); + workspaceService.setup(w => w.getWorkspaceFolder(typeMoq.It.isValue(workspaceUri))) + .returns(() => undefined) + .verifiable(typeMoq.Times.once()); + + const { configValue } = getExpectedValueAndSettings(); + + const promise = testConfigSettingsService.updateTestArgs(workspaceUri, product, configValue); + expect(promise).to.eventually.rejectedWith(); + workspaceService.verifyAll(); + }); + }); + }); + }); +}); diff --git a/src/test/unittests/configuration.unit.test.ts b/src/test/unittests/configuration.unit.test.ts new file mode 100644 index 000000000000..d98da27f72dd --- /dev/null +++ b/src/test/unittests/configuration.unit.test.ts @@ -0,0 +1,340 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:max-func-body-length no-any + +import { expect } from 'chai'; +import * as typeMoq from 'typemoq'; +import { OutputChannel, Uri, WorkspaceConfiguration } from 'vscode'; +import { IApplicationShell, IWorkspaceService } from '../../client/common/application/types'; +import { EnumEx } from '../../client/common/enumUtils'; +import { IConfigurationService, IInstaller, IOutputChannel, IPythonSettings, IUnitTestSettings, Product } from '../../client/common/types'; +import { IServiceContainer } from '../../client/ioc/types'; +import { TEST_OUTPUT_CHANNEL } from '../../client/unittests/common/constants'; +import { UnitTestProduct } from '../../client/unittests/common/types'; +import { UnitTestConfigurationService } from '../../client/unittests/configuration'; +import { ITestConfigurationManager, ITestConfigurationManagerFactory } from '../../client/unittests/types'; + +suite('Unit Tests - ConfigurationService', () => { + [Product.pytest, Product.unittest, Product.nosetest].forEach(prodItem => { + const product = prodItem as any as UnitTestProduct; + const prods = EnumEx.getNamesAndValues(Product); + const productName = prods.filter(item => item.value === product)[0]; + const workspaceUri = Uri.file(__filename); + suite(productName.name, () => { + let testConfigService: typeMoq.IMock; + let workspaceService: typeMoq.IMock; + let factory: typeMoq.IMock; + let appShell: typeMoq.IMock; + let unitTestSettings: typeMoq.IMock; + setup(() => { + const serviceContainer = typeMoq.Mock.ofType(); + const configurationService = typeMoq.Mock.ofType(); + appShell = typeMoq.Mock.ofType(); + const outputChannel = typeMoq.Mock.ofType(); + const installer = typeMoq.Mock.ofType(); + workspaceService = typeMoq.Mock.ofType(); + factory = typeMoq.Mock.ofType(); + unitTestSettings = typeMoq.Mock.ofType(); + const pythonSettings = typeMoq.Mock.ofType(); + + pythonSettings.setup(p => p.unitTest).returns(() => unitTestSettings.object); + configurationService.setup(c => c.getSettings(workspaceUri)).returns(() => pythonSettings.object); + + serviceContainer.setup(c => c.get(typeMoq.It.isValue(IOutputChannel), typeMoq.It.isValue(TEST_OUTPUT_CHANNEL))).returns(() => outputChannel.object); + serviceContainer.setup(c => c.get(typeMoq.It.isValue(IInstaller))).returns(() => installer.object); + serviceContainer.setup(c => c.get(typeMoq.It.isValue(IConfigurationService))).returns(() => configurationService.object); + serviceContainer.setup(c => c.get(typeMoq.It.isValue(IApplicationShell))).returns(() => appShell.object); + serviceContainer.setup(c => c.get(typeMoq.It.isValue(IWorkspaceService))).returns(() => workspaceService.object); + serviceContainer.setup(c => c.get(typeMoq.It.isValue(ITestConfigurationManagerFactory))).returns(() => factory.object); + testConfigService = typeMoq.Mock.ofType(UnitTestConfigurationService, typeMoq.MockBehavior.Loose, true, serviceContainer.object); + }); + test('Enable Test when setting unitTest.promptToConfigure is enabled', async () => { + const configMgr = typeMoq.Mock.ofType(); + configMgr.setup(c => c.enable()) + .returns(() => Promise.resolve()) + .verifiable(typeMoq.Times.once()); + + factory.setup(f => f.create(workspaceUri, product)) + .returns(() => configMgr.object) + .verifiable(typeMoq.Times.once()); + + const workspaceConfig = typeMoq.Mock.ofType(); + workspaceService.setup(w => w.getConfiguration(typeMoq.It.isValue('python'), workspaceUri)) + .returns(() => workspaceConfig.object) + .verifiable(typeMoq.Times.once()); + + workspaceConfig.setup(w => w.get(typeMoq.It.isValue('unitTest.promptToConfigure'))) + .returns(() => true) + .verifiable(typeMoq.Times.once()); + + await testConfigService.target.enableTest(workspaceUri, product); + + configMgr.verifyAll(); + factory.verifyAll(); + workspaceService.verifyAll(); + workspaceConfig.verifyAll(); + }); + test('Enable Test when setting unitTest.promptToConfigure is disabled', async () => { + const configMgr = typeMoq.Mock.ofType(); + configMgr.setup(c => c.enable()) + .returns(() => Promise.resolve()) + .verifiable(typeMoq.Times.once()); + + factory.setup(f => f.create(workspaceUri, product)) + .returns(() => configMgr.object) + .verifiable(typeMoq.Times.once()); + + const workspaceConfig = typeMoq.Mock.ofType(); + workspaceService.setup(w => w.getConfiguration(typeMoq.It.isValue('python'), workspaceUri)) + .returns(() => workspaceConfig.object) + .verifiable(typeMoq.Times.once()); + + workspaceConfig.setup(w => w.get(typeMoq.It.isValue('unitTest.promptToConfigure'))) + .returns(() => false) + .verifiable(typeMoq.Times.once()); + + workspaceConfig.setup(w => w.update(typeMoq.It.isValue('unitTest.promptToConfigure'), typeMoq.It.isValue(undefined))) + .returns(() => Promise.resolve()) + .verifiable(typeMoq.Times.once()); + + await testConfigService.target.enableTest(workspaceUri, product); + + configMgr.verifyAll(); + factory.verifyAll(); + workspaceService.verifyAll(); + workspaceConfig.verifyAll(); + }); + test('Enable Test when setting unitTest.promptToConfigure is disabled and fail to update the settings', async () => { + const configMgr = typeMoq.Mock.ofType(); + configMgr.setup(c => c.enable()) + .returns(() => Promise.resolve()) + .verifiable(typeMoq.Times.once()); + + factory.setup(f => f.create(workspaceUri, product)) + .returns(() => configMgr.object) + .verifiable(typeMoq.Times.once()); + + const workspaceConfig = typeMoq.Mock.ofType(); + workspaceService.setup(w => w.getConfiguration(typeMoq.It.isValue('python'), workspaceUri)) + .returns(() => workspaceConfig.object) + .verifiable(typeMoq.Times.once()); + + workspaceConfig.setup(w => w.get(typeMoq.It.isValue('unitTest.promptToConfigure'))) + .returns(() => false) + .verifiable(typeMoq.Times.once()); + + const errorMessage = 'Update Failed'; + const updateFailError = new Error(errorMessage); + workspaceConfig.setup(w => w.update(typeMoq.It.isValue('unitTest.promptToConfigure'), typeMoq.It.isValue(undefined))) + .returns(() => Promise.reject(updateFailError)) + .verifiable(typeMoq.Times.once()); + + const promise = testConfigService.target.enableTest(workspaceUri, product); + + await expect(promise).to.eventually.be.rejectedWith(errorMessage); + configMgr.verifyAll(); + factory.verifyAll(); + workspaceService.verifyAll(); + workspaceConfig.verifyAll(); + }); + test('Select Test runner displays 3 items', async () => { + const placeHolder = 'Some message'; + appShell.setup(s => s.showQuickPick(typeMoq.It.isAny(), typeMoq.It.isObjectWith({ placeHolder }))) + .callback(items => expect(items).be.lengthOf(3)) + .verifiable(typeMoq.Times.once()); + + await testConfigService.target.selectTestRunner(placeHolder); + appShell.verifyAll(); + }); + test('Ensure selected item is returned', async () => { + const placeHolder = 'Some message'; + const indexes = [Product.unittest, Product.pytest, Product.nosetest]; + appShell.setup(s => s.showQuickPick(typeMoq.It.isAny(), typeMoq.It.isObjectWith({ placeHolder }))) + .callback(items => expect(items).be.lengthOf(3)) + .returns((items) => items[indexes.indexOf(product)]) + .verifiable(typeMoq.Times.once()); + + const selectedItem = await testConfigService.target.selectTestRunner(placeHolder); + expect(selectedItem).to.be.equal(product); + appShell.verifyAll(); + }); + test('Ensure undefined is returned when nothing is seleted', async () => { + const placeHolder = 'Some message'; + appShell.setup(s => s.showQuickPick(typeMoq.It.isAny(), typeMoq.It.isObjectWith({ placeHolder }))) + .returns(() => Promise.resolve(undefined)) + .verifiable(typeMoq.Times.once()); + + const selectedItem = await testConfigService.target.selectTestRunner(placeHolder); + expect(selectedItem).to.be.equal(undefined, 'invalid value'); + appShell.verifyAll(); + }); + test('Prompt to enable a test if a test framework is not enabled', async () => { + unitTestSettings.setup(u => u.pyTestEnabled).returns(() => false); + unitTestSettings.setup(u => u.unittestEnabled).returns(() => false); + unitTestSettings.setup(u => u.nosetestsEnabled).returns(() => false); + + appShell.setup(s => s.showInformationMessage(typeMoq.It.isAny(), typeMoq.It.isAny())) + .returns(() => Promise.resolve(undefined)) + .verifiable(typeMoq.Times.once()); + + let exceptionThrown = false; + try { + await testConfigService.target.displayTestFrameworkError(workspaceUri); + } catch { + exceptionThrown = true; + } + + expect(exceptionThrown).to.be.equal(true, 'Exception not thrown'); + appShell.verifyAll(); + }); + test('Prompt to select a test if a test framework is not enabled', async () => { + unitTestSettings.setup(u => u.pyTestEnabled).returns(() => false); + unitTestSettings.setup(u => u.unittestEnabled).returns(() => false); + unitTestSettings.setup(u => u.nosetestsEnabled).returns(() => false); + + appShell.setup(s => s.showInformationMessage(typeMoq.It.isAny(), typeMoq.It.isAny())) + .returns((_msg, option) => Promise.resolve(option)) + .verifiable(typeMoq.Times.once()); + + let exceptionThrown = false; + let selectTestRunnerInvoked = false; + try { + testConfigService.callBase = false; + testConfigService.setup(t => t.selectTestRunner(typeMoq.It.isAny())) + .returns(() => { + selectTestRunnerInvoked = true; + return Promise.resolve(undefined); + }); + await testConfigService.target.displayTestFrameworkError(workspaceUri); + } catch { + exceptionThrown = true; + } + + expect(selectTestRunnerInvoked).to.be.equal(true, 'Method not invoked'); + expect(exceptionThrown).to.be.equal(true, 'Exception not thrown'); + appShell.verifyAll(); + }); + test('Configure selected test framework and disable others', async () => { + unitTestSettings.setup(u => u.pyTestEnabled).returns(() => false); + unitTestSettings.setup(u => u.unittestEnabled).returns(() => false); + unitTestSettings.setup(u => u.nosetestsEnabled).returns(() => false); + + appShell.setup(s => s.showInformationMessage(typeMoq.It.isAny(), typeMoq.It.isAny())) + .returns((_msg, option) => Promise.resolve(option)) + .verifiable(typeMoq.Times.once()); + + let selectTestRunnerInvoked = false; + testConfigService.callBase = false; + testConfigService.setup(t => t.selectTestRunner(typeMoq.It.isAny())) + .returns(() => { + selectTestRunnerInvoked = true; + return Promise.resolve(product as any); + }); + + let enableTestInvoked = false; + testConfigService.setup(t => t.enableTest(typeMoq.It.isValue(workspaceUri), typeMoq.It.isValue(product))) + .returns(() => { + enableTestInvoked = true; + return Promise.resolve(); + }); + + const configMgr = typeMoq.Mock.ofType(); + factory.setup(f => f.create(typeMoq.It.isValue(workspaceUri), typeMoq.It.isValue(product))) + .returns(() => configMgr.object) + .verifiable(typeMoq.Times.once()); + + configMgr.setup(c => c.configure(typeMoq.It.isValue(workspaceUri))) + .returns(() => Promise.resolve()) + .verifiable(typeMoq.Times.once()); + + await testConfigService.target.displayTestFrameworkError(workspaceUri); + + expect(selectTestRunnerInvoked).to.be.equal(true, 'Select Test Runner not invoked'); + expect(enableTestInvoked).to.be.equal(true, 'Enable Test not invoked'); + appShell.verifyAll(); + factory.verifyAll(); + configMgr.verifyAll(); + }); + test('If more than one test framework is enabled, then prompt to select a test framework', async () => { + unitTestSettings.setup(u => u.pyTestEnabled).returns(() => true); + unitTestSettings.setup(u => u.unittestEnabled).returns(() => true); + unitTestSettings.setup(u => u.nosetestsEnabled).returns(() => true); + + appShell.setup(s => s.showInformationMessage(typeMoq.It.isAny(), typeMoq.It.isAny())) + .returns(() => Promise.resolve(undefined)) + .verifiable(typeMoq.Times.never()); + + let exceptionThrown = false; + try { + await testConfigService.target.displayTestFrameworkError(workspaceUri); + } catch { + exceptionThrown = true; + } + + expect(exceptionThrown).to.be.equal(true, 'Exception not thrown'); + appShell.verifyAll(); + }); + test('If more than one test framework is enabled, then prompt to select a test framework and enable test, but do not configure', async () => { + unitTestSettings.setup(u => u.pyTestEnabled).returns(() => true); + unitTestSettings.setup(u => u.unittestEnabled).returns(() => true); + unitTestSettings.setup(u => u.nosetestsEnabled).returns(() => true); + + appShell.setup(s => s.showInformationMessage(typeMoq.It.isAny(), typeMoq.It.isAny())) + .returns((_msg, option) => Promise.resolve(option)) + .verifiable(typeMoq.Times.never()); + + let selectTestRunnerInvoked = false; + testConfigService.callBase = false; + testConfigService.setup(t => t.selectTestRunner(typeMoq.It.isAny())) + .returns(() => { + selectTestRunnerInvoked = true; + return Promise.resolve(product as any); + }); + + let enableTestInvoked = false; + testConfigService.setup(t => t.enableTest(typeMoq.It.isValue(workspaceUri), typeMoq.It.isValue(product))) + .returns(() => { + enableTestInvoked = true; + return Promise.resolve(); + }); + + const configMgr = typeMoq.Mock.ofType(); + factory.setup(f => f.create(typeMoq.It.isValue(workspaceUri), typeMoq.It.isValue(product))) + .returns(() => configMgr.object) + .verifiable(typeMoq.Times.once()); + + configMgr.setup(c => c.configure(typeMoq.It.isValue(workspaceUri))) + .returns(() => Promise.resolve()) + .verifiable(typeMoq.Times.never()); + const configManagersToVerify: typeof configMgr[] = [configMgr]; + + [Product.unittest, Product.pytest, Product.nosetest] + .filter(prod => product !== prod) + .forEach(prod => { + const otherTestConfigMgr = typeMoq.Mock.ofType(); + factory.setup(f => f.create(typeMoq.It.isValue(workspaceUri), typeMoq.It.isValue(prod))) + .returns(() => otherTestConfigMgr.object) + .verifiable(typeMoq.Times.once()); + otherTestConfigMgr.setup(c => c.disable()) + .returns(() => Promise.resolve()) + .verifiable(typeMoq.Times.once()); + + configManagersToVerify.push(otherTestConfigMgr); + }); + + await testConfigService.target.displayTestFrameworkError(workspaceUri); + + expect(selectTestRunnerInvoked).to.be.equal(true, 'Select Test Runner not invoked'); + expect(enableTestInvoked).to.be.equal(false, 'Enable Test is invoked'); + factory.verifyAll(); + appShell.verifyAll(); + for (const item of configManagersToVerify) { + item.verifyAll(); + } + }); + }); + }); +}); diff --git a/src/test/unittests/configurationFactory.unit.test.ts b/src/test/unittests/configurationFactory.unit.test.ts new file mode 100644 index 000000000000..23e316f4ef55 --- /dev/null +++ b/src/test/unittests/configurationFactory.unit.test.ts @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { expect, use } from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import * as typeMoq from 'typemoq'; +import { OutputChannel, Uri } from 'vscode'; +import { IInstaller, IOutputChannel, Product } from '../../client/common/types'; +import { IServiceContainer } from '../../client/ioc/types'; +import { TEST_OUTPUT_CHANNEL } from '../../client/unittests/common/constants'; +import { ITestConfigSettingsService } from '../../client/unittests/common/types'; +import { TestConfigurationManagerFactory } from '../../client/unittests/configurationFactory'; +import * as nose from '../../client/unittests/nosetest/testConfigurationManager'; +import * as pytest from '../../client/unittests/pytest/testConfigurationManager'; +import { ITestConfigurationManagerFactory } from '../../client/unittests/types'; +import * as unittest from '../../client/unittests/unittest/testConfigurationManager'; + +use(chaiAsPromised); + +suite('Unit Tests - ConfigurationManagerFactory', () => { + let factory: ITestConfigurationManagerFactory; + setup(() => { + const serviceContainer = typeMoq.Mock.ofType(); + const outputChannel = typeMoq.Mock.ofType(); + const installer = typeMoq.Mock.ofType(); + const testConfigService = typeMoq.Mock.ofType(); + + serviceContainer.setup(c => c.get(typeMoq.It.isValue(IOutputChannel), typeMoq.It.isValue(TEST_OUTPUT_CHANNEL))).returns(() => outputChannel.object); + serviceContainer.setup(c => c.get(typeMoq.It.isValue(IInstaller))).returns(() => installer.object); + serviceContainer.setup(c => c.get(typeMoq.It.isValue(ITestConfigSettingsService))).returns(() => testConfigService.object); + factory = new TestConfigurationManagerFactory(serviceContainer.object); + }); + test('Create Unit Test Configuration', async () => { + const configMgr = factory.create(Uri.file(__filename), Product.unittest); + expect(configMgr).to.be.instanceOf(unittest.ConfigurationManager); + }); + test('Create pytest Configuration', async () => { + const configMgr = factory.create(Uri.file(__filename), Product.pytest); + expect(configMgr).to.be.instanceOf(pytest.ConfigurationManager); + }); + test('Create nose Configuration', async () => { + const configMgr = factory.create(Uri.file(__filename), Product.nosetest); + expect(configMgr).to.be.instanceOf(nose.ConfigurationManager); + }); +}); diff --git a/src/test/unittests/display/main.test.ts b/src/test/unittests/display/main.test.ts new file mode 100644 index 000000000000..67de45bdaaf6 --- /dev/null +++ b/src/test/unittests/display/main.test.ts @@ -0,0 +1,366 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:max-func-body-length no-any + +import { expect } from 'chai'; +import * as typeMoq from 'typemoq'; +import { StatusBarItem, Uri } from 'vscode'; +import { IApplicationShell } from '../../../client/common/application/types'; +import { Commands } from '../../../client/common/constants'; +import { noop } from '../../../client/common/core.utils'; +import { createDeferred } from '../../../client/common/helpers'; +import { IConfigurationService, IPythonSettings, IUnitTestSettings } from '../../../client/common/types'; +import { IServiceContainer } from '../../../client/ioc/types'; +import { CANCELLATION_REASON } from '../../../client/unittests/common/constants'; +import { ITestsHelper, Tests } from '../../../client/unittests/common/types'; +import { TestResultDisplay } from '../../../client/unittests/display/main'; +import { sleep } from '../../core'; + +suite('Unit Tests - TestResultDisplay', () => { + const workspaceUri = Uri.file(__filename); + let appShell: typeMoq.IMock; + let unitTestSettings: typeMoq.IMock; + let serviceContainer: typeMoq.IMock; + let display: TestResultDisplay; + let testsHelper: typeMoq.IMock; + let configurationService: typeMoq.IMock; + setup(() => { + serviceContainer = typeMoq.Mock.ofType(); + configurationService = typeMoq.Mock.ofType(); + appShell = typeMoq.Mock.ofType(); + unitTestSettings = typeMoq.Mock.ofType(); + const pythonSettings = typeMoq.Mock.ofType(); + testsHelper = typeMoq.Mock.ofType(); + + pythonSettings.setup(p => p.unitTest).returns(() => unitTestSettings.object); + configurationService.setup(c => c.getSettings(workspaceUri)).returns(() => pythonSettings.object); + + serviceContainer.setup(c => c.get(typeMoq.It.isValue(IConfigurationService))).returns(() => configurationService.object); + serviceContainer.setup(c => c.get(typeMoq.It.isValue(IApplicationShell))).returns(() => appShell.object); + serviceContainer.setup(c => c.get(typeMoq.It.isValue(ITestsHelper))).returns(() => testsHelper.object); + }); + teardown(() => { + try { + display.dispose(); + } catch { noop(); } + }); + function createTestResultDisplay() { + display = new TestResultDisplay(serviceContainer.object); + } + test('Should create a status bar item upon instantiation', async () => { + const statusBar = typeMoq.Mock.ofType(); + appShell.setup(a => a.createStatusBarItem(typeMoq.It.isAny())) + .returns(() => statusBar.object) + .verifiable(typeMoq.Times.once()); + + createTestResultDisplay(); + appShell.verifyAll(); + }); + test('Should be disabled upon instantiation', async () => { + const statusBar = typeMoq.Mock.ofType(); + appShell.setup(a => a.createStatusBarItem(typeMoq.It.isAny())) + .returns(() => statusBar.object) + .verifiable(typeMoq.Times.once()); + + createTestResultDisplay(); + appShell.verifyAll(); + expect(display.enabled).to.be.equal(false, 'not disabled'); + }); + test('Enable display should show the statusbar', async () => { + const statusBar = typeMoq.Mock.ofType(); + appShell.setup(a => a.createStatusBarItem(typeMoq.It.isAny())) + .returns(() => statusBar.object) + .verifiable(typeMoq.Times.once()); + + statusBar.setup(s => s.show()).verifiable(typeMoq.Times.once()); + + createTestResultDisplay(); + display.enabled = true; + statusBar.verifyAll(); + }); + test('Disable display should hide the statusbar', async () => { + const statusBar = typeMoq.Mock.ofType(); + appShell.setup(a => a.createStatusBarItem(typeMoq.It.isAny())) + .returns(() => statusBar.object) + .verifiable(typeMoq.Times.once()); + + statusBar.setup(s => s.hide()).verifiable(typeMoq.Times.once()); + + createTestResultDisplay(); + display.enabled = false; + statusBar.verifyAll(); + }); + test('Ensure status bar is displayed and updated with progress with ability to stop tests', async () => { + const statusBar = typeMoq.Mock.ofType(); + appShell.setup(a => a.createStatusBarItem(typeMoq.It.isAny())) + .returns(() => statusBar.object) + .verifiable(typeMoq.Times.once()); + + statusBar.setup(s => s.show()).verifiable(typeMoq.Times.once()); + + createTestResultDisplay(); + display.displayProgressStatus(createDeferred().promise, false); + + statusBar.verifyAll(); + statusBar.verify(s => s.command = typeMoq.It.isValue(Commands.Tests_Ask_To_Stop_Test), typeMoq.Times.atLeastOnce()); + statusBar.verify(s => s.text = typeMoq.It.isValue('$(stop) Running Tests'), typeMoq.Times.atLeastOnce()); + }); + test('Ensure status bar is updated with success with ability to view ui without any results', async () => { + const statusBar = typeMoq.Mock.ofType(); + appShell.setup(a => a.createStatusBarItem(typeMoq.It.isAny())) + .returns(() => statusBar.object) + .verifiable(typeMoq.Times.once()); + + statusBar.setup(s => s.show()).verifiable(typeMoq.Times.once()); + + createTestResultDisplay(); + const def = createDeferred(); + + display.displayProgressStatus(def.promise, false); + + statusBar.verifyAll(); + statusBar.verify(s => s.command = typeMoq.It.isValue(Commands.Tests_Ask_To_Stop_Test), typeMoq.Times.atLeastOnce()); + statusBar.verify(s => s.text = typeMoq.It.isValue('$(stop) Running Tests'), typeMoq.Times.atLeastOnce()); + + const tests = typeMoq.Mock.ofType(); + tests.setup((t: any) => t.then).returns(() => undefined); + tests.setup(t => t.summary).returns(() => { + return { errors: 0, failures: 0, passed: 0, skipped: 0 }; + }).verifiable(typeMoq.Times.atLeastOnce()); + + appShell.setup(a => a.showWarningMessage(typeMoq.It.isAny(), typeMoq.It.isAny(), typeMoq.It.isAny(), typeMoq.It.isAny())) + .returns(() => Promise.resolve(undefined)) + .verifiable(typeMoq.Times.once()); + + def.resolve(tests.object); + await sleep(1); + + tests.verifyAll(); + appShell.verifyAll(); + statusBar.verify(s => s.command = typeMoq.It.isValue(Commands.Tests_View_UI), typeMoq.Times.atLeastOnce()); + }); + test('Ensure status bar is updated with success with ability to view ui with results', async () => { + const statusBar = typeMoq.Mock.ofType(); + appShell.setup(a => a.createStatusBarItem(typeMoq.It.isAny())) + .returns(() => statusBar.object) + .verifiable(typeMoq.Times.once()); + + statusBar.setup(s => s.show()).verifiable(typeMoq.Times.once()); + + createTestResultDisplay(); + const def = createDeferred(); + + display.displayProgressStatus(def.promise, false); + + statusBar.verifyAll(); + statusBar.verify(s => s.command = typeMoq.It.isValue(Commands.Tests_Ask_To_Stop_Test), typeMoq.Times.atLeastOnce()); + statusBar.verify(s => s.text = typeMoq.It.isValue('$(stop) Running Tests'), typeMoq.Times.atLeastOnce()); + + const tests = typeMoq.Mock.ofType(); + tests.setup((t: any) => t.then).returns(() => undefined); + tests.setup(t => t.summary).returns(() => { + return { errors: 0, failures: 0, passed: 1, skipped: 0 }; + }).verifiable(typeMoq.Times.atLeastOnce()); + + appShell.setup(a => a.showWarningMessage(typeMoq.It.isAny(), typeMoq.It.isAny(), typeMoq.It.isAny(), typeMoq.It.isAny())) + .returns(() => Promise.resolve(undefined)) + .verifiable(typeMoq.Times.never()); + + def.resolve(tests.object); + await sleep(1); + + tests.verifyAll(); + appShell.verifyAll(); + statusBar.verify(s => s.command = typeMoq.It.isValue(Commands.Tests_View_UI), typeMoq.Times.atLeastOnce()); + }); + test('Ensure status bar is updated with error when cancelled by user with ability to view ui with results', async () => { + const statusBar = typeMoq.Mock.ofType(); + appShell.setup(a => a.createStatusBarItem(typeMoq.It.isAny())) + .returns(() => statusBar.object) + .verifiable(typeMoq.Times.once()); + + statusBar.setup(s => s.show()).verifiable(typeMoq.Times.once()); + + createTestResultDisplay(); + const def = createDeferred(); + + display.displayProgressStatus(def.promise, false); + + statusBar.verifyAll(); + statusBar.verify(s => s.command = typeMoq.It.isValue(Commands.Tests_Ask_To_Stop_Test), typeMoq.Times.atLeastOnce()); + statusBar.verify(s => s.text = typeMoq.It.isValue('$(stop) Running Tests'), typeMoq.Times.atLeastOnce()); + + testsHelper.setup(t => t.displayTestErrorMessage(typeMoq.It.isAny())).verifiable(typeMoq.Times.never()); + + def.reject(CANCELLATION_REASON); + await sleep(1); + + appShell.verifyAll(); + statusBar.verify(s => s.command = typeMoq.It.isValue(Commands.Tests_View_UI), typeMoq.Times.atLeastOnce()); + testsHelper.verifyAll(); + }); + test('Ensure status bar is updated, and error message display with error in running tests, with ability to view ui with results', async () => { + const statusBar = typeMoq.Mock.ofType(); + appShell.setup(a => a.createStatusBarItem(typeMoq.It.isAny())) + .returns(() => statusBar.object) + .verifiable(typeMoq.Times.once()); + + statusBar.setup(s => s.show()).verifiable(typeMoq.Times.once()); + + createTestResultDisplay(); + const def = createDeferred(); + + display.displayProgressStatus(def.promise, false); + + statusBar.verifyAll(); + statusBar.verify(s => s.command = typeMoq.It.isValue(Commands.Tests_Ask_To_Stop_Test), typeMoq.Times.atLeastOnce()); + statusBar.verify(s => s.text = typeMoq.It.isValue('$(stop) Running Tests'), typeMoq.Times.atLeastOnce()); + + testsHelper.setup(t => t.displayTestErrorMessage(typeMoq.It.isAny())).verifiable(typeMoq.Times.once()); + + def.reject('Some other reason'); + await sleep(1); + + appShell.verifyAll(); + statusBar.verify(s => s.command = typeMoq.It.isValue(Commands.Tests_View_UI), typeMoq.Times.atLeastOnce()); + testsHelper.verifyAll(); + }); + + test('Ensure status bar is displayed and updated with progress with ability to stop test discovery', async () => { + const statusBar = typeMoq.Mock.ofType(); + appShell.setup(a => a.createStatusBarItem(typeMoq.It.isAny())) + .returns(() => statusBar.object) + .verifiable(typeMoq.Times.once()); + + statusBar.setup(s => s.show()).verifiable(typeMoq.Times.once()); + + createTestResultDisplay(); + display.displayDiscoverStatus(createDeferred().promise, false).ignoreErrors(); + + statusBar.verifyAll(); + statusBar.verify(s => s.command = typeMoq.It.isValue(Commands.Tests_Ask_To_Stop_Discovery), typeMoq.Times.atLeastOnce()); + statusBar.verify(s => s.text = typeMoq.It.isValue('$(stop) Discovering Tests'), typeMoq.Times.atLeastOnce()); + }); + test('Ensure status bar is displayed and updated with success and no tests, with ability to view ui to view results of test discovery', async () => { + const statusBar = typeMoq.Mock.ofType(); + appShell.setup(a => a.createStatusBarItem(typeMoq.It.isAny())) + .returns(() => statusBar.object) + .verifiable(typeMoq.Times.once()); + + statusBar.setup(s => s.show()).verifiable(typeMoq.Times.once()); + + createTestResultDisplay(); + const def = createDeferred(); + + display.displayDiscoverStatus(def.promise, false).ignoreErrors(); + + statusBar.verifyAll(); + statusBar.verify(s => s.command = typeMoq.It.isValue(Commands.Tests_Ask_To_Stop_Discovery), typeMoq.Times.atLeastOnce()); + statusBar.verify(s => s.text = typeMoq.It.isValue('$(stop) Discovering Tests'), typeMoq.Times.atLeastOnce()); + + const tests = typeMoq.Mock.ofType(); + appShell.setup(a => a.showInformationMessage(typeMoq.It.isAny(), typeMoq.It.isAny(), typeMoq.It.isAny(), typeMoq.It.isAny())) + .returns(() => Promise.resolve(undefined)) + .verifiable(typeMoq.Times.once()); + + def.resolve(undefined as any); + await sleep(1); + + tests.verifyAll(); + appShell.verifyAll(); + statusBar.verify(s => s.command = typeMoq.It.isValue(Commands.Tests_View_UI), typeMoq.Times.atLeastOnce()); + }); + test('Ensure tests are disabled when there are errors and user choses to disable tests', async () => { + const statusBar = typeMoq.Mock.ofType(); + appShell.setup(a => a.createStatusBarItem(typeMoq.It.isAny())) + .returns(() => statusBar.object) + .verifiable(typeMoq.Times.once()); + + statusBar.setup(s => s.show()).verifiable(typeMoq.Times.once()); + + createTestResultDisplay(); + const def = createDeferred(); + + display.displayDiscoverStatus(def.promise, false).ignoreErrors(); + + statusBar.verifyAll(); + statusBar.verify(s => s.command = typeMoq.It.isValue(Commands.Tests_Ask_To_Stop_Discovery), typeMoq.Times.atLeastOnce()); + statusBar.verify(s => s.text = typeMoq.It.isValue('$(stop) Discovering Tests'), typeMoq.Times.atLeastOnce()); + + const tests = typeMoq.Mock.ofType(); + appShell.setup(a => a.showInformationMessage(typeMoq.It.isAny(), typeMoq.It.isAny(), typeMoq.It.isAny(), typeMoq.It.isAny())) + .returns((msg, item) => Promise.resolve(item)) + .verifiable(typeMoq.Times.once()); + + for (const setting of ['unitTest.promptToConfigure', 'unitTest.pyTestEnabled', + 'unitTest.unittestEnabled', 'unitTest.nosetestsEnabled']) { + configurationService.setup(c => c.updateSettingAsync(typeMoq.It.isValue(setting), typeMoq.It.isValue(false))) + .returns(() => Promise.resolve()) + .verifiable(typeMoq.Times.once()); + } + def.resolve(undefined as any); + await sleep(1); + + tests.verifyAll(); + appShell.verifyAll(); + statusBar.verify(s => s.command = typeMoq.It.isValue(Commands.Tests_View_UI), typeMoq.Times.atLeastOnce()); + configurationService.verifyAll(); + }); + test('Ensure status bar is displayed and updated with error info when test discovery is cancelled by the user', async () => { + const statusBar = typeMoq.Mock.ofType(); + appShell.setup(a => a.createStatusBarItem(typeMoq.It.isAny())) + .returns(() => statusBar.object) + .verifiable(typeMoq.Times.once()); + + statusBar.setup(s => s.show()).verifiable(typeMoq.Times.once()); + + createTestResultDisplay(); + const def = createDeferred(); + + display.displayDiscoverStatus(def.promise, false).ignoreErrors(); + + statusBar.verifyAll(); + statusBar.verify(s => s.command = typeMoq.It.isValue(Commands.Tests_Ask_To_Stop_Discovery), typeMoq.Times.atLeastOnce()); + statusBar.verify(s => s.text = typeMoq.It.isValue('$(stop) Discovering Tests'), typeMoq.Times.atLeastOnce()); + + appShell.setup(a => a.showErrorMessage(typeMoq.It.isAny())) + .verifiable(typeMoq.Times.never()); + + def.reject(CANCELLATION_REASON); + await sleep(1); + + appShell.verifyAll(); + statusBar.verify(s => s.command = typeMoq.It.isValue(Commands.Tests_Discover), typeMoq.Times.atLeastOnce()); + configurationService.verifyAll(); + }); + test('Ensure status bar is displayed and updated with error info, and message is displayed when test discovery is fails due to errors', async () => { + const statusBar = typeMoq.Mock.ofType(); + appShell.setup(a => a.createStatusBarItem(typeMoq.It.isAny())) + .returns(() => statusBar.object) + .verifiable(typeMoq.Times.once()); + + statusBar.setup(s => s.show()).verifiable(typeMoq.Times.once()); + + createTestResultDisplay(); + const def = createDeferred(); + + display.displayDiscoverStatus(def.promise, false).ignoreErrors(); + + statusBar.verifyAll(); + statusBar.verify(s => s.command = typeMoq.It.isValue(Commands.Tests_Ask_To_Stop_Discovery), typeMoq.Times.atLeastOnce()); + statusBar.verify(s => s.text = typeMoq.It.isValue('$(stop) Discovering Tests'), typeMoq.Times.atLeastOnce()); + + appShell.setup(a => a.showErrorMessage(typeMoq.It.isAny())) + .verifiable(typeMoq.Times.once()); + + def.reject('some weird error'); + await sleep(1); + + appShell.verifyAll(); + statusBar.verify(s => s.command = typeMoq.It.isValue(Commands.Tests_Discover), typeMoq.Times.atLeastOnce()); + configurationService.verifyAll(); + }); +}); diff --git a/src/test/vscode-mock.ts b/src/test/vscode-mock.ts index 0e150c2aef1b..2c3524f63311 100644 --- a/src/test/vscode-mock.ts +++ b/src/test/vscode-mock.ts @@ -15,15 +15,6 @@ const mockedVSCode: Partial = {}; const mockedVSCodeNamespaces: { [P in keyof VSCode]?: TypeMoq.IMock } = {}; const originalLoad = Module._load; -generateMock('workspace'); -generateMock('window'); -generateMock('commands'); -generateMock('languages'); -generateMock('env'); -generateMock('debug'); -generateMock('extensions'); -generateMock('scm'); - function generateMock(name: K): void { const mockedObj = TypeMoq.Mock.ofType(); mockedVSCode[name] = mockedObj.object; @@ -31,6 +22,15 @@ function generateMock(name: K): void { } export function initialize() { + generateMock('workspace'); + generateMock('window'); + generateMock('commands'); + generateMock('languages'); + generateMock('env'); + generateMock('debug'); + generateMock('extensions'); + generateMock('scm'); + Module._load = function (request, parent) { if (request === 'vscode') { return mockedVSCode; @@ -76,9 +76,14 @@ export class Uri implements vscode.Uri { throw new Error('Not implemented'); } public toString(skipEncoding?: boolean): string { - throw new Error('Not implemented'); + return this.fsPath; } public toJSON(): any { return this.fsPath; } } + +mockedVSCode.Uri = Uri as any; +// tslint:disable-next-line:no-function-expression +mockedVSCode.EventEmitter = function () { return TypeMoq.Mock.ofType>(); } as any; +mockedVSCode.StatusBarAlignment = TypeMoq.Mock.ofType().object as any; From 4dfa0edc58f11a1a9df349b369f8fc4ad18be3d6 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 22 May 2018 14:49:13 -0700 Subject: [PATCH 257/433] Clarify how to test `python.autoComplete.preloadModules` --- .github/test_plan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/test_plan.md b/.github/test_plan.md index 725b7b3b05e6..8177f1da6b9c 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -143,7 +143,7 @@ foo = 42 # Marked as a blacklisted name. Please also test for general accuracy on the most "interesting" code you can find. - [ ] `"python.autoComplete.extraPaths"` works -- [ ] `"python.autoComplete.preloadModules"` works +- [ ] `"python.autoComplete.preloadModules"` works (e.g. listing `numpy` should visibly speed up completing on the module's contents) - [ ] `"python.autocomplete.addBrackets": true` causes auto-completion of functions to append `()` #### [Formatting](https://code.visualstudio.com/docs/python/editing#_formatting) From 209ec85d92e9f370279d98fa3b31540108473e38 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Wed, 23 May 2018 00:27:19 +0100 Subject: [PATCH 258/433] Fix typo in comment (#1711) --- src/test/testRunner.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/testRunner.ts b/src/test/testRunner.ts index 30f64f619d8e..7699cc9cc8fb 100644 --- a/src/test/testRunner.ts +++ b/src/test/testRunner.ts @@ -38,7 +38,7 @@ type Instrumenter = istanbul.Instrumenter & { coverState: CoverState }; type TestCallback = (error?: Error, failures?: number) => void; // Linux: prevent a weird NPE when mocha on Linux requires the window size from the TTY. -// Since we are not running in a tty environment, we just implementt he method statically. +// Since we are not running in a tty environment, we just implement the method statically. const tty = require('tty'); if (!tty.getWindowSize) { tty.getWindowSize = function (): number[] { return [80, 75]; }; From bb5b18e4771026b680b873974cc8539122a10eeb Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Wed, 23 May 2018 14:06:54 -0700 Subject: [PATCH 259/433] Add black --- .github/test_plan.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/test_plan.md b/.github/test_plan.md index 8177f1da6b9c..5bea2fcd552b 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -155,8 +155,10 @@ def foo():pass - [ ] Prompted to install a formatter if none installed and `Format Document` is run - [ ] Installing `autopep8` works + - [ ] Installing `black` works - [ ] Installing `yapf` works - [ ] autopep8 works +- [ ] black works - [ ] yapf works - [ ] `"editor.formatOnType": true` works and has expected results From f0c618d1ed8d5a2d14a53ac9e4d86504b9d97c20 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Wed, 23 May 2018 16:24:31 -0700 Subject: [PATCH 260/433] Fix bug, remove unnecessary flag, fix hash comparison on Linux (#1734) * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * #1096 The if statement is automatically formatted incorrectly * Merge fix * Add more tests * More tests * Typo * Test * Also better handle multiline arguments * Add a couple missing periods [skip ci] * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * Merge fix * #1257 On type formatting errors for args and kwargs * Handle f-strings * Stop importing from test code * #1308 Single line statements leading to an indentation on the next line * #726 editing python after inline if statement invalid indent * Undo change * Move constant * Harden LS startup error checks * #1364 Intellisense doesn't work after specific const string * Telemetry for the analysis enging * PR feedback * Fix typo * Test baseline update * Jedi 0.12 * Priority to goto_defition * News * Replace unzip * Linux flavors + test * Grammar check * Grammar test * Test baselines * Add news * Pin dependency [skip ci] * Specify markdown as preferable format * Improve function argument detection * Specify markdown * Pythia setting * Baseline updates * Baseline update * Improve startup * Handle missing interpreter better * Handle interpreter change * Delete old file * Fix LS startup time reporting * Remove Async suffix from IFileSystem * Remove Pythia * Remove pre-packaged MSIL * Exe name on Unix * Plain linux * Fix casing * Fix message * Update PTVS engine activation steps * Type formatter eats space in from . * fIX CASING * Remove flag --- src/client/activation/analysis.ts | 1 - src/client/activation/downloader.ts | 24 ------------------- src/client/activation/hashVerifier.ts | 4 ++-- src/client/common/configSettings.ts | 3 --- src/client/common/types.ts | 1 - src/client/formatters/lineFormatter.ts | 10 ++++++++ .../format/extension.lineFormatter.test.ts | 9 +++++++ 7 files changed, 21 insertions(+), 31 deletions(-) diff --git a/src/client/activation/analysis.ts b/src/client/activation/analysis.ts index 8072e99d32af..bf8fd4c9e587 100644 --- a/src/client/activation/analysis.ts +++ b/src/client/activation/analysis.ts @@ -240,7 +240,6 @@ export class AnalysisExtensionActivator implements IExtensionActivator { maxDocumentationTextLength: 0 }, asyncStartup: true, - intelliCodeEnabled: settings.intelliCodeEnabled, testEnvironment: isTestExecution() } }; diff --git a/src/client/activation/downloader.ts b/src/client/activation/downloader.ts index f6d7036ad12a..f28075571579 100644 --- a/src/client/activation/downloader.ts +++ b/src/client/activation/downloader.ts @@ -21,7 +21,6 @@ const downloadUriPrefix = 'https://pvsc.blob.core.windows.net/python-analysis'; const downloadBaseFileName = 'Python-Analysis-VSCode'; const downloadVersion = '0.1.0'; const downloadFileExtension = '.nupkg'; -const modelName = 'model-sequence.json.gz'; export class AnalysisEngineDownloader { private readonly output: OutputChannel; @@ -56,29 +55,6 @@ export class AnalysisEngineDownloader { } } - public async downloadIntelliCodeModel(context: ExtensionContext): Promise { - const modelFolder = path.join(context.extensionPath, 'analysis', 'Pythia', 'model'); - const localPath = path.join(modelFolder, modelName); - if (await this.fs.fileExists(localPath)) { - return; - } - - let localTempFilePath = ''; - try { - localTempFilePath = await this.downloadFile(downloadUriPrefix, modelName, 'Downloading IntelliCode Model File... '); - await this.fs.createDirectory(modelFolder); - await this.fs.copyFile(localTempFilePath, localPath); - } catch (err) { - this.output.appendLine('failed.'); - this.output.appendLine(err); - throw new Error(err); - } finally { - if (localTempFilePath.length > 0) { - await this.fs.deleteFile(localTempFilePath); - } - } - } - private async downloadFile(location: string, fileName: string, title: string): Promise { const uri = `${location}/${fileName}`; this.output.append(`Downloading ${uri}... `); diff --git a/src/client/activation/hashVerifier.ts b/src/client/activation/hashVerifier.ts index c62cb36484f7..61d1177966f9 100644 --- a/src/client/activation/hashVerifier.ts +++ b/src/client/activation/hashVerifier.ts @@ -22,7 +22,7 @@ export class HashVerifier { readStream.pipe(hash); await deferred.promise; - const actual = hash.read(); - return expectedDigest === platformString ? true : actual === expectedDigest; + const actual = hash.read() as string; + return expectedDigest === platformString ? true : actual.toLowerCase() === expectedDigest.toLowerCase(); } } diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index 63408c7c5638..033a33096cab 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -25,7 +25,6 @@ export const IS_WINDOWS = /^win/.test(process.platform); // tslint:disable-next-line:completed-docs export class PythonSettings extends EventEmitter implements IPythonSettings { private static pythonSettings: Map = new Map(); - public intelliCodeEnabled = true; public downloadCodeAnalysis = true; public jediEnabled = true; public jediPath = ''; @@ -127,8 +126,6 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { this.jediPath = ''; } this.jediMemoryLimit = pythonSettings.get('jediMemoryLimit')!; - } else { - this.intelliCodeEnabled = systemVariables.resolveAny(pythonSettings.get('intelliCodeEnabled', true))!; } // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 05a03ee04cf7..547ccc8f6401 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -100,7 +100,6 @@ export interface IPythonSettings { readonly pythonPath: string; readonly venvPath: string; readonly venvFolders: string[]; - readonly intelliCodeEnabled: boolean; readonly downloadCodeAnalysis: boolean; readonly jediEnabled: boolean; readonly jediPath: string; diff --git a/src/client/formatters/lineFormatter.ts b/src/client/formatters/lineFormatter.ts index 28a71f6ff08d..9bd256f50177 100644 --- a/src/client/formatters/lineFormatter.ts +++ b/src/client/formatters/lineFormatter.ts @@ -98,6 +98,8 @@ export class LineFormatter { private handleOperator(index: number): void { const t = this.tokens.getItemAt(index); const prev = index > 0 ? this.tokens.getItemAt(index - 1) : undefined; + const next = index < this.tokens.count - 1 ? this.tokens.getItemAt(index + 1) : undefined; + if (t.length === 1) { const opCode = this.text.charCodeAt(t.start); switch (opCode) { @@ -107,6 +109,14 @@ export class LineFormatter { } break; case Char.Period: + if (prev && this.isKeyword(prev, 'from')) { + this.builder.softAppendSpace(); + } + this.builder.append(this.text[t.start]); + if (next && this.isKeyword(next, 'import')) { + this.builder.softAppendSpace(); + } + return; case Char.At: case Char.ExclamationMark: this.builder.append(this.text[t.start]); diff --git a/src/test/format/extension.lineFormatter.test.ts b/src/test/format/extension.lineFormatter.test.ts index 46ca5e46f816..656f22c3ffd6 100644 --- a/src/test/format/extension.lineFormatter.test.ts +++ b/src/test/format/extension.lineFormatter.test.ts @@ -112,6 +112,15 @@ suite('Formatting - line formatter', () => { test('Function returning tuple', () => { testFormatLine('x,y=f(a)', 'x, y = f(a)'); }); + test('from. import A', () => { + testFormatLine('from. import A', 'from . import A'); + }); + test('from .. import', () => { + testFormatLine('from ..import', 'from .. import'); + }); + test('from..x import', () => { + testFormatLine('from..x import', 'from ..x import'); + }); test('Grammar file', () => { const content = fs.readFileSync(grammarFile).toString('utf8'); const lines = content.splitLines({ trim: false, removeEmptyEntries: false }); From 393f1972eec780a65b29ed8e1bdbe85ef5cd0518 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 24 May 2018 15:26:57 -0700 Subject: [PATCH 261/433] Ensure debugged program is terminated when `Stop` debugging button is clicked --- news/2 Fixes/1345.md | 1 + news/2 Fixes/{980 => 980.md} | 0 2 files changed, 1 insertion(+) create mode 100644 news/2 Fixes/1345.md rename news/2 Fixes/{980 => 980.md} (100%) diff --git a/news/2 Fixes/1345.md b/news/2 Fixes/1345.md new file mode 100644 index 000000000000..eff8ca0a2b33 --- /dev/null +++ b/news/2 Fixes/1345.md @@ -0,0 +1 @@ +Ensure debugged program is terminated when `Stop` debugging button is clicked. diff --git a/news/2 Fixes/980 b/news/2 Fixes/980.md similarity index 100% rename from news/2 Fixes/980 rename to news/2 Fixes/980.md From 8cb13aa870881411c9f2aa515de9e50944381218 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 24 May 2018 17:33:01 -0700 Subject: [PATCH 262/433] Upload extension to the Azure blob even if tests fail in master branch (#1749) * Upload extension to the Azure blob even if tests fail in master branch * Change file name for upload * Change upload script --- .travis.yml | 5 ++--- news/3 Code Health/1730.md | 1 + news/3 Code Health/1732.md | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 news/3 Code Health/1730.md create mode 100644 news/3 Code Health/1732.md diff --git a/.travis.yml b/.travis.yml index 50471d9ad1d3..fe6190e5977d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -115,9 +115,8 @@ script: python3 -m pip install --upgrade -r news/requirements.txt; python3 news/announce.py --dry-run; fi -after_success: - - if [ $AZURE_STORAGE_ACCOUNT ]; then + - if [[ $AZURE_STORAGE_ACCOUNT && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_PULL_REQUEST" == "false" ]]; then yarn run clean; vsce package; - azure storage blob upload --container $AZURE_STORAGE_CONTAINER --blob ms-python-insiders.vsix --account-name $AZURE_STORAGE_ACCOUNT --account-key $AZURE_STORAGE_ACCESS_KEY --file python*.vsix --quiet; + azure storage blob upload python*.vsix $AZURE_STORAGE_CONTAINER ms-python-insiders.vsix --account-name $AZURE_STORAGE_ACCOUNT --account-key $AZURE_STORAGE_ACCESS_KEY --quiet; fi diff --git a/news/3 Code Health/1730.md b/news/3 Code Health/1730.md new file mode 100644 index 000000000000..d5d41c722fc8 --- /dev/null +++ b/news/3 Code Health/1730.md @@ -0,0 +1 @@ +Build and upload development build of the extension to the Azure blob store even if CI tests fail on the `master` branch. \ No newline at end of file diff --git a/news/3 Code Health/1732.md b/news/3 Code Health/1732.md new file mode 100644 index 000000000000..bc6c53c11f02 --- /dev/null +++ b/news/3 Code Health/1732.md @@ -0,0 +1 @@ +Changes to the script used to upload the extension to the Azure blob store. \ No newline at end of file From f4a3de98f8929a082183b1745dbf9b63b9bb9aad Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 28 May 2018 09:02:43 -0700 Subject: [PATCH 263/433] Prompt to reload VS Code when changing analysis engines (#1754) * Prompt to reload VS Code when chaning analysis engines * Change message * Validate reloading as well --- news/3 Code Health/1747.md | 1 + src/client/activation/activationService.ts | 75 ++++++ src/client/activation/analysis.ts | 18 +- src/client/activation/classic.ts | 30 ++- src/client/activation/serviceRegistry.ts | 16 ++ src/client/activation/types.ts | 13 +- src/client/common/constants.ts | 3 +- src/client/common/types.ts | 7 +- src/client/extension.ts | 24 +- src/client/formatters/baseFormatter.ts | 1 - src/client/ioc/serviceManager.ts | 3 +- src/client/linters/lintingEngine.ts | 1 - .../activation/activationService.unit.test.ts | 220 ++++++++++++++++++ 13 files changed, 374 insertions(+), 38 deletions(-) create mode 100644 news/3 Code Health/1747.md create mode 100644 src/client/activation/activationService.ts create mode 100644 src/client/activation/serviceRegistry.ts create mode 100644 src/test/activation/activationService.unit.test.ts diff --git a/news/3 Code Health/1747.md b/news/3 Code Health/1747.md new file mode 100644 index 000000000000..7d098b4bf0a8 --- /dev/null +++ b/news/3 Code Health/1747.md @@ -0,0 +1 @@ +Prompt user to reload Visual Studio Code when toggling between the analysis engines. diff --git a/src/client/activation/activationService.ts b/src/client/activation/activationService.ts new file mode 100644 index 000000000000..1f8ff2d754ff --- /dev/null +++ b/src/client/activation/activationService.ts @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { ConfigurationChangeEvent, Disposable, OutputChannel, Uri } from 'vscode'; +import { IApplicationShell, ICommandManager, IWorkspaceService } from '../common/application/types'; +import { isPythonAnalysisEngineTest, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; +import '../common/extensions'; +import { IConfigurationService, IDisposableRegistry, IOutputChannel, IPythonSettings } from '../common/types'; +import { IServiceContainer } from '../ioc/types'; +import { ExtensionActivators, IExtensionActivationService, IExtensionActivator } from './types'; + +const jediEnabledSetting: keyof IPythonSettings = 'jediEnabled'; + +type ActivatorInfo = { jedi: boolean; activator: IExtensionActivator }; + +@injectable() +export class ExtensionActivationService implements IExtensionActivationService, Disposable { + private currentActivator?: ActivatorInfo; + private readonly workspaceService: IWorkspaceService; + private readonly output: OutputChannel; + private readonly appShell: IApplicationShell; + constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { + this.workspaceService = this.serviceContainer.get(IWorkspaceService); + this.output = this.serviceContainer.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); + this.appShell = this.serviceContainer.get(IApplicationShell); + + const disposables = serviceContainer.get(IDisposableRegistry); + disposables.push(this); + disposables.push(this.workspaceService.onDidChangeConfiguration(this.onDidChangeConfiguration.bind(this))); + } + public async activate(): Promise { + if (this.currentActivator) { + return; + } + + const jedi = this.useJedi(); + + const engineName = jedi ? 'classic analysis engine' : 'analysis engine'; + this.output.appendLine(`Starting the ${engineName}.`); + const activatorName = jedi ? ExtensionActivators.Jedi : ExtensionActivators.DotNet; + const activator = this.serviceContainer.get(IExtensionActivator, activatorName); + this.currentActivator = { jedi, activator }; + + await activator.activate(); + } + public dispose() { + if (this.currentActivator) { + this.currentActivator.activator.deactivate().ignoreErrors(); + } + } + private async onDidChangeConfiguration(event: ConfigurationChangeEvent) { + const workspacesUris: (Uri | undefined)[] = this.workspaceService.hasWorkspaceFolders ? this.workspaceService.workspaceFolders!.map(workspace => workspace.uri) : [undefined]; + if (workspacesUris.findIndex(uri => event.affectsConfiguration(`python.${jediEnabledSetting}`, uri)) === -1) { + return; + } + const jedi = this.useJedi(); + if (this.currentActivator && this.currentActivator.jedi === jedi) { + return; + } + + const item = await this.appShell.showInformationMessage('Please reload the window switching between the analysis engines.', 'Reload'); + if (item === 'Reload') { + this.serviceContainer.get(ICommandManager).executeCommand('workbench.action.reloadWindow'); + } + } + private useJedi(): boolean { + const workspacesUris: (Uri | undefined)[] = this.workspaceService.hasWorkspaceFolders ? this.workspaceService.workspaceFolders!.map(item => item.uri) : [undefined]; + const configuraionService = this.serviceContainer.get(IConfigurationService); + const jediEnabledForAnyWorkspace = workspacesUris.filter(uri => configuraionService.getSettings(uri).jediEnabled).length > 0; + return !isPythonAnalysisEngineTest() && jediEnabledForAnyWorkspace; + } +} diff --git a/src/client/activation/analysis.ts b/src/client/activation/analysis.ts index bf8fd4c9e587..b628159409e6 100644 --- a/src/client/activation/analysis.ts +++ b/src/client/activation/analysis.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import { inject, injectable } from 'inversify'; import * as path from 'path'; import { ExtensionContext, OutputChannel } from 'vscode'; import { Message } from 'vscode-jsonrpc'; @@ -10,7 +11,7 @@ import { isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; import { createDeferred, Deferred } from '../common/helpers'; import { IFileSystem, IPlatformService } from '../common/platform/types'; import { StopWatch } from '../common/stopWatch'; -import { IConfigurationService, IOutputChannel, IPythonSettings } from '../common/types'; +import { IConfigurationService, IExtensionContext, IOutputChannel } from '../common/types'; import { IEnvironmentVariablesProvider } from '../common/variables/types'; import { IInterpreterService } from '../interpreter/contracts'; import { IServiceContainer } from '../ioc/types'; @@ -43,6 +44,7 @@ class LanguageServerStartupErrorHandler implements ErrorHandler { } } +@injectable() export class AnalysisExtensionActivator implements IExtensionActivator { private readonly configuration: IConfigurationService; private readonly appShell: IApplicationShell; @@ -53,10 +55,11 @@ export class AnalysisExtensionActivator implements IExtensionActivator { private readonly interpreterService: IInterpreterService; private readonly disposables: Disposable[] = []; private languageClient: LanguageClient | undefined; - private context: ExtensionContext | undefined; + private readonly context: ExtensionContext; private interpreterHash: string = ''; - constructor(private readonly services: IServiceContainer, pythonSettings: IPythonSettings) { + constructor(@inject(IServiceContainer) private readonly services: IServiceContainer) { + this.context = this.services.get(IExtensionContext); this.configuration = this.services.get(IConfigurationService); this.appShell = this.services.get(IApplicationShell); this.output = this.services.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); @@ -65,15 +68,14 @@ export class AnalysisExtensionActivator implements IExtensionActivator { this.interpreterService = this.services.get(IInterpreterService); } - public async activate(context: ExtensionContext): Promise { + public async activate(): Promise { this.sw.reset(); - this.context = context; - const clientOptions = await this.getAnalysisOptions(context); + const clientOptions = await this.getAnalysisOptions(this.context); if (!clientOptions) { return false; } this.disposables.push(this.interpreterService.onDidChangeInterpreter(() => this.restartLanguageServer())); - return this.startLanguageServer(context, clientOptions); + return this.startLanguageServer(this.context, clientOptions); } public async deactivate(): Promise { @@ -94,7 +96,7 @@ export class AnalysisExtensionActivator implements IExtensionActivator { if (!idata || idata.hash !== this.interpreterHash) { this.interpreterHash = idata ? idata.hash : ''; await this.deactivate(); - await this.activate(this.context); + await this.activate(); } } diff --git a/src/client/activation/classic.ts b/src/client/activation/classic.ts index 76a25a426415..5ca9ee04c216 100644 --- a/src/client/activation/classic.ts +++ b/src/client/activation/classic.ts @@ -1,9 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { DocumentFilter, ExtensionContext, languages, OutputChannel } from 'vscode'; -import { STANDARD_OUTPUT_CHANNEL } from '../common/constants'; -import { ILogger, IOutputChannel, IPythonSettings } from '../common/types'; +import { inject, injectable } from 'inversify'; +import { DocumentFilter, languages, OutputChannel } from 'vscode'; +import { PYTHON, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; +import { IConfigurationService, IExtensionContext, ILogger, IOutputChannel } from '../common/types'; import { IShebangCodeLensProvider } from '../interpreter/contracts'; import { IServiceManager } from '../ioc/types'; import { JediFactory } from '../languageServices/jediProxyFactory'; @@ -19,15 +20,22 @@ import { PythonSymbolProvider } from '../providers/symbolProvider'; import { IUnitTestManagementService } from '../unittests/types'; import { IExtensionActivator } from './types'; +@injectable() export class ClassicExtensionActivator implements IExtensionActivator { - constructor(private serviceManager: IServiceManager, private pythonSettings: IPythonSettings, private documentSelector: DocumentFilter[]) { + private readonly context: IExtensionContext; + private jediFactory?: JediFactory; + private readonly documentSelector: DocumentFilter[]; + constructor(@inject(IServiceManager) private serviceManager: IServiceManager) { + this.context = this.serviceManager.get(IExtensionContext); + this.documentSelector = PYTHON; } - public async activate(context: ExtensionContext): Promise { + public async activate(): Promise { + const context = this.context; const standardOutputChannel = this.serviceManager.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); activateSimplePythonRefactorProvider(context, standardOutputChannel, this.serviceManager); - const jediFactory = new JediFactory(context.asAbsolutePath('.'), this.serviceManager); + const jediFactory = this.jediFactory = new JediFactory(context.asAbsolutePath('.'), this.serviceManager); context.subscriptions.push(jediFactory); context.subscriptions.push(...activateGoToObjectDefinitionProvider(jediFactory)); @@ -44,7 +52,8 @@ export class ClassicExtensionActivator implements IExtensionActivator { const symbolProvider = new PythonSymbolProvider(jediFactory); context.subscriptions.push(languages.registerDocumentSymbolProvider(this.documentSelector, symbolProvider)); - if (this.pythonSettings.devOptions.indexOf('DISABLE_SIGNATURE') === -1) { + const pythonSettings = this.serviceManager.get(IConfigurationService).getSettings(); + if (pythonSettings.devOptions.indexOf('DISABLE_SIGNATURE') === -1) { context.subscriptions.push(languages.registerSignatureHelpProvider(this.documentSelector, new PythonSignatureProvider(jediFactory), '(', ',')); } @@ -56,6 +65,9 @@ export class ClassicExtensionActivator implements IExtensionActivator { return true; } - // tslint:disable-next-line:no-empty - public async deactivate(): Promise { } + public async deactivate(): Promise { + if (this.jediFactory) { + this.jediFactory.dispose(); + } + } } diff --git a/src/client/activation/serviceRegistry.ts b/src/client/activation/serviceRegistry.ts new file mode 100644 index 000000000000..b07ba82d6218 --- /dev/null +++ b/src/client/activation/serviceRegistry.ts @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { IServiceManager } from '../ioc/types'; +import { ExtensionActivationService } from './activationService'; +import { AnalysisExtensionActivator } from './analysis'; +import { ClassicExtensionActivator } from './classic'; +import { ExtensionActivators, IExtensionActivationService, IExtensionActivator } from './types'; + +export function registerTypes(serviceManager: IServiceManager) { + serviceManager.addSingleton(IExtensionActivationService, ExtensionActivationService); + serviceManager.add(IExtensionActivator, ClassicExtensionActivator, ExtensionActivators.Jedi); + serviceManager.add(IExtensionActivator, AnalysisExtensionActivator, ExtensionActivators.DotNet); +} diff --git a/src/client/activation/types.ts b/src/client/activation/types.ts index f8366a6ce5dd..714f51378251 100644 --- a/src/client/activation/types.ts +++ b/src/client/activation/types.ts @@ -1,9 +1,18 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import * as vscode from 'vscode'; +export const IExtensionActivationService = Symbol('IExtensionActivationService'); +export interface IExtensionActivationService { + activate(): Promise; +} + +export enum ExtensionActivators { + Jedi = 'Jedi', + DotNet = 'DotNet' +} +export const IExtensionActivator = Symbol('IExtensionActivator'); export interface IExtensionActivator { - activate(context: vscode.ExtensionContext): Promise; + activate(): Promise; deactivate(): Promise; } diff --git a/src/client/common/constants.ts b/src/client/common/constants.ts index 52533d6642f1..6affcbcf1617 100644 --- a/src/client/common/constants.ts +++ b/src/client/common/constants.ts @@ -71,8 +71,7 @@ export namespace LinterErrors { export const STANDARD_OUTPUT_CHANNEL = 'STANDARD_OUTPUT_CHANNEL'; export function isTestExecution(): boolean { - // tslint:disable-next-line:interface-name no-string-literal - return process.env['VSC_PYTHON_CI_TEST'] === '1'; + return process.env.VSC_PYTHON_CI_TEST === '1'; } export function isPythonAnalysisEngineTest(): boolean { return process.env.VSC_PYTHON_ANALYSIS === '1'; diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 547ccc8f6401..71d39e8e4497 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -3,14 +3,16 @@ // Licensed under the MIT License. import { Socket } from 'net'; -import { ConfigurationTarget, DiagnosticSeverity, Disposable, Uri } from 'vscode'; +import { ConfigurationTarget, DiagnosticSeverity, Disposable, ExtensionContext, OutputChannel, Uri } from 'vscode'; import { EnvironmentVariables } from './variables/types'; export const IOutputChannel = Symbol('IOutputChannel'); +export interface IOutputChannel extends OutputChannel { } export const IDocumentSymbolProvider = Symbol('IDocumentSymbolProvider'); export const IsWindows = Symbol('IS_WINDOWS'); export const Is64Bit = Symbol('Is64Bit'); export const IDisposableRegistry = Symbol('IDiposableRegistry'); +export type IDisposableRegistry = Disposable[]; export const IMemento = Symbol('IGlobalMemento'); export const GLOBAL_MEMENTO = Symbol('IGlobalMemento'); export const WORKSPACE_MEMENTO = Symbol('IWorkspaceMemento'); @@ -233,3 +235,6 @@ export interface ISocketServer extends Disposable { readonly client: Promise; Start(options?: { port?: number; host?: string }): Promise; } + +export const IExtensionContext = Symbol('ExtensionContext'); +export interface IExtensionContext extends ExtensionContext { } diff --git a/src/client/extension.ts b/src/client/extension.ts index 70499bd31280..05a15fa5db55 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -11,11 +11,10 @@ import { extensions, IndentAction, languages, Memento, OutputChannel, window } from 'vscode'; -import { AnalysisExtensionActivator } from './activation/analysis'; -import { ClassicExtensionActivator } from './activation/classic'; -import { IExtensionActivator } from './activation/types'; +import { registerTypes as activationRegisterTypes } from './activation/serviceRegistry'; +import { IExtensionActivationService } from './activation/types'; import { PythonSettings } from './common/configSettings'; -import { isPythonAnalysisEngineTest, PYTHON, PYTHON_LANGUAGE, STANDARD_OUTPUT_CHANNEL } from './common/constants'; +import { PYTHON, PYTHON_LANGUAGE, STANDARD_OUTPUT_CHANNEL } from './common/constants'; import { FeatureDeprecationManager } from './common/featureDeprecationManager'; import { createDeferred } from './common/helpers'; import { PythonInstaller } from './common/installer/pythonInstallation'; @@ -25,7 +24,7 @@ import { registerTypes as processRegisterTypes } from './common/process/serviceR import { registerTypes as commonRegisterTypes } from './common/serviceRegistry'; import { StopWatch } from './common/stopWatch'; import { ITerminalHelper } from './common/terminal/types'; -import { GLOBAL_MEMENTO, IConfigurationService, IDisposableRegistry, ILogger, IMemento, IOutputChannel, IPersistentStateFactory, WORKSPACE_MEMENTO } from './common/types'; +import { GLOBAL_MEMENTO, IConfigurationService, IDisposableRegistry, IExtensionContext, ILogger, IMemento, IOutputChannel, IPersistentStateFactory, WORKSPACE_MEMENTO } from './common/types'; import { registerTypes as variableRegisterTypes } from './common/variables/serviceRegistry'; import { AttachRequestArguments, LaunchRequestArguments } from './debugger/Common/Contracts'; import { BaseConfigurationProvider } from './debugger/configProviders/baseProvider'; @@ -37,7 +36,7 @@ import { ICondaService, IInterpreterService } from './interpreter/contracts'; import { registerTypes as interpretersRegisterTypes } from './interpreter/serviceRegistry'; import { ServiceContainer } from './ioc/container'; import { ServiceManager } from './ioc/serviceManager'; -import { IServiceContainer } from './ioc/types'; +import { IServiceContainer, IServiceManager } from './ioc/types'; import { LinterCommands } from './linters/linterCommands'; import { registerTypes as lintersRegisterTypes } from './linters/serviceRegistry'; import { ILintingEngine } from './linters/types'; @@ -76,18 +75,14 @@ export async function activate(context: ExtensionContext) { const configuration = serviceManager.get(IConfigurationService); const pythonSettings = configuration.getSettings(); - const activator: IExtensionActivator = isPythonAnalysisEngineTest() || !pythonSettings.jediEnabled - ? new AnalysisExtensionActivator(serviceManager, pythonSettings) - : new ClassicExtensionActivator(serviceManager, pythonSettings, PYTHON); - - await activator.activate(context); + const activationService = serviceContainer.get(IExtensionActivationService); + await activationService.activate(); const standardOutputChannel = serviceManager.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); sortImports.activate(context, standardOutputChannel, serviceManager); serviceManager.get(ICodeExecutionManager).registerCommands(); - // tslint:disable-next-line:no-floating-promises - sendStartupTelemetry(activated, serviceContainer); + sendStartupTelemetry(activated, serviceContainer).ignoreErrors(); const pythonInstaller = new PythonInstaller(serviceContainer); pythonInstaller.checkPythonInstallation(PythonSettings.getInstance()) @@ -160,15 +155,18 @@ export async function activate(context: ExtensionContext) { function registerServices(context: ExtensionContext, serviceManager: ServiceManager, serviceContainer: ServiceContainer) { serviceManager.addSingletonInstance(IServiceContainer, serviceContainer); + serviceManager.addSingletonInstance(IServiceManager, serviceManager); serviceManager.addSingletonInstance(IDisposableRegistry, context.subscriptions); serviceManager.addSingletonInstance(IMemento, context.globalState, GLOBAL_MEMENTO); serviceManager.addSingletonInstance(IMemento, context.workspaceState, WORKSPACE_MEMENTO); + serviceManager.addSingletonInstance(IExtensionContext, context); const standardOutputChannel = window.createOutputChannel('Python'); const unitTestOutChannel = window.createOutputChannel('Python Test Log'); serviceManager.addSingletonInstance(IOutputChannel, standardOutputChannel, STANDARD_OUTPUT_CHANNEL); serviceManager.addSingletonInstance(IOutputChannel, unitTestOutChannel, TEST_OUTPUT_CHANNEL); + activationRegisterTypes(serviceManager); commonRegisterTypes(serviceManager); processRegisterTypes(serviceManager); variableRegisterTypes(serviceManager); diff --git a/src/client/formatters/baseFormatter.ts b/src/client/formatters/baseFormatter.ts index d72edac84532..21a0ca067145 100644 --- a/src/client/formatters/baseFormatter.ts +++ b/src/client/formatters/baseFormatter.ts @@ -41,7 +41,6 @@ export abstract class BaseFormatter { return vscode.Uri.file(__dirname); } protected async provideDocumentFormattingEdits(document: vscode.TextDocument, options: vscode.FormattingOptions, token: vscode.CancellationToken, args: string[], cwd?: string): Promise { - this.outputChannel.clear(); if (typeof cwd !== 'string' || cwd.length === 0) { cwd = this.getWorkspaceUri(document).fsPath; } diff --git a/src/client/ioc/serviceManager.ts b/src/client/ioc/serviceManager.ts index 792358da06dd..bcd4331dfd13 100644 --- a/src/client/ioc/serviceManager.ts +++ b/src/client/ioc/serviceManager.ts @@ -1,11 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { Container, interfaces } from 'inversify'; +import { Container, injectable, interfaces } from 'inversify'; import { Abstract, IServiceManager, Newable } from './types'; type identifier = string | symbol | Newable | Abstract; +@injectable() export class ServiceManager implements IServiceManager { constructor(private container: Container) { } // tslint:disable-next-line:no-any diff --git a/src/client/linters/lintingEngine.ts b/src/client/linters/lintingEngine.ts index 93b424780278..722dc61c830e 100644 --- a/src/client/linters/lintingEngine.ts +++ b/src/client/linters/lintingEngine.ts @@ -92,7 +92,6 @@ export class LintingEngine implements ILintingEngine { }); this.pendingLintings.set(document.uri.fsPath, cancelToken); - this.outputChannel.clear(); const promises: Promise[] = this.linterManager.getActiveLinters(document.uri) .map(info => { diff --git a/src/test/activation/activationService.unit.test.ts b/src/test/activation/activationService.unit.test.ts new file mode 100644 index 000000000000..24c5d2841522 --- /dev/null +++ b/src/test/activation/activationService.unit.test.ts @@ -0,0 +1,220 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:max-func-body-length + +import * as TypeMoq from 'typemoq'; +import { ConfigurationChangeEvent, Disposable } from 'vscode'; +import { ExtensionActivationService } from '../../client/activation/activationService'; +import { ExtensionActivators, IExtensionActivationService, IExtensionActivator } from '../../client/activation/types'; +import { IApplicationShell, ICommandManager, IWorkspaceService } from '../../client/common/application/types'; +import { isPythonAnalysisEngineTest } from '../../client/common/constants'; +import { IConfigurationService, IDisposableRegistry, IOutputChannel, IPythonSettings } from '../../client/common/types'; +import { IServiceContainer } from '../../client/ioc/types'; + +suite('Activation - ActivationService', () => { + [true, false].forEach(jediIsEnabled => { + suite(`Jedi is ${jediIsEnabled ? 'dnabled' : 'disabled'}`, () => { + let serviceContainer: TypeMoq.IMock; + let pythonSettings: TypeMoq.IMock; + let appShell: TypeMoq.IMock; + let cmdManager: TypeMoq.IMock; + let workspaceService: TypeMoq.IMock; + setup(function () { + if (isPythonAnalysisEngineTest()) { + // tslint:disable-next-line:no-invalid-this + return this.skip(); + } + serviceContainer = TypeMoq.Mock.ofType(); + appShell = TypeMoq.Mock.ofType(); + workspaceService = TypeMoq.Mock.ofType(); + cmdManager = TypeMoq.Mock.ofType(); + const configService = TypeMoq.Mock.ofType(); + pythonSettings = TypeMoq.Mock.ofType(); + + workspaceService.setup(w => w.hasWorkspaceFolders).returns(() => false); + workspaceService.setup(w => w.workspaceFolders).returns(() => []); + configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); + + const output = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IOutputChannel), TypeMoq.It.isAny())).returns(() => output.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IWorkspaceService))).returns(() => workspaceService.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IApplicationShell))).returns(() => appShell.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IDisposableRegistry))).returns(() => []); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationService))).returns(() => configService.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ICommandManager))).returns(() => cmdManager.object); + }); + + async function testActivation(activationService: IExtensionActivationService, activator: TypeMoq.IMock) { + activator + .setup(a => a.activate()).returns(() => Promise.resolve(true)) + .verifiable(TypeMoq.Times.once()); + const activatorName = jediIsEnabled ? ExtensionActivators.Jedi : ExtensionActivators.DotNet; + serviceContainer + .setup(c => c.get(TypeMoq.It.isValue(IExtensionActivator), TypeMoq.It.isValue(activatorName))) + .returns(() => activator.object) + .verifiable(TypeMoq.Times.once()); + + await activationService.activate(); + + activator.verifyAll(); + serviceContainer.verifyAll(); + } + test('Activatory must be activated', async () => { + pythonSettings.setup(p => p.jediEnabled).returns(() => jediIsEnabled); + const activator = TypeMoq.Mock.ofType(); + const activationService = new ExtensionActivationService(serviceContainer.object); + + await testActivation(activationService, activator); + }); + test('Activatory must be deactivated', async () => { + pythonSettings.setup(p => p.jediEnabled).returns(() => jediIsEnabled); + const activator = TypeMoq.Mock.ofType(); + const activationService = new ExtensionActivationService(serviceContainer.object); + + await testActivation(activationService, activator); + + activator + .setup(a => a.deactivate()).returns(() => Promise.resolve()) + .verifiable(TypeMoq.Times.once()); + + activationService.dispose(); + activator.verifyAll(); + }); + test('Prompt user to reload VS Code and reload, when setting is toggled', async () => { + let callbackHandler!: (e: ConfigurationChangeEvent) => Promise; + let jediIsEnabledValueInSetting = jediIsEnabled; + workspaceService + .setup(w => w.onDidChangeConfiguration(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .callback(cb => callbackHandler = cb) + .returns(() => TypeMoq.Mock.ofType().object) + .verifiable(TypeMoq.Times.once()); + + pythonSettings.setup(p => p.jediEnabled).returns(() => jediIsEnabledValueInSetting); + const activator = TypeMoq.Mock.ofType(); + const activationService = new ExtensionActivationService(serviceContainer.object); + + workspaceService.verifyAll(); + await testActivation(activationService, activator); + + const event = TypeMoq.Mock.ofType(); + event.setup(e => e.affectsConfiguration(TypeMoq.It.isValue('python.jediEnabled'), TypeMoq.It.isAny())) + .returns(() => true) + .verifiable(TypeMoq.Times.atLeastOnce()); + appShell.setup(a => a.showInformationMessage(TypeMoq.It.isAny(), TypeMoq.It.isValue('Reload'))) + .returns(() => Promise.resolve('Reload')) + .verifiable(TypeMoq.Times.once()); + cmdManager.setup(c => c.executeCommand(TypeMoq.It.isValue('workbench.action.reloadWindow'))) + .verifiable(TypeMoq.Times.once()); + + // Toggle the value in the setting and invoke the callback. + jediIsEnabledValueInSetting = !jediIsEnabledValueInSetting; + await callbackHandler(event.object); + + event.verifyAll(); + appShell.verifyAll(); + cmdManager.verifyAll(); + }); + test('Prompt user to reload VS Code and do not reload, when setting is toggled', async () => { + let callbackHandler!: (e: ConfigurationChangeEvent) => Promise; + let jediIsEnabledValueInSetting = jediIsEnabled; + workspaceService + .setup(w => w.onDidChangeConfiguration(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .callback(cb => callbackHandler = cb) + .returns(() => TypeMoq.Mock.ofType().object) + .verifiable(TypeMoq.Times.once()); + + pythonSettings.setup(p => p.jediEnabled).returns(() => jediIsEnabledValueInSetting); + const activator = TypeMoq.Mock.ofType(); + const activationService = new ExtensionActivationService(serviceContainer.object); + + workspaceService.verifyAll(); + await testActivation(activationService, activator); + + const event = TypeMoq.Mock.ofType(); + event.setup(e => e.affectsConfiguration(TypeMoq.It.isValue('python.jediEnabled'), TypeMoq.It.isAny())) + .returns(() => true) + .verifiable(TypeMoq.Times.atLeastOnce()); + appShell.setup(a => a.showInformationMessage(TypeMoq.It.isAny(), TypeMoq.It.isValue('Reload'))) + .returns(() => Promise.resolve(undefined)) + .verifiable(TypeMoq.Times.once()); + cmdManager.setup(c => c.executeCommand(TypeMoq.It.isValue('workbench.action.reloadWindow'))) + .verifiable(TypeMoq.Times.never()); + + // Toggle the value in the setting and invoke the callback. + jediIsEnabledValueInSetting = !jediIsEnabledValueInSetting; + await callbackHandler(event.object); + + event.verifyAll(); + appShell.verifyAll(); + cmdManager.verifyAll(); + }); + test('Do not prompt user to reload VS Code when setting is not toggled', async () => { + let callbackHandler!: (e: ConfigurationChangeEvent) => Promise; + workspaceService + .setup(w => w.onDidChangeConfiguration(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .callback(cb => callbackHandler = cb) + .returns(() => TypeMoq.Mock.ofType().object) + .verifiable(TypeMoq.Times.once()); + + pythonSettings.setup(p => p.jediEnabled).returns(() => jediIsEnabled); + const activator = TypeMoq.Mock.ofType(); + const activationService = new ExtensionActivationService(serviceContainer.object); + + workspaceService.verifyAll(); + await testActivation(activationService, activator); + + const event = TypeMoq.Mock.ofType(); + event.setup(e => e.affectsConfiguration(TypeMoq.It.isValue('python.jediEnabled'), TypeMoq.It.isAny())) + .returns(() => true) + .verifiable(TypeMoq.Times.atLeastOnce()); + appShell.setup(a => a.showInformationMessage(TypeMoq.It.isAny(), TypeMoq.It.isValue('Reload'))) + .returns(() => Promise.resolve(undefined)) + .verifiable(TypeMoq.Times.never()); + cmdManager.setup(c => c.executeCommand(TypeMoq.It.isValue('workbench.action.reloadWindow'))) + .verifiable(TypeMoq.Times.never()); + + // Invoke the config changed callback. + await callbackHandler(event.object); + + event.verifyAll(); + appShell.verifyAll(); + cmdManager.verifyAll(); + }); + test('Do not prompt user to reload VS Code when setting is not changed', async () => { + let callbackHandler!: (e: ConfigurationChangeEvent) => Promise; + workspaceService + .setup(w => w.onDidChangeConfiguration(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .callback(cb => callbackHandler = cb) + .returns(() => TypeMoq.Mock.ofType().object) + .verifiable(TypeMoq.Times.once()); + + pythonSettings.setup(p => p.jediEnabled).returns(() => jediIsEnabled); + const activator = TypeMoq.Mock.ofType(); + const activationService = new ExtensionActivationService(serviceContainer.object); + + workspaceService.verifyAll(); + await testActivation(activationService, activator); + + const event = TypeMoq.Mock.ofType(); + event.setup(e => e.affectsConfiguration(TypeMoq.It.isValue('python.jediEnabled'), TypeMoq.It.isAny())) + .returns(() => false) + .verifiable(TypeMoq.Times.atLeastOnce()); + appShell.setup(a => a.showInformationMessage(TypeMoq.It.isAny(), TypeMoq.It.isValue('Reload'))) + .returns(() => Promise.resolve(undefined)) + .verifiable(TypeMoq.Times.never()); + cmdManager.setup(c => c.executeCommand(TypeMoq.It.isValue('workbench.action.reloadWindow'))) + .verifiable(TypeMoq.Times.never()); + + // Invoke the config changed callback. + await callbackHandler(event.object); + + event.verifyAll(); + appShell.verifyAll(); + cmdManager.verifyAll(); + }); + }); + }); +}); From 1368a9f51a4adf67d4185c380f402e894042fe77 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 28 May 2018 09:02:55 -0700 Subject: [PATCH 264/433] Fix some analysis engine tests (#1756) --- src/test/activation/platformData.test.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/test/activation/platformData.test.ts b/src/test/activation/platformData.test.ts index 4270f4c6a3fc..fe999ec670fd 100644 --- a/src/test/activation/platformData.test.ts +++ b/src/test/activation/platformData.test.ts @@ -15,18 +15,18 @@ const testDataWinMac = [ ]; const testDataLinux = [ - { name: 'centos', expectedName: 'centos-x64' }, - { name: 'debian', expectedName: 'debian-x64' }, - { name: 'fedora', expectedName: 'fedora-x64' }, - { name: 'ol', expectedName: 'ol-x64' }, - { name: 'opensuse', expectedName: 'opensuse-x64' }, - { name: 'rhel', expectedName: 'rhel-x64' }, - { name: 'ubuntu', expectedName: 'ubuntu-x64' } + { name: 'centos', expectedName: 'linux-x64' }, + { name: 'debian', expectedName: 'linux-x64' }, + { name: 'fedora', expectedName: 'linux-x64' }, + { name: 'ol', expectedName: 'linux-x64' }, + { name: 'opensuse', expectedName: 'linux-x64' }, + { name: 'rhel', expectedName: 'linux-x64' }, + { name: 'ubuntu', expectedName: 'linux-x64' } ]; const testDataModuleName = [ { isWindows: true, expectedName: 'Microsoft.PythonTools.VsCode.exe' }, - { isWindows: false, expectedName: 'Microsoft.PythonTools.VsCode' } + { isWindows: false, expectedName: 'Microsoft.PythonTools.VsCode.VsCode' } ]; // tslint:disable-next-line:max-func-body-length From ca78bb438281ff75841ae85a7f48b10fd627fa78 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 28 May 2018 09:03:29 -0700 Subject: [PATCH 265/433] Fix rename refactor issue when not ending with EOL (#1748) --- news/2 Fixes/695.md | 1 + pythonFiles/refactor.py | 31 ++++++-- .../source folder/with empty line.py | 8 +++ .../source folder/without empty line.py | 8 +++ src/test/refactor/rename.test.ts | 72 +++++++++++++++++++ 5 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 news/2 Fixes/695.md create mode 100644 src/test/pythonFiles/refactoring/source folder/with empty line.py create mode 100644 src/test/pythonFiles/refactoring/source folder/without empty line.py create mode 100644 src/test/refactor/rename.test.ts diff --git a/news/2 Fixes/695.md b/news/2 Fixes/695.md new file mode 100644 index 000000000000..bb5b48569222 --- /dev/null +++ b/news/2 Fixes/695.md @@ -0,0 +1 @@ +Resoves rename refactor issue that remvoes the last line of the source file when the line is being refactored and source does not end with an EOL. \ No newline at end of file diff --git a/pythonFiles/refactor.py b/pythonFiles/refactor.py index dd4b23ee7727..b782eee26ffb 100644 --- a/pythonFiles/refactor.py +++ b/pythonFiles/refactor.py @@ -2,9 +2,11 @@ # 1. Working directory. # 2. Rope folder +import difflib import io -import sys import json +import os +import sys import traceback try: @@ -55,6 +57,27 @@ def __init__(self, filePath, fileMode=ChangeType.EDIT, diff=""): self.diff = diff self.fileMode = fileMode +def get_diff(changeset): + """This is a copy of the code form the ChangeSet.get_description method found in Rope.""" + new = changeset.new_contents + old = changeset.old_contents + if old is None: + if changeset.resource.exists(): + old = changeset.resource.read() + else: + old = '' + + # Ensure code has a trailing empty lines, before generating a diff. + # https://github.com/Microsoft/vscode-python/issues/695. + old_lines = old.splitlines(True) + if not old_lines[-1].endswith('\n'): + old_lines[-1] = old_lines[-1] + os.linesep + new = new + os.linesep + + result = difflib.unified_diff( + old_lines, new.splitlines(True), + 'a/' + changeset.resource.path, 'b/' + changeset.resource.path) + return ''.join(list(result)) class BaseRefactoring(object): """ @@ -117,7 +140,7 @@ def onRefactor(self): for item in changes.changes: if isinstance(item, rope.base.change.ChangeContents): self.changes.append( - Change(item.resource.real_path, ChangeType.EDIT, item.get_description())) + Change(item.resource.real_path, ChangeType.EDIT, get_diff(item))) else: raise Exception('Unknown Change') @@ -141,7 +164,7 @@ def onRefactor(self): for item in changes.changes: if isinstance(item, rope.base.change.ChangeContents): self.changes.append( - Change(item.resource.real_path, ChangeType.EDIT, item.get_description())) + Change(item.resource.real_path, ChangeType.EDIT, get_diff(item))) else: raise Exception('Unknown Change') @@ -160,7 +183,7 @@ def onRefactor(self): for item in changes.changes: if isinstance(item, rope.base.change.ChangeContents): self.changes.append( - Change(item.resource.real_path, ChangeType.EDIT, item.get_description())) + Change(item.resource.real_path, ChangeType.EDIT, get_diff(item))) else: raise Exception('Unknown Change') diff --git a/src/test/pythonFiles/refactoring/source folder/with empty line.py b/src/test/pythonFiles/refactoring/source folder/with empty line.py new file mode 100644 index 000000000000..01ed75727900 --- /dev/null +++ b/src/test/pythonFiles/refactoring/source folder/with empty line.py @@ -0,0 +1,8 @@ +import os + +def one(): + return True + +def two(): + if one(): + print("A" + one()) diff --git a/src/test/pythonFiles/refactoring/source folder/without empty line.py b/src/test/pythonFiles/refactoring/source folder/without empty line.py new file mode 100644 index 000000000000..a449eb106f5c --- /dev/null +++ b/src/test/pythonFiles/refactoring/source folder/without empty line.py @@ -0,0 +1,8 @@ +import os + +def one(): + return True + +def two(): + if one(): + print("A" + one()) \ No newline at end of file diff --git a/src/test/refactor/rename.test.ts b/src/test/refactor/rename.test.ts new file mode 100644 index 000000000000..cbc4f641ddf7 --- /dev/null +++ b/src/test/refactor/rename.test.ts @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { expect } from 'chai'; +import { EOL } from 'os'; +import * as path from 'path'; +import * as typeMoq from 'typemoq'; +import { Range, TextEditorCursorStyle, TextEditorLineNumbersStyle, TextEditorOptions, window, workspace } from 'vscode'; +import { EXTENSION_ROOT_DIR } from '../../client/common/constants'; +import { BufferDecoder } from '../../client/common/process/decoder'; +import { ProcessService } from '../../client/common/process/proc'; +import { PythonExecutionFactory } from '../../client/common/process/pythonExecutionFactory'; +import { IProcessServiceFactory, IPythonExecutionFactory } from '../../client/common/process/types'; +import { IConfigurationService, IPythonSettings } from '../../client/common/types'; +import { IServiceContainer } from '../../client/ioc/types'; +import { RefactorProxy } from '../../client/refactor/proxy'; +import { PYTHON_PATH } from '../common'; +import { closeActiveWindows, initialize, initializeTest } from './../initialize'; + +type RenameResponse = { + results: [{ diff: string }]; +}; + +suite('Refactor Rename', () => { + const options: TextEditorOptions = { cursorStyle: TextEditorCursorStyle.Line, insertSpaces: true, lineNumbers: TextEditorLineNumbersStyle.Off, tabSize: 4 }; + let pythonSettings: typeMoq.IMock; + let serviceContainer: typeMoq.IMock; + suiteSetup(initialize); + setup(async () => { + pythonSettings = typeMoq.Mock.ofType(); + pythonSettings.setup(p => p.pythonPath).returns(() => PYTHON_PATH); + const configService = typeMoq.Mock.ofType(); + configService.setup(c => c.getSettings(typeMoq.It.isAny())).returns(() => pythonSettings.object); + const processServiceFactory = typeMoq.Mock.ofType(); + processServiceFactory.setup(p => p.create(typeMoq.It.isAny())).returns(() => Promise.resolve(new ProcessService(new BufferDecoder()))); + + serviceContainer = typeMoq.Mock.ofType(); + serviceContainer.setup(s => s.get(typeMoq.It.isValue(IConfigurationService), typeMoq.It.isAny())).returns(() => configService.object); + serviceContainer.setup(s => s.get(typeMoq.It.isValue(IProcessServiceFactory), typeMoq.It.isAny())).returns(() => processServiceFactory.object); + serviceContainer.setup(s => s.get(typeMoq.It.isValue(IPythonExecutionFactory), typeMoq.It.isAny())).returns(() => new PythonExecutionFactory(serviceContainer.object)); + await initializeTest(); + }); + teardown(closeActiveWindows); + suiteTeardown(closeActiveWindows); + + test('Rename function in source without a trailing empty line', async () => { + const sourceFile = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'refactoring', 'source folder', 'without empty line.py'); + const expectedDiff = `--- a/${path.basename(sourceFile)}${EOL}+++ b/${path.basename(sourceFile)}${EOL}@@ -1,8 +1,8 @@${EOL} import os${EOL} ${EOL}-def one():${EOL}+def three():${EOL} return True${EOL} ${EOL} def two():${EOL}- if one():${EOL}- print(\"A\" + one())${EOL}+ if three():${EOL}+ print(\"A\" + three())${EOL}`; + + const proxy = new RefactorProxy(EXTENSION_ROOT_DIR, pythonSettings.object, path.dirname(sourceFile), serviceContainer.object); + const textDocument = await workspace.openTextDocument(sourceFile); + await window.showTextDocument(textDocument); + + const response = await proxy.rename(textDocument, 'three', sourceFile, new Range(7, 20, 7, 23), options); + expect(response.results).to.be.lengthOf(1); + expect(response.results[0].diff).to.be.equal(expectedDiff); + }); + test('Rename function in source with a trailing empty line', async () => { + const sourceFile = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'refactoring', 'source folder', 'with empty line.py'); + const expectedDiff = `--- a/${path.basename(sourceFile)}${EOL}+++ b/${path.basename(sourceFile)}${EOL}@@ -1,8 +1,8 @@${EOL} import os${EOL} ${EOL}-def one():${EOL}+def three():${EOL} return True${EOL} ${EOL} def two():${EOL}- if one():${EOL}- print(\"A\" + one())${EOL}+ if three():${EOL}+ print(\"A\" + three())${EOL}`; + + const proxy = new RefactorProxy(EXTENSION_ROOT_DIR, pythonSettings.object, path.dirname(sourceFile), serviceContainer.object); + const textDocument = await workspace.openTextDocument(sourceFile); + await window.showTextDocument(textDocument); + + const response = await proxy.rename(textDocument, 'three', sourceFile, new Range(7, 20, 7, 23), options); + expect(response.results).to.be.lengthOf(1); + expect(response.results[0].diff).to.be.equal(expectedDiff); + }); +}); From c74abddf2e60ea9cb9a65d05581230d2c2d8bba1 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 28 May 2018 14:30:33 -0700 Subject: [PATCH 266/433] RC release (#1760) --- CHANGELOG.md | 99 ++++++++++++++++++++++++++++++++++++++ news/3 Code Health/1604.md | 2 +- package.json | 2 +- 3 files changed, 101 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ddc0129fd3f..5dc7cbbb7cd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,104 @@ # Changelog +## 2018.5.0 (28 May 2018) + +Thanks to the following projects which we fully rely on to provide some of +our features: +- [isort 4.2.15](https://pypi.org/project/isort/4.2.15/) +- [jedi 0.12.0](https://pypi.org/project/jedi/0.12.0/) + and [parso 0.2.0](https://pypi.org/project/parso/0.2.0/) +- [ptvsd 3.0.0](https://pypi.org/project/ptvsd/3.0.0/) and [4.1.1a1](https://pypi.org/project/ptvsd/4.1.1a1/) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +### Enhancements + +1. Add support for the [Black formatter](https://pypi.org/project/black/) + (thanks to [Josh Smeaton](https://github.com/jarshwah) for the initial patch) + ([#1153](https://github.com/Microsoft/vscode-python/issues/1153)) +1. Add the command 'Discover Unit Tests'. + ([#1474](https://github.com/Microsoft/vscode-python/issues/1474)) +1. Auto detect `*.jinja2` and `*.j2` extensions as Jinja templates, to enable debugging of Jinja templates. + ([#1484](https://github.com/Microsoft/vscode-python/issues/1484)) + +### Fixes + +1. Ensure debugger breaks on `assert` failures. + ([#1194](https://github.com/Microsoft/vscode-python/issues/1194)) +1. Ensure debugged program is terminated when `Stop` debugging button is clicked. + ([#1345](https://github.com/Microsoft/vscode-python/issues/1345)) +1. Ensure python environment activation works as expected within a multi-root workspace. + ([#1476](https://github.com/Microsoft/vscode-python/issues/1476)) +1. Close communication channel before exiting the test runner. + ([#1529](https://github.com/Microsoft/vscode-python/issues/1529)) +1. Allow for negative column numbers in messages returned by `pylint`. + ([#1628](https://github.com/Microsoft/vscode-python/issues/1628)) +1. Modify the `FLASK_APP` environment variable in the flask debug configuration to include just the name of the application file. + ([#1634](https://github.com/Microsoft/vscode-python/issues/1634)) +1. Ensure the display name of an interpreter does not get prefixed twice with the words `Python`. + ([#1651](https://github.com/Microsoft/vscode-python/issues/1651)) +1. `Go to Definition` now works for functions which have numbers that use `_` as a separator (as part of our Jedi 0.12.0 upgrade). + ([#180](https://github.com/Microsoft/vscode-python/issues/180)) +1. Display documentation for auto completion items when the feature to automatically insert of brackets for selected item is turned on. + ([#452](https://github.com/Microsoft/vscode-python/issues/452)) +1. Ensure empty paths do not get added into `sys.path` by the Jedi language server. (this was fixed in the previous release in [#1471](https://github.com/Microsoft/vscode-python/pull/1471)) + ([#677](https://github.com/Microsoft/vscode-python/issues/677)) +1. Resoves rename refactor issue that remvoes the last line of the source file when the line is being refactored and source does not end with an EOL. + ([#695](https://github.com/Microsoft/vscode-python/issues/695)) +1. Ensure the prompt to install missing packages is not displayed more than once. + ([#980](https://github.com/Microsoft/vscode-python/issues/980)) + +### Code Health + +1. Add syntax highlighting to constraints.txt file to match that of piprequirements files + (thanks [Waleed Sehgal](https://github.com/waleedsehgal)) + ([#1053](https://github.com/Microsoft/vscode-python/issues/1053)) +1. Refactor unit testing functionality to improve testability of individual components. + ([#1068](https://github.com/Microsoft/vscode-python/issues/1068)) +1. Add unit tests for evaluating expressions in the experimental debugger. + ([#1109](https://github.com/Microsoft/vscode-python/issues/1109)) +1. Add tests to ensure custom arguments get passed into python program when using the experimental debugger. + ([#1280](https://github.com/Microsoft/vscode-python/issues/1280)) +1. Ensure custom environment variables are always used when spawning any process from within the extension. + ([#1339](https://github.com/Microsoft/vscode-python/issues/1339)) +1. Add tests for hit count breakpoints for the experimental debugger. + ([#1410](https://github.com/Microsoft/vscode-python/issues/1410)) +1. Ensure none of the npm packages (used by the extension) rely on native dependencies. + ([#1416](https://github.com/Microsoft/vscode-python/issues/1416)) +1. Remove explicit initialization of PYTHONPATH with the current workspace path in unit testing of modules with the experimental debugger. + ([#1465](https://github.com/Microsoft/vscode-python/issues/1465)) +1. Flag `program` in `launch.json` configuration items as an optional attribute. + ([#1503](https://github.com/Microsoft/vscode-python/issues/1503)) +1. Remove unused setting `disablePromptForFeatures`. + ([#1551](https://github.com/Microsoft/vscode-python/issues/1551)) +1. Remove unused Unit Test setting `debugHost`. + ([#1552](https://github.com/Microsoft/vscode-python/issues/1552)) +1. Create a new API to retrieve interpreter details with the ability to cache the details. + ([#1569](https://github.com/Microsoft/vscode-python/issues/1569)) +1. Add tests for log points in the experimental debugger. + ([#1582](https://github.com/Microsoft/vscode-python/issues/1582)) +1. Update typescript package to 2.8.3 + ([#1604](https://github.com/Microsoft/vscode-python/issues/1604)) +1. Fix typescript compilation error. + ([#1623](https://github.com/Microsoft/vscode-python/issues/1623)) +1. Fix unit tests used to test flask template debugging on AppVeyor for the experimental debugger. + ([#1640](https://github.com/Microsoft/vscode-python/issues/1640)) +1. Change yarn install script to include the keyword `--lock-file` + (thanks [Lingyu Li](https://github.com/lingyv-li/)) + ([#1682](https://github.com/Microsoft/vscode-python/issues/1682)) +1. Run unit tests as a pre-commit hook. + ([#1703](https://github.com/Microsoft/vscode-python/issues/1703)) +1. Update debug capabilities to add support for the setting `supportTerminateDebuggee` due to an upstream update from [PTVSD](https://github.com/Microsoft/ptvsd/issues). + ([#1719](https://github.com/Microsoft/vscode-python/issues/1719)) +1. Build and upload development build of the extension to the Azure blob store even if CI tests fail on the `master` branch. + ([#1730](https://github.com/Microsoft/vscode-python/issues/1730)) +1. Changes to the script used to upload the extension to the Azure blob store. + ([#1732](https://github.com/Microsoft/vscode-python/issues/1732)) +1. Prompt user to reload Visual Studio Code when toggling between the analysis engines. + ([#1747](https://github.com/Microsoft/vscode-python/issues/1747)) + + + ## 2018.4.0 (2 May 2018) Thanks to the following projects which we fully rely on to provide some of diff --git a/news/3 Code Health/1604.md b/news/3 Code Health/1604.md index 4f693181c6df..c3eca35501c3 100644 --- a/news/3 Code Health/1604.md +++ b/news/3 Code Health/1604.md @@ -1 +1 @@ -- Update typescript package to 2.8.3 +Update typescript package to 2.8.3 diff --git a/package.json b/package.json index 38ab9f5db6d0..177c346d6c3a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2018.5.0-alpha", + "version": "2018.5.0-rc", "publisher": "ms-python", "author": { "name": "Microsoft Corporation" From 6faf5160369df64eda7331f0a24b9ea7183c50e7 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 28 May 2018 15:26:03 -0700 Subject: [PATCH 267/433] Link to Python issues in vscode-docs --- .github/release_plan.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/release_plan.md b/.github/release_plan.md index 2d0adf4f62d9..699a1a30a022 100644 --- a/.github/release_plan.md +++ b/.github/release_plan.md @@ -38,7 +38,7 @@ ## Release a beta version for testing - [ ] Update the [version](https://github.com/Microsoft/vscode-python/blob/master/package.json) to be a `beta` & update the [changelog](https://github.com/Microsoft/vscode-python/blob/master/CHANGELOG.md) - [ ] Announce the beta [development build](https://github.com/Microsoft/vscode-python/blob/master/CONTRIBUTING.md#development-build) (along with how to help [validate fixes](https://github.com/Microsoft/vscode-python/issues?q=label%3A%22validate+fix%22+is%3Aclosed)) -- [ ] Open appropriate [documentation issues](https://github.com/microsoft/vscode-docs) +- [ ] Open appropriate [documentation issues](https://github.com/microsoft/vscode-docs/issues?q=is%3Aissue+is%3Aopen+label%3Apython) # Week of Monday, XXX @@ -55,7 +55,7 @@ ## Test the release candidate code - [ ] Update the [version](https://github.com/Microsoft/vscode-python/blob/master/package.json) to be an `rc` & update the [changelog](https://github.com/Microsoft/vscode-python/blob/master/CHANGELOG.md) - [ ] Announce the release candidate [development build](https://github.com/Microsoft/vscode-python/blob/master/CONTRIBUTING.md#development-build) -- [ ] Open appropriate [documentation issues](https://github.com/microsoft/vscode-docs) +- [ ] Open appropriate [documentation issues](https://github.com/microsoft/vscode-docs/issues?q=is%3Aissue+is%3Aopen+label%3Apython) - [ ] Begin drafting a [blog](http://aka.ms/pythonblog) post ## Prep the release From 10d348b3d5b84b7c830408dd030a4fa632a13b38 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 29 May 2018 11:09:57 -0700 Subject: [PATCH 268/433] Ignore .npmrc when building the extension --- .vscodeignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscodeignore b/.vscodeignore index ed657c8e58e9..9e3e7a4c7326 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -5,6 +5,7 @@ .gitattributes .gitignore .gitmodules +.npmrc .travis.yml CODE_OF_CONDUCT.md CODING_STANDARDS.md From f6ed2fa33ed028973f670c9e7a7a6d21c97a29b4 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Tue, 29 May 2018 11:17:35 -0700 Subject: [PATCH 269/433] Simplifies LS startup and update test baselines (#1762) * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * #1096 The if statement is automatically formatted incorrectly * Merge fix * Add more tests * More tests * Typo * Test * Also better handle multiline arguments * Add a couple missing periods [skip ci] * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * Merge fix * #1257 On type formatting errors for args and kwargs * Handle f-strings * Stop importing from test code * #1308 Single line statements leading to an indentation on the next line * #726 editing python after inline if statement invalid indent * Undo change * Move constant * Harden LS startup error checks * #1364 Intellisense doesn't work after specific const string * Telemetry for the analysis enging * PR feedback * Fix typo * Test baseline update * Jedi 0.12 * Priority to goto_defition * News * Replace unzip * Linux flavors + test * Grammar check * Grammar test * Test baselines * Add news * Pin dependency [skip ci] * Specify markdown as preferable format * Improve function argument detection * Specify markdown * Pythia setting * Baseline updates * Baseline update * Improve startup * Handle missing interpreter better * Handle interpreter change * Delete old file * Fix LS startup time reporting * Remove Async suffix from IFileSystem * Remove Pythia * Remove pre-packaged MSIL * Exe name on Unix * Plain linux * Fix casing * Fix message * Update PTVS engine activation steps * Type formatter eats space in from . * fIX CASING * Remove flag * Don't wait for LS * Small test fixes * Update hover baselines --- src/client/activation/analysis.ts | 57 +++++-------------------- src/test/definitions/hover.ptvs.test.ts | 14 +++--- 2 files changed, 17 insertions(+), 54 deletions(-) diff --git a/src/client/activation/analysis.ts b/src/client/activation/analysis.ts index b628159409e6..522722b39a69 100644 --- a/src/client/activation/analysis.ts +++ b/src/client/activation/analysis.ts @@ -4,11 +4,9 @@ import { inject, injectable } from 'inversify'; import * as path from 'path'; import { ExtensionContext, OutputChannel } from 'vscode'; -import { Message } from 'vscode-jsonrpc'; -import { CloseAction, Disposable, ErrorAction, ErrorHandler, LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient'; +import { Disposable, LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient'; import { IApplicationShell } from '../common/application/types'; import { isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; -import { createDeferred, Deferred } from '../common/helpers'; import { IFileSystem, IPlatformService } from '../common/platform/types'; import { StopWatch } from '../common/stopWatch'; import { IConfigurationService, IExtensionContext, IOutputChannel } from '../common/types'; @@ -18,8 +16,7 @@ import { IServiceContainer } from '../ioc/types'; import { PYTHON_ANALYSIS_ENGINE_DOWNLOADED, PYTHON_ANALYSIS_ENGINE_ENABLED, - PYTHON_ANALYSIS_ENGINE_ERROR, - PYTHON_ANALYSIS_ENGINE_STARTUP + PYTHON_ANALYSIS_ENGINE_ERROR } from '../telemetry/constants'; import { getTelemetryReporter } from '../telemetry/telemetry'; import { AnalysisEngineDownloader } from './downloader'; @@ -32,18 +29,6 @@ const dotNetCommand = 'dotnet'; const languageClientName = 'Python Tools'; const analysisEngineFolder = 'analysis'; -class LanguageServerStartupErrorHandler implements ErrorHandler { - constructor(private readonly deferred: Deferred) { } - public error(error: Error, message: Message, count: number): ErrorAction { - this.deferred.reject(error); - return ErrorAction.Continue; - } - public closed(): CloseAction { - this.deferred.reject(); - return CloseAction.Restart; - } -} - @injectable() export class AnalysisExtensionActivator implements IExtensionActivator { private readonly configuration: IConfigurationService; @@ -102,8 +87,6 @@ export class AnalysisExtensionActivator implements IExtensionActivator { private async startLanguageServer(context: ExtensionContext, clientOptions: LanguageClientOptions): Promise { // Determine if we are running MSIL/Universal via dotnet or self-contained app. - const mscorlib = path.join(context.extensionPath, analysisEngineFolder, 'mscorlib.dll'); - const downloader = new AnalysisEngineDownloader(this.services, analysisEngineFolder); const reporter = getTelemetryReporter(); reporter.sendTelemetryEvent(PYTHON_ANALYSIS_ENGINE_ENABLED); @@ -112,20 +95,21 @@ export class AnalysisExtensionActivator implements IExtensionActivator { if (!settings.downloadCodeAnalysis) { // Depends on .NET Runtime or SDK. Typically development-only case. this.languageClient = this.createSimpleLanguageClient(context, clientOptions); - await this.tryStartLanguageClient(context, this.languageClient); + await this.startLanguageClient(context); return true; } + const mscorlib = path.join(context.extensionPath, analysisEngineFolder, 'mscorlib.dll'); if (!await this.fs.fileExists(mscorlib)) { + const downloader = new AnalysisEngineDownloader(this.services, analysisEngineFolder); await downloader.downloadAnalysisEngine(context); reporter.sendTelemetryEvent(PYTHON_ANALYSIS_ENGINE_DOWNLOADED); } const serverModule = path.join(context.extensionPath, analysisEngineFolder, this.platformData.getEngineExecutableName()); - // Now try to start self-contained app this.languageClient = this.createSelfContainedLanguageClient(context, serverModule, clientOptions); try { - await this.tryStartLanguageClient(context, this.languageClient); + await this.startLanguageClient(context); return true; } catch (ex) { this.appShell.showErrorMessage(`Language server failed to start. Error ${ex}`); @@ -134,31 +118,10 @@ export class AnalysisExtensionActivator implements IExtensionActivator { } } - private async tryStartLanguageClient(context: ExtensionContext, lc: LanguageClient): Promise { - let disposable: Disposable | undefined; - const deferred = createDeferred(); - try { - const sw = new StopWatch(); - lc.clientOptions.errorHandler = new LanguageServerStartupErrorHandler(deferred); - - disposable = lc.start(); - lc.onReady() - .then(() => deferred.resolve()) - .catch((reason) => { - deferred.reject(reason); - }); - await deferred.promise; - - this.output.appendLine(`Language server ready: ${this.sw.elapsedTime} ms`); - context.subscriptions.push(disposable); - - const reporter = getTelemetryReporter(); - reporter.sendTelemetryEvent(PYTHON_ANALYSIS_ENGINE_STARTUP, {}, { startup_time: sw.elapsedTime }); - } catch (ex) { - if (disposable) { - disposable.dispose(); - } - throw ex; + private async startLanguageClient(context: ExtensionContext): Promise { + context.subscriptions.push(this.languageClient!.start()); + if (isTestExecution()) { + await this.languageClient!.onReady(); } } diff --git a/src/test/definitions/hover.ptvs.test.ts b/src/test/definitions/hover.ptvs.test.ts index 089245836090..cb297e0e9374 100644 --- a/src/test/definitions/hover.ptvs.test.ts +++ b/src/test/definitions/hover.ptvs.test.ts @@ -53,7 +53,7 @@ suite('Hover Definition (Analysis Engine)', () => { const expected = [ 'obj.method1:', '```python', - 'method method1 of one.Class1 objects', + 'method method1 of pythonFiles.autocomp.one.Class1 objects', '```', 'This is method1' ]; @@ -70,7 +70,7 @@ suite('Hover Definition (Analysis Engine)', () => { const expected = [ 'two.ct().fun:', '```python', - 'method fun of two.ct objects', + 'method fun of pythonFiles.autocomp.two.ct objects', '```', 'This is fun' ]; @@ -86,7 +86,7 @@ suite('Hover Definition (Analysis Engine)', () => { const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); const expected = [ '```python', - 'four.Foo.bar() -> bool', + 'pythonFiles.autocomp.four.Foo.bar() -> bool', 'declared in Foo', '```', '说明 - keep this line, it works', @@ -105,7 +105,7 @@ suite('Hover Definition (Analysis Engine)', () => { const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); const expected = [ '```python', - 'four.showMessage()', + 'pythonFiles.autocomp.four.showMessage()', '```', 'Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи.', 'Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.' @@ -138,7 +138,7 @@ suite('Hover Definition (Analysis Engine)', () => { const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); const expected = [ '```python', - 'class misc.Random(_random.Random)', + 'class pythonFiles.autocomp.misc.Random(_random.Random)', '```', 'Random number generator base class used by bound module functions.', 'Used to instantiate instances of Random to get generators that don\'t', @@ -162,7 +162,7 @@ suite('Hover Definition (Analysis Engine)', () => { const expected = [ 'rnd2.randint:', '```python', - 'method randint of misc.Random objects -> int', + 'method randint of pythonFiles.autocomp.misc.Random objects -> int', '```', 'Return random integer in range [a, b], including both end points.' ]; @@ -195,7 +195,7 @@ suite('Hover Definition (Analysis Engine)', () => { const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); const expected = [ '```python', - 'class misc.Thread(_Verbose)', + 'class pythonFiles.autocomp.misc.Thread(_Verbose)', '```', 'A class that represents a thread of control.', 'This class can be safely subclassed in a limited fashion.' From 148e879ff4964fb5278b36c4158b70c8c6e2f09e Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Tue, 29 May 2018 12:14:20 -0700 Subject: [PATCH 270/433] Rename engine to 'Microsoft Python Language Server' (#1768) * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * #1096 The if statement is automatically formatted incorrectly * Merge fix * Add more tests * More tests * Typo * Test * Also better handle multiline arguments * Add a couple missing periods [skip ci] * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * Merge fix * #1257 On type formatting errors for args and kwargs * Handle f-strings * Stop importing from test code * #1308 Single line statements leading to an indentation on the next line * #726 editing python after inline if statement invalid indent * Undo change * Move constant * Harden LS startup error checks * #1364 Intellisense doesn't work after specific const string * Telemetry for the analysis enging * PR feedback * Fix typo * Test baseline update * Jedi 0.12 * Priority to goto_defition * News * Replace unzip * Linux flavors + test * Grammar check * Grammar test * Test baselines * Add news * Pin dependency [skip ci] * Specify markdown as preferable format * Improve function argument detection * Specify markdown * Pythia setting * Baseline updates * Baseline update * Improve startup * Handle missing interpreter better * Handle interpreter change * Delete old file * Fix LS startup time reporting * Remove Async suffix from IFileSystem * Remove Pythia * Remove pre-packaged MSIL * Exe name on Unix * Plain linux * Fix casing * Fix message * Update PTVS engine activation steps * Type formatter eats space in from . * fIX CASING * Remove flag * Don't wait for LS * Small test fixes * Update hover baselines * Rename the engine --- src/client/activation/downloader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/activation/downloader.ts b/src/client/activation/downloader.ts index f28075571579..3fee93e418f5 100644 --- a/src/client/activation/downloader.ts +++ b/src/client/activation/downloader.ts @@ -41,7 +41,7 @@ export class AnalysisEngineDownloader { let localTempFilePath = ''; try { - localTempFilePath = await this.downloadFile(downloadUriPrefix, enginePackageFileName, 'Downloading Python Analysis Engine... '); + localTempFilePath = await this.downloadFile(downloadUriPrefix, enginePackageFileName, 'Downloading Microsoft Python Language Server... '); await this.verifyDownload(localTempFilePath, platformString); await this.unpackArchive(context.extensionPath, localTempFilePath); } catch (err) { From e2559f804069e0c434f44387d9a7d04030a7ec27 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 29 May 2018 22:33:19 +0200 Subject: [PATCH 271/433] Fix auto completion with docs (#1769) --- src/client/providers/completionSource.ts | 9 +-------- src/test/providers/completionSource.test.ts | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/client/providers/completionSource.ts b/src/client/providers/completionSource.ts index 5084a6a958f7..764f9e570cfd 100644 --- a/src/client/providers/completionSource.ts +++ b/src/client/providers/completionSource.ts @@ -50,20 +50,13 @@ export class CompletionSource { // Supply hover source with simulated document text where item in question was 'already typed'. const document = documentPosition.document; const position = documentPosition.position; - let insertText: string | undefined; - if (typeof completionItem.insertText === 'string') { - insertText = completionItem.insertText!; - } else if (completionItem.insertText instanceof vscode.SnippetString) { - insertText = (completionItem.insertText! as vscode.SnippetString).value; - } - const itemText = insertText ? insertText : completionItem.label; const wordRange = document.getWordRangeAtPosition(position); const leadingRange = wordRange !== undefined ? new vscode.Range(new vscode.Position(0, 0), wordRange.start) : new vscode.Range(new vscode.Position(0, 0), position); - const itemString = `${itemText}`; + const itemString = completionItem.label; const sourceText = `${document.getText(leadingRange)}${itemString}`; const range = new vscode.Range(leadingRange.end, leadingRange.end.translate(0, itemString.length)); diff --git a/src/test/providers/completionSource.test.ts b/src/test/providers/completionSource.test.ts index cb6f3578aaf4..a6ddf77bf934 100644 --- a/src/test/providers/completionSource.test.ts +++ b/src/test/providers/completionSource.test.ts @@ -66,7 +66,7 @@ suite('Completion Provider', () => { return Promise.resolve(completionResult.object); }); - const expectedSource = `${source}${autoCompleteItems[0].text}${addBrackets ? '($1)' : ''}`; + const expectedSource = `${source}${autoCompleteItems[0].text}`; itemInfoSource.setup(i => i.getItemInfoFromText(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), expectedSource, TypeMoq.It.isAny())) .returns(() => Promise.resolve(undefined)) From 921d7a7cd082da0e590a89ac2d2adb1849320f6b Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 30 May 2018 21:25:54 +0200 Subject: [PATCH 272/433] Fix typo in test (#1795) --- news/3 Code Health/1794.md | 1 + src/test/activation/activationService.unit.test.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 news/3 Code Health/1794.md diff --git a/news/3 Code Health/1794.md b/news/3 Code Health/1794.md new file mode 100644 index 000000000000..54738e99dc85 --- /dev/null +++ b/news/3 Code Health/1794.md @@ -0,0 +1 @@ +Fix typo in unit test. diff --git a/src/test/activation/activationService.unit.test.ts b/src/test/activation/activationService.unit.test.ts index 24c5d2841522..e5390cc2ae23 100644 --- a/src/test/activation/activationService.unit.test.ts +++ b/src/test/activation/activationService.unit.test.ts @@ -16,7 +16,7 @@ import { IServiceContainer } from '../../client/ioc/types'; suite('Activation - ActivationService', () => { [true, false].forEach(jediIsEnabled => { - suite(`Jedi is ${jediIsEnabled ? 'dnabled' : 'disabled'}`, () => { + suite(`Jedi is ${jediIsEnabled ? 'enabled' : 'disabled'}`, () => { let serviceContainer: TypeMoq.IMock; let pythonSettings: TypeMoq.IMock; let appShell: TypeMoq.IMock; From 088177587166bb8bae2b946a6c8c4ae125ab1dbc Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 31 May 2018 10:25:40 -0700 Subject: [PATCH 273/433] Move from Click to docopt for announce.py (#1806) --- .gitignore | 1 + news/announce.py | 26 ++++++++++++++------------ news/requirements.txt | 2 +- news/test_announce.py | 17 +++++++++++++---- 4 files changed, 29 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index 47c9018661ee..d872c09b46b9 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ debug_coverage*/** analysis/** bin/** obj/** +.pytest_cache diff --git a/news/announce.py b/news/announce.py index af715d4c7e4e..895127b2d479 100644 --- a/news/announce.py +++ b/news/announce.py @@ -1,4 +1,8 @@ -"""Generate the changelog.""" +"""Generate the changelog. + +Usage: announce [--dry_run | --interim | --final] [] + +""" import enum import operator import os @@ -8,7 +12,7 @@ import sys import types -import click +import docopt FILENAME_RE = re.compile(r"(?P\d+)(?P-\S+)?\.md") @@ -117,15 +121,6 @@ class RunType(enum.Enum): final = 2 -@click.command() -@click.option('--dry-run', 'run_type', flag_value=RunType.dry_run, - help='validate input') -@click.option('--interim', 'run_type', flag_value=RunType.interim, default=True, - help='generate Markdown') -@click.option('--final', 'run_type', flag_value=RunType.final, - help='generate Markdown & `git rm` news files') -@click.argument('directory', default=pathlib.Path(__file__).parent, - type=click.Path(exists=True, file_okay=False)) def main(run_type, directory): directory = pathlib.Path(directory) data = gather(directory) @@ -137,4 +132,11 @@ def main(run_type, directory): if __name__ == '__main__': - main() + arguments = docopt.docopt(__doc__) + run_type = RunType.interim + for possible_run_type in RunType: + if arguments[f"--{possible_run_type.name}"]: + run_type = possible_run_type + break + directory = arguments[""] or pathlib.Path(__file__).parent + main(run_type, directory) diff --git a/news/requirements.txt b/news/requirements.txt index d47e9da45e5a..316eef65b021 100644 --- a/news/requirements.txt +++ b/news/requirements.txt @@ -1,2 +1,2 @@ -click~=6.7.0 +docopt==0.6.2 pytest~=3.4.1 diff --git a/news/test_announce.py b/news/test_announce.py index 6c9aa256bdc1..3270088b06b6 100644 --- a/news/test_announce.py +++ b/news/test_announce.py @@ -1,5 +1,6 @@ import pathlib +import docopt import pytest import announce as ann @@ -48,8 +49,7 @@ def test_sections_sorting(directory): def test_sections_naming(directory): (directory / 'Hello').mkdir() - with pytest.raises(ValueError): - list(ann.sections(directory)) + assert not ann.sections(directory) def test_gather(directory): @@ -79,8 +79,6 @@ def test_gather(directory): assert entries[1].description == 'Fix 2' - - def test_entry_markdown(): markdown = ann.entry_markdown(ann.NewsEntry(42, 'Hello, world!', None)) assert '42' in markdown @@ -126,3 +124,14 @@ def fake_git_rm(path): section, entries = results.pop() assert len(entries) == 1 assert rm_path == entries[0].path + + +def test_cli(): + for option in ("--"+opt for opt in ["dry_run", "interim", "final"]): + args = docopt.docopt(ann.__doc__, [option]) + assert args[option] + args = docopt.docopt(ann.__doc__, ["./news"]) + assert args[""] == "./news" + args = docopt.docopt(ann.__doc__, ["--dry_run", "./news"]) + assert args["--dry_run"] + assert args[""] == "./news" From 150f91740377220eb2ccc7dc6200eb8b1add13f0 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 31 May 2018 10:45:55 -0700 Subject: [PATCH 274/433] Use an `else` clause on a `for` loop --- news/announce.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/news/announce.py b/news/announce.py index 895127b2d479..f924f4189368 100644 --- a/news/announce.py +++ b/news/announce.py @@ -133,10 +133,11 @@ def main(run_type, directory): if __name__ == '__main__': arguments = docopt.docopt(__doc__) - run_type = RunType.interim for possible_run_type in RunType: if arguments[f"--{possible_run_type.name}"]: run_type = possible_run_type break + else: + run_type = RunType.interim directory = arguments[""] or pathlib.Path(__file__).parent main(run_type, directory) From 114afdd1a7d02986347f44ba4836ba5769066633 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Thu, 31 May 2018 12:02:06 -0700 Subject: [PATCH 275/433] Format on type fixes (#1798) * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * #1096 The if statement is automatically formatted incorrectly * Merge fix * Add more tests * More tests * Typo * Test * Also better handle multiline arguments * Add a couple missing periods [skip ci] * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * Merge fix * #1257 On type formatting errors for args and kwargs * Handle f-strings * Stop importing from test code * #1308 Single line statements leading to an indentation on the next line * #726 editing python after inline if statement invalid indent * Undo change * Move constant * Harden LS startup error checks * #1364 Intellisense doesn't work after specific const string * Telemetry for the analysis enging * PR feedback * Fix typo * Test baseline update * Jedi 0.12 * Priority to goto_defition * News * Replace unzip * Linux flavors + test * Grammar check * Grammar test * Test baselines * Add news * Pin dependency [skip ci] * Specify markdown as preferable format * Improve function argument detection * Specify markdown * Pythia setting * Baseline updates * Baseline update * Improve startup * Handle missing interpreter better * Handle interpreter change * Delete old file * Fix LS startup time reporting * Remove Async suffix from IFileSystem * Remove Pythia * Remove pre-packaged MSIL * Exe name on Unix * Plain linux * Fix casing * Fix message * Update PTVS engine activation steps * Type formatter eats space in from . * fIX CASING * Remove flag * Don't wait for LS * Small test fixes * Update hover baselines * Rename the engine * Formatting 1 * Add support for 'rf' strings * Add two spaces before comment per PEP * Fix @ operator spacing * Handle module and unary ops * Type hints * Fix typo * Trailing comma * Require space after if * Update list of keywords * PR feedback --- src/client/formatters/lineFormatter.ts | 83 ++++++++++++++----- src/client/language/textBuilder.ts | 10 ++- src/client/language/tokenizer.ts | 43 +++++++--- .../format/extension.lineFormatter.test.ts | 33 +++++++- src/test/language/tokenizer.test.ts | 27 ++++-- .../pythonFiles/formatting/pythonGrammar.py | 6 +- 6 files changed, 160 insertions(+), 42 deletions(-) diff --git a/src/client/formatters/lineFormatter.ts b/src/client/formatters/lineFormatter.ts index 9bd256f50177..b7a6a13aa29b 100644 --- a/src/client/formatters/lineFormatter.ts +++ b/src/client/formatters/lineFormatter.ts @@ -10,6 +10,21 @@ import { TextRangeCollection } from '../language/textRangeCollection'; import { Tokenizer } from '../language/tokenizer'; import { ITextRangeCollection, IToken, TokenType } from '../language/types'; +const keywordsWithSpaceBeforeBrace = [ + 'and', 'as', 'assert', 'await', + 'del', + 'except', 'elif', + 'for', 'from', + 'global', + 'if', 'import', 'in', 'is', + 'lambda', + 'nonlocal', 'not', + 'or', + 'raise', 'return', + 'while', 'with', + 'yield' +]; + export class LineFormatter { private builder = new TextBuilder(); private tokens: ITextRangeCollection = new TextRangeCollection([]); @@ -59,7 +74,7 @@ export class LineFormatter { } const id = this.text.substring(t.start, t.end); this.builder.append(id); - if (this.keywordWithSpaceAfter(id) && next && this.isOpenBraceType(next.type)) { + if (this.isKeywordWithSpaceBeforeBrace(id) && next && this.isOpenBraceType(next.type)) { // for x in () this.builder.softAppendSpace(); } @@ -75,9 +90,9 @@ export class LineFormatter { break; case TokenType.Comment: - // Add space before in-line comment. + // Add 2 spaces before in-line comment per PEP guidelines. if (prev) { - this.builder.softAppendSpace(); + this.builder.softAppendSpace(2); } this.builder.append(this.text.substring(t.start, t.end)); break; @@ -98,28 +113,35 @@ export class LineFormatter { private handleOperator(index: number): void { const t = this.tokens.getItemAt(index); const prev = index > 0 ? this.tokens.getItemAt(index - 1) : undefined; + const opCode = this.text.charCodeAt(t.start); const next = index < this.tokens.count - 1 ? this.tokens.getItemAt(index + 1) : undefined; if (t.length === 1) { - const opCode = this.text.charCodeAt(t.start); switch (opCode) { case Char.Equal: - if (this.handleEqual(t, index)) { - return; - } - break; + this.handleEqual(t, index); + return; case Char.Period: if (prev && this.isKeyword(prev, 'from')) { this.builder.softAppendSpace(); } - this.builder.append(this.text[t.start]); + this.builder.append('.'); if (next && this.isKeyword(next, 'import')) { this.builder.softAppendSpace(); } return; case Char.At: + if (prev) { + // Binary case + this.builder.softAppendSpace(); + this.builder.append('@'); + this.builder.softAppendSpace(); + } else { + this.builder.append('@'); + } + return; case Char.ExclamationMark: - this.builder.append(this.text[t.start]); + this.builder.append('!'); return; case Char.Asterisk: if (prev && this.isKeyword(prev, 'lambda')) { @@ -153,19 +175,34 @@ export class LineFormatter { this.builder.softAppendSpace(); this.builder.append(this.text.substring(t.start, t.end)); + + // Check unary case + if (prev && prev.type === TokenType.Operator) { + if (opCode === Char.Hyphen || opCode === Char.Plus || opCode === Char.Tilde) { + return; + } + } this.builder.softAppendSpace(); } - private handleEqual(t: IToken, index: number): boolean { + private handleEqual(t: IToken, index: number): void { if (this.isMultipleStatements(index) && !this.braceCounter.isOpened(TokenType.OpenBrace)) { - return false; // x = 1; x, y = y, x + // x = 1; x, y = y, x + this.builder.softAppendSpace(); + this.builder.append('='); + this.builder.softAppendSpace(); + return; } + // Check if this is = in function arguments. If so, do not add spaces around it. if (this.isEqualsInsideArguments(index)) { this.builder.append('='); - return true; + return; } - return false; + + this.builder.softAppendSpace(); + this.builder.append('='); + this.builder.softAppendSpace(); } private handleOther(t: IToken, index: number): void { @@ -188,6 +225,12 @@ export class LineFormatter { return; } + if (t.type === TokenType.Number && prev && prev.type === TokenType.Operator && prev.length === 1 && this.text.charCodeAt(prev.start) === Char.Tilde) { + // Special case for ~ before numbers + this.builder.append(this.text.substring(t.start, t.end)); + return; + } + if (t.type === TokenType.Unknown) { this.handleUnknown(t); } else { @@ -224,6 +267,10 @@ export class LineFormatter { return false; } + if (index > 1 && this.tokens.getItemAt(index - 2).type === TokenType.Colon) { + return false; // Type hint should have spaces around like foo(x: int = 1) per PEP 8 + } + const first = this.tokens.getItemAt(0); if (first.type === TokenType.Comma) { return true; // Line starts with commma @@ -278,11 +325,9 @@ export class LineFormatter { } return false; } - private keywordWithSpaceAfter(s: string): boolean { - return s === 'in' || s === 'return' || s === 'and' || - s === 'or' || s === 'not' || s === 'from' || - s === 'import' || s === 'except' || s === 'for' || - s === 'as' || s === 'is'; + + private isKeywordWithSpaceBeforeBrace(s: string): boolean { + return keywordsWithSpaceBeforeBrace.indexOf(s) >= 0; } private isKeyword(t: IToken, keyword: string): boolean { return t.type === TokenType.Identifier && t.length === keyword.length && this.text.substr(t.start, t.length) === keyword; diff --git a/src/client/language/textBuilder.ts b/src/client/language/textBuilder.ts index aebe6187696b..e11f2a1299c4 100644 --- a/src/client/language/textBuilder.ts +++ b/src/client/language/textBuilder.ts @@ -16,8 +16,14 @@ export class TextBuilder { return this.segments.join(''); } - public softAppendSpace(): void { - if (!this.isLastWhiteSpace() && this.segments.length > 0) { + public softAppendSpace(count: number = 1): void { + if (this.segments.length === 0) { + return; + } + if (this.isLastWhiteSpace()) { + count = count - 1; + } + for (let i = 0; i < count; i += 1) { this.segments.push(' '); } } diff --git a/src/client/language/tokenizer.ts b/src/client/language/tokenizer.ts index 7ceafdccb0e6..2574c388aeb1 100644 --- a/src/client/language/tokenizer.ts +++ b/src/client/language/tokenizer.ts @@ -280,6 +280,8 @@ export class Tokenizer implements ITokenizer { case Char.Caret: case Char.Equal: case Char.ExclamationMark: + case Char.Percent: + case Char.Tilde: length = nextChar === Char.Equal ? 2 : 1; break; @@ -350,23 +352,40 @@ export class Tokenizer implements ITokenizer { this.tokens.push(new Token(TokenType.Comment, start, this.cs.position - start)); } + // tslint:disable-next-line:cyclomatic-complexity private getStringPrefixLength(): number { - if (this.cs.currentChar === Char.f && (this.cs.nextChar === Char.SingleQuote || this.cs.nextChar === Char.DoubleQuote)) { - return 1; // f-string + if (this.cs.currentChar === Char.SingleQuote || this.cs.currentChar === Char.DoubleQuote) { + return 0; // Simple string, no prefix } - if (this.cs.currentChar === Char.b || this.cs.currentChar === Char.B || this.cs.currentChar === Char.u || this.cs.currentChar === Char.U) { - if (this.cs.nextChar === Char.SingleQuote || this.cs.nextChar === Char.DoubleQuote) { - // b-string or u-string - return 1; + + if (this.cs.nextChar === Char.SingleQuote || this.cs.nextChar === Char.DoubleQuote) { + switch (this.cs.currentChar) { + case Char.f: + case Char.F: + case Char.r: + case Char.R: + case Char.b: + case Char.B: + case Char.u: + case Char.U: + return 1; // single-char prefix like u"" or r"" + default: + break; } - if (this.cs.nextChar === Char.r || this.cs.nextChar === Char.R) { - // b-string or u-string with 'r' suffix - if (this.cs.lookAhead(2) === Char.SingleQuote || this.cs.lookAhead(2) === Char.DoubleQuote) { - return 2; - } + } + + if (this.cs.lookAhead(2) === Char.SingleQuote || this.cs.lookAhead(2) === Char.DoubleQuote) { + const prefix = this.cs.getText().substr(this.cs.position, 2).toLowerCase(); + switch (prefix) { + case 'rf': + case 'ur': + case 'br': + return 2; + default: + break; } } - return this.cs.currentChar === Char.SingleQuote || this.cs.currentChar === Char.DoubleQuote ? 0 : -1; + return -1; } private getQuoteType(): QuoteType { diff --git a/src/test/format/extension.lineFormatter.test.ts b/src/test/format/extension.lineFormatter.test.ts index 656f22c3ffd6..acb86d2c5715 100644 --- a/src/test/format/extension.lineFormatter.test.ts +++ b/src/test/format/extension.lineFormatter.test.ts @@ -59,7 +59,7 @@ suite('Formatting - line formatter', () => { testFormatLine('[ 1 :[2: (x,),y]]{1}', '[1:[2:(x,), y]]{1}'); }); test('Trailing comment', () => { - testFormatLine('x=1 # comment', 'x = 1 # comment'); + testFormatLine('x=1 # comment', 'x = 1 # comment'); }); test('Single comment', () => { testFormatLine('# comment', '# comment'); @@ -87,9 +87,14 @@ suite('Formatting - line formatter', () => { }); test('Brace after keyword', () => { testFormatLine('for x in(1,2,3)', 'for x in (1, 2, 3)'); + testFormatLine('assert(1,2,3)', 'assert (1, 2, 3)'); + testFormatLine('if (True|False)and(False/True)not (! x )', 'if (True | False) and (False / True) not (!x)'); + testFormatLine('while (True|False)', 'while (True | False)'); + testFormatLine('yield(a%b)', 'yield (a % b)'); }); test('Dot operator', () => { testFormatLine('x.y', 'x.y'); + testFormatLine('5 .y', '5.y'); }); test('Unknown tokens no space', () => { testFormatLine('abc\\n\\', 'abc\\n\\'); @@ -121,6 +126,32 @@ suite('Formatting - line formatter', () => { test('from..x import', () => { testFormatLine('from..x import', 'from ..x import'); }); + test('Raw strings', () => { + testFormatLine('z=r""', 'z = r""'); + testFormatLine('z=rf""', 'z = rf""'); + testFormatLine('z=R""', 'z = R""'); + testFormatLine('z=RF""', 'z = RF""'); + }); + test('Binary @', () => { + testFormatLine('a@ b', 'a @ b'); + }); + test('Unary operators', () => { + testFormatLine('x= - y', 'x = -y'); + testFormatLine('x= + y', 'x = +y'); + testFormatLine('x= ~ y', 'x = ~y'); + testFormatLine('x=-1', 'x = -1'); + testFormatLine('x= +1', 'x = +1'); + testFormatLine('x= ~1 ', 'x = ~1'); + }); + test('Equals with type hints', () => { + testFormatLine('def foo(x:int=3,x=100.)', 'def foo(x: int = 3, x=100.)'); + }); + test('Trailing comma', () => { + testFormatLine('a, =[1]', 'a, = [1]'); + }); + test('if()', () => { + testFormatLine('if(True) :', 'if (True):'); + }); test('Grammar file', () => { const content = fs.readFileSync(grammarFile).toString('utf8'); const lines = content.splitLines({ trim: false, removeEmptyEntries: false }); diff --git a/src/test/language/tokenizer.test.ts b/src/test/language/tokenizer.test.ts index d7119b7b4f6f..397a1f9f398d 100644 --- a/src/test/language/tokenizer.test.ts +++ b/src/test/language/tokenizer.test.ts @@ -185,7 +185,7 @@ suite('Language.Tokenizer', () => { }); test('Unknown token', () => { const t = new Tokenizer(); - const tokens = t.tokenize('~$'); + const tokens = t.tokenize('`$'); assert.equal(tokens.count, 1); assert.equal(tokens.getItemAt(0).type, TokenType.Unknown); @@ -301,20 +301,37 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(5).type, TokenType.Number); assert.equal(tokens.getItemAt(5).length, 5); }); + test('Simple expression, leading minus', () => { + const t = new Tokenizer(); + const tokens = t.tokenize('x == -y'); + assert.equal(tokens.count, 4); + + assert.equal(tokens.getItemAt(0).type, TokenType.Identifier); + assert.equal(tokens.getItemAt(0).length, 1); + + assert.equal(tokens.getItemAt(1).type, TokenType.Operator); + assert.equal(tokens.getItemAt(1).length, 2); + + assert.equal(tokens.getItemAt(2).type, TokenType.Operator); + assert.equal(tokens.getItemAt(2).length, 1); + + assert.equal(tokens.getItemAt(3).type, TokenType.Identifier); + assert.equal(tokens.getItemAt(3).length, 1); + }); test('Operators', () => { const text = '< <> << <<= ' + '== != > >> >>= >= <=' + - '+ -' + + '+ - ~ %' + '* ** / /= //=' + - '*= += -= **= ' + + '*= += -= ~= %= **= ' + '& &= | |= ^ ^= ->'; const tokens = new Tokenizer().tokenize(text); const lengths = [ 1, 2, 2, 3, 2, 2, 1, 2, 3, 2, 2, - 1, 1, + 1, 1, 1, 1, 1, 2, 1, 2, 3, - 2, 2, 2, 3, + 2, 2, 2, 2, 2, 3, 1, 2, 1, 2, 1, 2, 2]; assert.equal(tokens.count, lengths.length); for (let i = 0; i < tokens.count; i += 1) { diff --git a/src/test/pythonFiles/formatting/pythonGrammar.py b/src/test/pythonFiles/formatting/pythonGrammar.py index 1a17d94302b5..937cba401d3f 100644 --- a/src/test/pythonFiles/formatting/pythonGrammar.py +++ b/src/test/pythonFiles/formatting/pythonGrammar.py @@ -236,7 +236,7 @@ def test_eof_error(self): compile(s, "", "exec") self.assertIn("unexpected EOF", str(cm.exception)) -var_annot_global: int # a global annotated is necessary for test_var_annot +var_annot_global: int # a global annotated is necessary for test_var_annot # custom namespace for testing __annotations__ @@ -643,7 +643,7 @@ def test_lambdef(self): ### lambdef: 'lambda' [varargslist] ':' test l1 = lambda: 0 self.assertEqual(l1(), 0) - l2 = lambda: a[d] # XXX just testing the expression + l2 = lambda: a[d] # XXX just testing the expression l3 = lambda: [2 < x for x in [-1, 3, 0]] self.assertEqual(l3(), [0, 1, 0]) l4 = lambda x=lambda y=lambda z=1: z: y(): x() @@ -1492,7 +1492,7 @@ def __imatmul__(self, o): self.other = o return self m = M() - self.assertEqual(m@m, 4) + self.assertEqual(m @ m, 4) m @= 42 self.assertEqual(m.other, 42) From cb030322dfa11348a690e58d5919442b77c9ce67 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Thu, 31 May 2018 15:16:04 -0700 Subject: [PATCH 276/433] Handle numbers formatted with underscores in tokenizer (#1819) * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * #1096 The if statement is automatically formatted incorrectly * Merge fix * Add more tests * More tests * Typo * Test * Also better handle multiline arguments * Add a couple missing periods [skip ci] * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * Merge fix * #1257 On type formatting errors for args and kwargs * Handle f-strings * Stop importing from test code * #1308 Single line statements leading to an indentation on the next line * #726 editing python after inline if statement invalid indent * Undo change * Move constant * Harden LS startup error checks * #1364 Intellisense doesn't work after specific const string * Telemetry for the analysis enging * PR feedback * Fix typo * Test baseline update * Jedi 0.12 * Priority to goto_defition * News * Replace unzip * Linux flavors + test * Grammar check * Grammar test * Test baselines * Add news * Pin dependency [skip ci] * Specify markdown as preferable format * Improve function argument detection * Specify markdown * Pythia setting * Baseline updates * Baseline update * Improve startup * Handle missing interpreter better * Handle interpreter change * Delete old file * Fix LS startup time reporting * Remove Async suffix from IFileSystem * Remove Pythia * Remove pre-packaged MSIL * Exe name on Unix * Plain linux * Fix casing * Fix message * Update PTVS engine activation steps * Type formatter eats space in from . * fIX CASING * Remove flag * Don't wait for LS * Small test fixes * Update hover baselines * Rename the engine * Formatting 1 * Add support for 'rf' strings * Add two spaces before comment per PEP * Fix @ operator spacing * Handle module and unary ops * Type hints * Fix typo * Trailing comma * Require space after if * underscore numbers * Update list of keywords * PR feedback * News * Use a bit more Markdown in the news entry --- news/2 Fixes/1779.md | 1 + src/client/language/characters.ts | 12 ++++-- src/client/language/tokenizer.ts | 66 ++++++++++++++++++++--------- src/test/language/tokenizer.test.ts | 38 +++++++++++++---- 4 files changed, 84 insertions(+), 33 deletions(-) create mode 100644 news/2 Fixes/1779.md diff --git a/news/2 Fixes/1779.md b/news/2 Fixes/1779.md new file mode 100644 index 000000000000..066f595d007c --- /dev/null +++ b/news/2 Fixes/1779.md @@ -0,0 +1 @@ +`editor.formatOnType` no longer breaks numbers formatted with underscores. diff --git a/src/client/language/characters.ts b/src/client/language/characters.ts index e0bc20ba4131..5a4da26a7b6d 100644 --- a/src/client/language/characters.ts +++ b/src/client/language/characters.ts @@ -83,18 +83,22 @@ export function isLineBreak(ch: number): boolean { return ch === Char.CarriageReturn || ch === Char.LineFeed; } +export function isNumber(ch: number): boolean { + return ch >= Char._0 && ch <= Char._9 || ch === Char.Underscore; +} + export function isDecimal(ch: number): boolean { - return ch >= Char._0 && ch <= Char._9; + return ch >= Char._0 && ch <= Char._9 || ch === Char.Underscore; } export function isHex(ch: number): boolean { - return isDecimal(ch) || (ch >= Char.a && ch <= Char.f) || (ch >= Char.A && ch <= Char.F); + return isDecimal(ch) || (ch >= Char.a && ch <= Char.f) || (ch >= Char.A && ch <= Char.F) || ch === Char.Underscore; } export function isOctal(ch: number): boolean { - return ch >= Char._0 && ch <= Char._7; + return ch >= Char._0 && ch <= Char._7 || ch === Char.Underscore; } export function isBinary(ch: number): boolean { - return ch === Char._0 || ch === Char._1; + return ch === Char._0 || ch === Char._1 || ch === Char.Underscore; } diff --git a/src/client/language/tokenizer.ts b/src/client/language/tokenizer.ts index 2574c388aeb1..52a3599f132b 100644 --- a/src/client/language/tokenizer.ts +++ b/src/client/language/tokenizer.ts @@ -4,7 +4,7 @@ // tslint:disable-next-line:import-name import Char from 'typescript-char'; -import { isBinary, isDecimal, isHex, isIdentifierChar, isIdentifierStartChar, isOctal } from './characters'; +import { isBinary, isDecimal, isHex, isIdentifierChar, isIdentifierStartChar, isOctal, isWhiteSpace } from './characters'; import { CharacterStream } from './characterStream'; import { TextRangeCollection } from './textRangeCollection'; import { ICharacterStream, ITextRangeCollection, IToken, ITokenizer, TextRange, TokenizerMode, TokenType } from './types'; @@ -29,13 +29,8 @@ class Token extends TextRange implements IToken { export class Tokenizer implements ITokenizer { private cs: ICharacterStream = new CharacterStream(''); private tokens: IToken[] = []; - private floatRegex = /[-+]?(?:(?:\d*\.\d+)|(?:\d+\.?))(?:[Ee][+-]?\d+)?/; private mode = TokenizerMode.Full; - constructor() { - //this.floatRegex.compile(); - } - public tokenize(text: string): ITextRangeCollection; public tokenize(text: string, start: number, length: number, mode: TokenizerMode): ITextRangeCollection; @@ -224,43 +219,74 @@ export class Tokenizer implements ITokenizer { if (this.cs.currentChar === Char._0) { let radix = 0; - // Try hex - if (this.cs.nextChar === Char.x || this.cs.nextChar === Char.X) { + // Try hex => hexinteger: "0" ("x" | "X") (["_"] hexdigit)+ + if ((this.cs.nextChar === Char.x || this.cs.nextChar === Char.X) && isHex(this.cs.lookAhead(2))) { this.cs.advance(2); while (isHex(this.cs.currentChar)) { this.cs.moveNext(); } radix = 16; } - // Try binary - if (this.cs.nextChar === Char.b || this.cs.nextChar === Char.B) { + // Try binary => bininteger: "0" ("b" | "B") (["_"] bindigit)+ + if ((this.cs.nextChar === Char.b || this.cs.nextChar === Char.B) && isBinary(this.cs.lookAhead(2))) { this.cs.advance(2); while (isBinary(this.cs.currentChar)) { this.cs.moveNext(); } radix = 2; } - // Try octal - if (this.cs.nextChar === Char.o || this.cs.nextChar === Char.O) { + // Try octal => octinteger: "0" ("o" | "O") (["_"] octdigit)+ + if ((this.cs.nextChar === Char.o || this.cs.nextChar === Char.O) && isOctal(this.cs.lookAhead(2))) { this.cs.advance(2); while (isOctal(this.cs.currentChar)) { this.cs.moveNext(); } radix = 8; } + if (radix > 0) { + const text = this.cs.getText().substr(start + leadingSign, this.cs.position - start - leadingSign); + if (!isNaN(parseInt(text, radix))) { + this.tokens.push(new Token(TokenType.Number, start, text.length + leadingSign)); + return true; + } + } + } + + let decimal = false; + // Try decimal int => + // decinteger: nonzerodigit (["_"] digit)* | "0" (["_"] "0")* + // nonzerodigit: "1"..."9" + // digit: "0"..."9" + if (this.cs.currentChar >= Char._1 && this.cs.currentChar <= Char._9) { + while (isDecimal(this.cs.currentChar)) { + this.cs.moveNext(); + } + decimal = this.cs.currentChar !== Char.Period && this.cs.currentChar !== Char.e && this.cs.currentChar !== Char.E; + } + + if (this.cs.currentChar === Char._0) { // "0" (["_"] "0")* + while (this.cs.currentChar === Char._0 || this.cs.currentChar === Char.Underscore) { + this.cs.moveNext(); + } + decimal = this.cs.currentChar !== Char.Period && this.cs.currentChar !== Char.e && this.cs.currentChar !== Char.E; + } + + if (decimal) { const text = this.cs.getText().substr(start + leadingSign, this.cs.position - start - leadingSign); - if (radix > 0 && parseInt(text.substr(2), radix)) { + if (!isNaN(parseInt(text, 10))) { this.tokens.push(new Token(TokenType.Number, start, text.length + leadingSign)); return true; } } - if (isDecimal(this.cs.currentChar) || this.cs.currentChar === Char.Period) { - const candidate = this.cs.getText().substr(this.cs.position); - const re = this.floatRegex.exec(candidate); - if (re && re.length > 0 && re[0] && candidate.startsWith(re[0])) { - this.tokens.push(new Token(TokenType.Number, start, re[0].length + leadingSign)); - this.cs.position = start + re[0].length + leadingSign; + // Floating point + if ((this.cs.currentChar >= Char._0 && this.cs.currentChar <= Char._9) || this.cs.currentChar === Char.Period) { + while (!isWhiteSpace(this.cs.currentChar)) { + this.cs.moveNext(); + } + const text = this.cs.getText().substr(start, this.cs.position - start); + if (!isNaN(parseFloat(text))) { + this.tokens.push(new Token(TokenType.Number, start, this.cs.position - start)); return true; } } @@ -380,7 +406,7 @@ export class Tokenizer implements ITokenizer { case 'rf': case 'ur': case 'br': - return 2; + return 2; default: break; } diff --git a/src/test/language/tokenizer.test.ts b/src/test/language/tokenizer.test.ts index 397a1f9f398d..7713b019ab0b 100644 --- a/src/test/language/tokenizer.test.ts +++ b/src/test/language/tokenizer.test.ts @@ -193,7 +193,7 @@ suite('Language.Tokenizer', () => { test('Hex number', () => { const t = new Tokenizer(); const tokens = t.tokenize('1 0X2 0x3 0x'); - assert.equal(tokens.count, 4); + assert.equal(tokens.count, 5); assert.equal(tokens.getItemAt(0).type, TokenType.Number); assert.equal(tokens.getItemAt(0).length, 1); @@ -204,13 +204,16 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(2).type, TokenType.Number); assert.equal(tokens.getItemAt(2).length, 3); - assert.equal(tokens.getItemAt(3).type, TokenType.Unknown); - assert.equal(tokens.getItemAt(3).length, 2); + assert.equal(tokens.getItemAt(3).type, TokenType.Number); + assert.equal(tokens.getItemAt(3).length, 1); + + assert.equal(tokens.getItemAt(4).type, TokenType.Identifier); + assert.equal(tokens.getItemAt(4).length, 1); }); test('Binary number', () => { const t = new Tokenizer(); const tokens = t.tokenize('1 0B1 0b010 0b3 0b'); - assert.equal(tokens.count, 6); + assert.equal(tokens.count, 7); assert.equal(tokens.getItemAt(0).type, TokenType.Number); assert.equal(tokens.getItemAt(0).length, 1); @@ -227,13 +230,16 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(4).type, TokenType.Identifier); assert.equal(tokens.getItemAt(4).length, 2); - assert.equal(tokens.getItemAt(5).type, TokenType.Unknown); - assert.equal(tokens.getItemAt(5).length, 2); + assert.equal(tokens.getItemAt(5).type, TokenType.Number); + assert.equal(tokens.getItemAt(5).length, 1); + + assert.equal(tokens.getItemAt(6).type, TokenType.Identifier); + assert.equal(tokens.getItemAt(6).length, 1); }); test('Octal number', () => { const t = new Tokenizer(); const tokens = t.tokenize('1 0o4 0o077 -0o200 0o9 0oO'); - assert.equal(tokens.count, 7); + assert.equal(tokens.count, 8); assert.equal(tokens.getItemAt(0).type, TokenType.Number); assert.equal(tokens.getItemAt(0).length, 1); @@ -253,8 +259,11 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(5).type, TokenType.Identifier); assert.equal(tokens.getItemAt(5).length, 2); - assert.equal(tokens.getItemAt(6).type, TokenType.Unknown); - assert.equal(tokens.getItemAt(6).length, 3); + assert.equal(tokens.getItemAt(6).type, TokenType.Number); + assert.equal(tokens.getItemAt(6).length, 1); + + assert.equal(tokens.getItemAt(7).type, TokenType.Identifier); + assert.equal(tokens.getItemAt(7).length, 2); }); test('Decimal number', () => { const t = new Tokenizer(); @@ -301,6 +310,17 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(5).type, TokenType.Number); assert.equal(tokens.getItemAt(5).length, 5); }); + test('Underscore numbers', () => { + const t = new Tokenizer(); + const tokens = t.tokenize('+1_0_0_0 0_0 .5_00_3e-4 0xCAFE_F00D 10_000_000.0 0b_0011_1111_0100_1110'); + const lengths = [8, 3, 10, 11, 12, 22]; + assert.equal(tokens.count, 6); + + for (let i = 0; i < tokens.count; i += 1) { + assert.equal(tokens.getItemAt(i).type, TokenType.Number); + assert.equal(tokens.getItemAt(i).length, lengths[i]); + } + }); test('Simple expression, leading minus', () => { const t = new Tokenizer(); const tokens = t.tokenize('x == -y'); From 9bfd126c8752bc8603dca277e0bfd486d77b786e Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Sat, 2 Jun 2018 00:44:43 +0200 Subject: [PATCH 277/433] Enable code refactoring when using the new Analysis Engine (#1801) --- news/2 Fixes/1774.md | 1 + src/client/activation/classic.ts | 9 +++------ src/client/extension.ts | 7 ++++++- 3 files changed, 10 insertions(+), 7 deletions(-) create mode 100644 news/2 Fixes/1774.md diff --git a/news/2 Fixes/1774.md b/news/2 Fixes/1774.md new file mode 100644 index 000000000000..9bf55ad96f30 --- /dev/null +++ b/news/2 Fixes/1774.md @@ -0,0 +1 @@ +Enable code refactoring when using the new Analysis Engine. diff --git a/src/client/activation/classic.ts b/src/client/activation/classic.ts index 5ca9ee04c216..a2da07d38cf9 100644 --- a/src/client/activation/classic.ts +++ b/src/client/activation/classic.ts @@ -2,9 +2,9 @@ // Licensed under the MIT License. import { inject, injectable } from 'inversify'; -import { DocumentFilter, languages, OutputChannel } from 'vscode'; -import { PYTHON, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; -import { IConfigurationService, IExtensionContext, ILogger, IOutputChannel } from '../common/types'; +import { DocumentFilter, languages } from 'vscode'; +import { PYTHON } from '../common/constants'; +import { IConfigurationService, IExtensionContext, ILogger } from '../common/types'; import { IShebangCodeLensProvider } from '../interpreter/contracts'; import { IServiceManager } from '../ioc/types'; import { JediFactory } from '../languageServices/jediProxyFactory'; @@ -15,7 +15,6 @@ import { activateGoToObjectDefinitionProvider } from '../providers/objectDefinit import { PythonReferenceProvider } from '../providers/referenceProvider'; import { PythonRenameProvider } from '../providers/renameProvider'; import { PythonSignatureProvider } from '../providers/signatureProvider'; -import { activateSimplePythonRefactorProvider } from '../providers/simpleRefactorProvider'; import { PythonSymbolProvider } from '../providers/symbolProvider'; import { IUnitTestManagementService } from '../unittests/types'; import { IExtensionActivator } from './types'; @@ -32,8 +31,6 @@ export class ClassicExtensionActivator implements IExtensionActivator { public async activate(): Promise { const context = this.context; - const standardOutputChannel = this.serviceManager.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); - activateSimplePythonRefactorProvider(context, standardOutputChannel, this.serviceManager); const jediFactory = this.jediFactory = new JediFactory(context.asAbsolutePath('.'), this.serviceManager); context.subscriptions.push(jediFactory); diff --git a/src/client/extension.ts b/src/client/extension.ts index 05a15fa5db55..44ad9ee776e2 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -43,7 +43,9 @@ import { ILintingEngine } from './linters/types'; import { DocStringFoldingProvider } from './providers/docStringFoldingProvider'; import { PythonFormattingEditProvider } from './providers/formatProvider'; import { LinterProvider } from './providers/linterProvider'; +import { PythonRenameProvider } from './providers/renameProvider'; import { ReplProvider } from './providers/replProvider'; +import { activateSimplePythonRefactorProvider } from './providers/simpleRefactorProvider'; import { TerminalProvider } from './providers/terminalProvider'; import { activateUpdateSparkLibraryProvider } from './providers/updateSparkLibraryProvider'; import * as sortImports from './sortImports'; @@ -75,10 +77,13 @@ export async function activate(context: ExtensionContext) { const configuration = serviceManager.get(IConfigurationService); const pythonSettings = configuration.getSettings(); + const standardOutputChannel = serviceManager.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); + context.subscriptions.push(languages.registerRenameProvider(PYTHON, new PythonRenameProvider(serviceManager))); + activateSimplePythonRefactorProvider(context, standardOutputChannel, serviceManager); + const activationService = serviceContainer.get(IExtensionActivationService); await activationService.activate(); - const standardOutputChannel = serviceManager.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); sortImports.activate(context, standardOutputChannel, serviceManager); serviceManager.get(ICodeExecutionManager).registerCommands(); From b1bd41867fbc242d625c59031346b0ed5eb2ab6f Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Fri, 1 Jun 2018 16:09:28 -0700 Subject: [PATCH 278/433] Improve detection if '=' is in function or lambda arguments (#1824) * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * #1096 The if statement is automatically formatted incorrectly * Merge fix * Add more tests * More tests * Typo * Test * Also better handle multiline arguments * Add a couple missing periods [skip ci] * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * Merge fix * #1257 On type formatting errors for args and kwargs * Handle f-strings * Stop importing from test code * #1308 Single line statements leading to an indentation on the next line * #726 editing python after inline if statement invalid indent * Undo change * Move constant * Harden LS startup error checks * #1364 Intellisense doesn't work after specific const string * Telemetry for the analysis enging * PR feedback * Fix typo * Test baseline update * Jedi 0.12 * Priority to goto_defition * News * Replace unzip * Linux flavors + test * Grammar check * Grammar test * Test baselines * Add news * Pin dependency [skip ci] * Specify markdown as preferable format * Improve function argument detection * Specify markdown * Pythia setting * Baseline updates * Baseline update * Improve startup * Handle missing interpreter better * Handle interpreter change * Delete old file * Fix LS startup time reporting * Remove Async suffix from IFileSystem * Remove Pythia * Remove pre-packaged MSIL * Exe name on Unix * Plain linux * Fix casing * Fix message * Update PTVS engine activation steps * Type formatter eats space in from . * fIX CASING * Remove flag * Don't wait for LS * Small test fixes * Update hover baselines * Rename the engine * Formatting 1 * Add support for 'rf' strings * Add two spaces before comment per PEP * Fix @ operator spacing * Handle module and unary ops * Type hints * Fix typo * Trailing comma * Require space after if * underscore numbers * Update list of keywords * Function arguments * Function arguments * PR feedback * Handle lambdas * News --- news/2 Fixes/1796.md | 1 + src/client/formatters/lineFormatter.ts | 157 +++++++++++++----- .../format/extension.lineFormatter.test.ts | 144 +++++++++------- 3 files changed, 202 insertions(+), 100 deletions(-) create mode 100644 news/2 Fixes/1796.md diff --git a/news/2 Fixes/1796.md b/news/2 Fixes/1796.md new file mode 100644 index 000000000000..41751c65168a --- /dev/null +++ b/news/2 Fixes/1796.md @@ -0,0 +1 @@ +`editor.formatOnType` now better handles multiline function arguments diff --git a/src/client/formatters/lineFormatter.ts b/src/client/formatters/lineFormatter.ts index b7a6a13aa29b..4a5142d873ce 100644 --- a/src/client/formatters/lineFormatter.ts +++ b/src/client/formatters/lineFormatter.ts @@ -3,7 +3,7 @@ // tslint:disable-next-line:import-name import Char from 'typescript-char'; -import { TextDocument } from 'vscode'; +import { Position, Range, TextDocument } from 'vscode'; import { BraceCounter } from '../language/braceCounter'; import { TextBuilder } from '../language/textBuilder'; import { TextRangeCollection } from '../language/textRangeCollection'; @@ -255,13 +255,11 @@ export class LineFormatter { // tslint:disable-next-line:cyclomatic-complexity private isEqualsInsideArguments(index: number): boolean { - // Since we don't have complete statement, this is mostly heuristics. - // Therefore the code may not be handling all possible ways of the - // argument list continuation. if (index < 1) { return false; } + // We are looking for IDENT = ? const prev = this.tokens.getItemAt(index - 1); if (prev.type !== TokenType.Identifier) { return false; @@ -271,41 +269,7 @@ export class LineFormatter { return false; // Type hint should have spaces around like foo(x: int = 1) per PEP 8 } - const first = this.tokens.getItemAt(0); - if (first.type === TokenType.Comma) { - return true; // Line starts with commma - } - - const last = this.tokens.getItemAt(this.tokens.count - 1); - if (last.type === TokenType.Comma) { - return true; // Line ends in comma - } - - if (last.type === TokenType.Comment && this.tokens.count > 1 && this.tokens.getItemAt(this.tokens.count - 2).type === TokenType.Comma) { - return true; // Line ends in comma and then comment - } - - if (this.document) { - const prevLine = this.lineNumber > 0 ? this.document.lineAt(this.lineNumber - 1).text : ''; - const prevLineTokens = new Tokenizer().tokenize(prevLine); - if (prevLineTokens.count > 0) { - const lastOnPrevLine = prevLineTokens.getItemAt(prevLineTokens.count - 1); - if (lastOnPrevLine.type === TokenType.Comma) { - return true; // Previous line ends in comma - } - if (lastOnPrevLine.type === TokenType.Comment && prevLineTokens.count > 1 && prevLineTokens.getItemAt(prevLineTokens.count - 2).type === TokenType.Comma) { - return true; // Previous line ends in comma and then comment - } - } - } - - for (let i = 0; i < index; i += 1) { - const t = this.tokens.getItemAt(i); - if (this.isKeyword(t, 'lambda')) { - return true; - } - } - return this.braceCounter.isOpened(TokenType.OpenBrace); + return this.isInsideFunctionArguments(this.tokens.getItemAt(index).start); } private isOpenBraceType(type: TokenType): boolean { @@ -317,6 +281,7 @@ export class LineFormatter { private isBraceType(type: TokenType): boolean { return this.isOpenBraceType(type) || this.isCloseBraceType(type); } + private isMultipleStatements(index: number): boolean { for (let i = index; i >= 0; i -= 1) { if (this.tokens.getItemAt(i).type === TokenType.Semicolon) { @@ -332,4 +297,118 @@ export class LineFormatter { private isKeyword(t: IToken, keyword: string): boolean { return t.type === TokenType.Identifier && t.length === keyword.length && this.text.substr(t.start, t.length) === keyword; } + + // tslint:disable-next-line:cyclomatic-complexity + private isInsideFunctionArguments(position: number): boolean { + if (!this.document) { + return false; // unable to determine + } + + // Walk up until beginning of the document or line with 'def IDENT(' or line ending with : + // IDENT( by itself is not reliable since they can be nested in IDENT(IDENT(a), x=1) + let start = new Position(0, 0); + for (let i = this.lineNumber; i >= 0; i -= 1) { + const line = this.document.lineAt(i); + const lineTokens = new Tokenizer().tokenize(line.text); + if (lineTokens.count === 0) { + continue; + } + // 'def IDENT(' + const first = lineTokens.getItemAt(0); + if (lineTokens.count >= 3 && + first.length === 3 && line.text.substr(first.start, first.length) === 'def' && + lineTokens.getItemAt(1).type === TokenType.Identifier && + lineTokens.getItemAt(2).type === TokenType.OpenBrace) { + start = line.range.start; + break; + } + + if (lineTokens.count > 0 && i < this.lineNumber) { + // One of previous lines ends with : + const last = lineTokens.getItemAt(lineTokens.count - 1); + if (last.type === TokenType.Colon) { + start = this.document.lineAt(i + 1).range.start; + break; + } else if (lineTokens.count > 1) { + const beforeLast = lineTokens.getItemAt(lineTokens.count - 2); + if (beforeLast.type === TokenType.Colon && last.type === TokenType.Comment) { + start = this.document.lineAt(i + 1).range.start; + break; + } + } + } + } + + // Now tokenize from the nearest reasonable point + const currentLine = this.document.lineAt(this.lineNumber); + const text = this.document.getText(new Range(start, currentLine.range.end)); + const tokens = new Tokenizer().tokenize(text); + + // Translate position in the line being formatted to the position in the tokenized block + position = this.document.offsetAt(currentLine.range.start) + position - this.document.offsetAt(start); + + // Walk tokens locating narrowest function signature as in IDENT( | ) + let funcCallStartIndex = -1; + let funcCallEndIndex = -1; + for (let i = 0; i < tokens.count - 1; i += 1) { + const t = tokens.getItemAt(i); + if (t.type === TokenType.Identifier) { + const next = tokens.getItemAt(i + 1); + if (next.type === TokenType.OpenBrace && !this.isKeywordWithSpaceBeforeBrace(text.substr(t.start, t.length))) { + // We are at IDENT(, try and locate the closing brace + let closeBraceIndex = this.findClosingBrace(tokens, i + 1); + // Closing brace is not required in case construct is not yet terminated + closeBraceIndex = closeBraceIndex > 0 ? closeBraceIndex : tokens.count - 1; + // Are we in range? + if (position > next.start && position < tokens.getItemAt(closeBraceIndex).start) { + funcCallStartIndex = i; + funcCallEndIndex = closeBraceIndex; + } + } + } + } + // Did we find anything? + if (funcCallStartIndex < 0) { + // No? See if we are between 'lambda' and ':' + for (let i = 0; i < tokens.count; i += 1) { + const t = tokens.getItemAt(i); + if (t.type === TokenType.Identifier && text.substr(t.start, t.length) === 'lambda') { + if (position < t.start) { + break; // Position is before the nearest 'lambda' + } + let colonIndex = this.findNearestColon(tokens, i + 1); + // Closing : is not required in case construct is not yet terminated + colonIndex = colonIndex > 0 ? colonIndex : tokens.count - 1; + if (position > t.start && position < tokens.getItemAt(colonIndex).start) { + funcCallStartIndex = i; + funcCallEndIndex = colonIndex; + } + } + } + } + return funcCallStartIndex >= 0 && funcCallEndIndex > 0; + } + + private findNearestColon(tokens: ITextRangeCollection, index: number): number { + for (let i = index; i < tokens.count; i += 1) { + if (tokens.getItemAt(i).type === TokenType.Colon) { + return i; + } + } + return -1; + } + + private findClosingBrace(tokens: ITextRangeCollection, index: number): number { + const braceCounter = new BraceCounter(); + for (let i = index; i < tokens.count; i += 1) { + const t = tokens.getItemAt(i); + if (t.type === TokenType.OpenBrace || t.type === TokenType.CloseBrace) { + braceCounter.countBrace(t); + } + if (braceCounter.count === 0) { + return i; + } + } + return -1; + } } diff --git a/src/test/format/extension.lineFormatter.test.ts b/src/test/format/extension.lineFormatter.test.ts index acb86d2c5715..2332aa52d7d8 100644 --- a/src/test/format/extension.lineFormatter.test.ts +++ b/src/test/format/extension.lineFormatter.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import * as fs from 'fs'; import * as path from 'path'; import * as TypeMoq from 'typemoq'; -import { TextDocument, TextLine } from 'vscode'; +import { Position, Range, TextDocument, TextLine } from 'vscode'; import '../../client/common/extensions'; import { LineFormatter } from '../../client/formatters/lineFormatter'; @@ -22,15 +22,10 @@ suite('Formatting - line formatter', () => { testFormatLine('( x +1 )*y/ 3', '(x + 1) * y / 3'); }); test('Braces spacing', () => { - testFormatLine('foo =(0 ,)', 'foo = (0,)'); - }); - test('Function arguments', () => { - testFormatLine('z=foo (0 , x= 1, (3+7) , y , z )', - 'z = foo(0, x=1, (3 + 7), y, z)'); + testFormatMultiline('foo =(0 ,)', 0, 'foo = (0,)'); }); test('Colon regular', () => { - testFormatLine('if x == 4 : print x,y; x,y= y, x', - 'if x == 4: print x, y; x, y = y, x'); + testFormatMultiline('if x == 4 : print x,y; x,y= y, x', 0, 'if x == 4: print x, y; x, y = y, x'); }); test('Colon slices', () => { testFormatLine('x[1: 30]', 'x[1:30]'); @@ -52,14 +47,13 @@ suite('Formatting - line formatter', () => { 'ham[:upper_fn(x):step_fn(x)], ham[::step_fn(x)]'); }); test('Colon in for loop', () => { - testFormatLine('for index in range( len(fruits) ): ', - 'for index in range(len(fruits)):'); + testFormatLine('for index in range( len(fruits) ): ', 'for index in range(len(fruits)):'); }); test('Nested braces', () => { testFormatLine('[ 1 :[2: (x,),y]]{1}', '[1:[2:(x,), y]]{1}'); }); test('Trailing comment', () => { - testFormatLine('x=1 # comment', 'x = 1 # comment'); + testFormatMultiline('x=1 # comment', 0, 'x = 1 # comment'); }); test('Single comment', () => { testFormatLine('# comment', '# comment'); @@ -67,21 +61,6 @@ suite('Formatting - line formatter', () => { test('Comment with leading whitespace', () => { testFormatLine(' # comment', ' # comment'); }); - test('Equals in first argument', () => { - testFormatLine('foo(x =0)', 'foo(x=0)'); - }); - test('Equals in second argument', () => { - testFormatLine('foo(x,y= \"a\",', 'foo(x, y=\"a\",'); - }); - test('Equals in multiline arguments', () => { - testFormatLine2('foo(a,', 'x = 1,y =-2)', 'x=1, y=-2)'); - }); - test('Equals in multiline arguments starting comma', () => { - testFormatLine(',x = 1,y =m)', ', x=1, y=m)'); - }); - test('Equals in multiline arguments ending comma', () => { - testFormatLine('x = 1,y =m,', 'x=1, y=m,'); - }); test('Operators without following space', () => { testFormatLine('foo( *a, ** b, ! c)', 'foo(*a, **b, !c)'); }); @@ -109,13 +88,13 @@ suite('Formatting - line formatter', () => { testFormatLine('lambda * args, :0', 'lambda *args,: 0'); }); test('Comma expression', () => { - testFormatLine('x=1,2,3', 'x = 1, 2, 3'); + testFormatMultiline('x=1,2,3', 0, 'x = 1, 2, 3'); }); test('is exression', () => { testFormatLine('a( (False is 2) is 3)', 'a((False is 2) is 3)'); }); test('Function returning tuple', () => { - testFormatLine('x,y=f(a)', 'x, y = f(a)'); + testFormatMultiline('x,y=f(a)', 0, 'x, y = f(a)'); }); test('from. import A', () => { testFormatLine('from. import A', 'from . import A'); @@ -127,24 +106,24 @@ suite('Formatting - line formatter', () => { testFormatLine('from..x import', 'from ..x import'); }); test('Raw strings', () => { - testFormatLine('z=r""', 'z = r""'); - testFormatLine('z=rf""', 'z = rf""'); - testFormatLine('z=R""', 'z = R""'); - testFormatLine('z=RF""', 'z = RF""'); + testFormatMultiline('z=r""', 0, 'z = r""'); + testFormatMultiline('z=rf""', 0, 'z = rf""'); + testFormatMultiline('z=R""', 0, 'z = R""'); + testFormatMultiline('z=RF""', 0, 'z = RF""'); }); test('Binary @', () => { testFormatLine('a@ b', 'a @ b'); }); test('Unary operators', () => { - testFormatLine('x= - y', 'x = -y'); - testFormatLine('x= + y', 'x = +y'); - testFormatLine('x= ~ y', 'x = ~y'); - testFormatLine('x=-1', 'x = -1'); - testFormatLine('x= +1', 'x = +1'); - testFormatLine('x= ~1 ', 'x = ~1'); + testFormatMultiline('x= - y', 0, 'x = -y'); + testFormatMultiline('x= + y', 0, 'x = +y'); + testFormatMultiline('x= ~ y', 0, 'x = ~y'); + testFormatMultiline('x=-1', 0, 'x = -1'); + testFormatMultiline('x= +1', 0, 'x = +1'); + testFormatMultiline('x= ~1 ', 0, 'x = ~1'); }); test('Equals with type hints', () => { - testFormatLine('def foo(x:int=3,x=100.)', 'def foo(x: int = 3, x=100.)'); + testFormatMultiline('def foo(x:int=3,x=100.)', 0, 'def foo(x: int = 3, x=100.)'); }); test('Trailing comma', () => { testFormatLine('a, =[1]', 'a, = [1]'); @@ -152,15 +131,35 @@ suite('Formatting - line formatter', () => { test('if()', () => { testFormatLine('if(True) :', 'if (True):'); }); + test('lambda arguments', () => { + testFormatMultiline('l4= lambda x =lambda y =lambda z= 1: z: y(): x()', 0, 'l4 = lambda x=lambda y=lambda z=1: z: y(): x()'); + }); + + test('Multiline function call', () => { + testFormatMultiline('def foo(x = 1)', 0, 'def foo(x=1)'); + testFormatMultiline('def foo(a\n, x = 1)', 1, ', x=1)'); + testFormatMultiline('foo(a ,b,\n x = 1)', 1, ' x=1)'); + testFormatMultiline('if True:\n if False:\n foo(a , bar(\n x = 1)', 3, ' x=1)'); + testFormatMultiline('z=foo (0 , x= 1, (3+7) , y , z )', 0, 'z = foo(0, x=1, (3 + 7), y, z)'); + testFormatMultiline('foo (0,\n x= 1,', 1, ' x=1,'); + testFormatMultiline( +// tslint:disable-next-line:no-multiline-string +`async def fetch(): + async with aiohttp.ClientSession() as session: + async with session.ws_connect( + "http://127.0.0.1:8000/", headers = cookie) as ws: # add unwanted spaces`, 3, + ' "http://127.0.0.1:8000/", headers=cookie) as ws: # add unwanted spaces'); + testFormatMultiline('def pos0key1(*, key): return key\npos0key1(key= 100)', 1, 'pos0key1(key=100)'); + testFormatMultiline('def test_string_literals(self):\n x= 1; y =2; self.assertTrue(len(x) == 0 and x == y)', 1, + ' x = 1; y = 2; self.assertTrue(len(x) == 0 and x == y)'); + }); test('Grammar file', () => { const content = fs.readFileSync(grammarFile).toString('utf8'); const lines = content.splitLines({ trim: false, removeEmptyEntries: false }); - let prevLine = ''; for (let i = 0; i < lines.length; i += 1) { const line = lines[i]; - const actual = formatLine2(prevLine, line); + const actual = formatMultiline(content, i); assert.equal(actual, line, `Line ${i + 1} changed: '${line.trim()}' to '${actual.trim()}'`); - prevLine = line; } }); @@ -169,32 +168,55 @@ suite('Formatting - line formatter', () => { assert.equal(actual, expected); } - function formatLine(text: string): string { - const line = TypeMoq.Mock.ofType(); - line.setup(x => x.text).returns(() => text); + function testFormatMultiline(content: string, lineNumber: number, expected: string): void { + const actual = formatMultiline(content, lineNumber); + assert.equal(actual, expected); + } + + function formatMultiline(content: string, lineNumber: number): string { + const lines = content.splitLines({ trim: false, removeEmptyEntries: false }); const document = TypeMoq.Mock.ofType(); - document.setup(x => x.lineAt(TypeMoq.It.isAnyNumber())).returns(() => line.object); + document.setup(x => x.lineAt(TypeMoq.It.isAnyNumber())).returns(n => { + const line = TypeMoq.Mock.ofType(); + line.setup(x => x.text).returns(() => lines[n]); + line.setup(x => x.range).returns(() => new Range(new Position(n, 0), new Position(n, lines[n].length))); + return line.object; + }); + document.setup(x => x.getText(TypeMoq.It.isAny())).returns(o => { + const r = o as Range; + const bits: string[] = []; - return formatter.formatLine(document.object, 0); - } + if (r.start.line === r.end.line) { + return lines[r.start.line].substring(r.start.character, r.end.character); + } - function formatLine2(prevLineText: string, lineText: string): string { - const thisLine = TypeMoq.Mock.ofType(); - thisLine.setup(x => x.text).returns(() => lineText); + bits.push(lines[r.start.line].substr(r.start.character)); + for (let i = r.start.line + 1; i < r.end.line; i += 1) { + bits.push(lines[i]); + } + bits.push(lines[r.end.line].substring(0, r.end.character)); + return bits.join('\n'); + }); + document.setup(x => x.offsetAt(TypeMoq.It.isAny())).returns(o => { + const p = o as Position; + let offset = 0; + for (let i = 0; i < p.line; i += 1) { + offset += lines[i].length + 1; // Accounting for the line break + } + return offset + p.character; + }); - const prevLine = TypeMoq.Mock.ofType(); - prevLine.setup(x => x.text).returns(() => prevLineText); + return formatter.formatLine(document.object, lineNumber); + } - const document = TypeMoq.Mock.ofType(); - document.setup(x => x.lineAt(0)).returns(() => prevLine.object); - document.setup(x => x.lineAt(1)).returns(() => thisLine.object); + function formatLine(text: string): string { + const line = TypeMoq.Mock.ofType(); + line.setup(x => x.text).returns(() => text); - return formatter.formatLine(document.object, 1); - } + const document = TypeMoq.Mock.ofType(); + document.setup(x => x.lineAt(TypeMoq.It.isAnyNumber())).returns(() => line.object); - function testFormatLine2(prevLineText: string, lineText: string, expected: string): void { - const actual = formatLine2(prevLineText, lineText); - assert.equal(actual, expected); + return formatter.formatLine(document.object, 0); } }); From 63cdd43aa05721559b8aac05bfb3af14bfba696d Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Sat, 2 Jun 2018 05:32:44 +0200 Subject: [PATCH 279/433] Fix arguments passed into announce.py (#1835) --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index fe6190e5977d..2e61eb19954d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -113,7 +113,7 @@ script: fi - if [ "$TRAVIS_PYTHON_VERSION" != "2.7" ]; then python3 -m pip install --upgrade -r news/requirements.txt; - python3 news/announce.py --dry-run; + python3 news/announce.py --dry_run; fi - if [[ $AZURE_STORAGE_ACCOUNT && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_PULL_REQUEST" == "false" ]]; then yarn run clean; From 34f2645ce33b5483e56e93bbd6616900fc338889 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 4 Jun 2018 19:32:36 +0200 Subject: [PATCH 280/433] Disable folding provider (#1855) --- src/client/extension.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/client/extension.ts b/src/client/extension.ts index 44ad9ee776e2..304b7419fd87 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -40,7 +40,6 @@ import { IServiceContainer, IServiceManager } from './ioc/types'; import { LinterCommands } from './linters/linterCommands'; import { registerTypes as lintersRegisterTypes } from './linters/serviceRegistry'; import { ILintingEngine } from './linters/types'; -import { DocStringFoldingProvider } from './providers/docStringFoldingProvider'; import { PythonFormattingEditProvider } from './providers/formatProvider'; import { LinterProvider } from './providers/linterProvider'; import { PythonRenameProvider } from './providers/renameProvider'; @@ -137,7 +136,6 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(languages.registerOnTypeFormattingEditProvider(PYTHON, new BlockFormatProviders(), ':')); context.subscriptions.push(languages.registerOnTypeFormattingEditProvider(PYTHON, new OnEnterFormatter(), '\n')); - context.subscriptions.push(languages.registerFoldingRangeProvider(PYTHON, new DocStringFoldingProvider())); const persistentStateFactory = serviceManager.get(IPersistentStateFactory); const deprecationMgr = new FeatureDeprecationManager(persistentStateFactory, !!jupyterExtension); From a78c4656530a393323364dd9f8ea9d9614f7d9ee Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 4 Jun 2018 23:18:51 +0200 Subject: [PATCH 281/433] Fix indentation when function contains type hints (#1814) --- news/2 Fixes/1461.md | 1 + src/client/extension.ts | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) create mode 100644 news/2 Fixes/1461.md diff --git a/news/2 Fixes/1461.md b/news/2 Fixes/1461.md new file mode 100644 index 000000000000..1c8c770456a9 --- /dev/null +++ b/news/2 Fixes/1461.md @@ -0,0 +1 @@ +Fix indentation when function contains type hints. diff --git a/src/client/extension.ts b/src/client/extension.ts index 304b7419fd87..eb405f591159 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -107,10 +107,6 @@ export async function activate(context: ExtensionContext) { // tslint:disable-next-line:no-non-null-assertion languages.setLanguageConfiguration(PYTHON_LANGUAGE, { onEnterRules: [ - { - beforeText: /^\s*(?:def|class|for|if|elif|else|while|try|with|finally|except)\b.*:\s*\S+/, - action: { indentAction: IndentAction.None } - }, { beforeText: /^\s*(?:def|class|for|if|elif|else|while|try|with|finally|except|async)\b.*:\s*/, action: { indentAction: IndentAction.Indent } From fabd92ae19ec517ce36e63bbe7d6b35268954915 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 4 Jun 2018 23:54:41 +0200 Subject: [PATCH 282/433] Fix formatter unit tests (#1839) --- src/test/format/extension.onEnterFormat.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/format/extension.onEnterFormat.test.ts b/src/test/format/extension.onEnterFormat.test.ts index 8f594d5e2559..c6560833edaf 100644 --- a/src/test/format/extension.onEnterFormat.test.ts +++ b/src/test/format/extension.onEnterFormat.test.ts @@ -46,7 +46,7 @@ suite('Formatting - OnEnter provider', () => { test('Formatting line ending in comment', async () => { const text = await formatAtPosition(6, 0); - assert.equal(text, 'x + 1 # ', 'Line ending in comment was not formatted'); + assert.equal(text, 'x + 1 # ', 'Line ending in comment was not formatted'); }); test('Formatting line with @', async () => { @@ -76,7 +76,7 @@ suite('Formatting - OnEnter provider', () => { test('Formatting space after open brace', async () => { const text = await formatAtPosition(12, 0); - assert.equal(text, 'while(1)', 'Space after open brace was not formatted'); + assert.equal(text, 'while (1)', 'Space after open brace was not formatted'); }); test('Formatting line ending in string', async () => { From 07361f584f2cdf5fd48f918737a25804002fcef7 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 5 Jun 2018 00:04:23 +0200 Subject: [PATCH 283/433] Fix failing Prospector unit tests and add more tests for linters (#1837) * Fix failing Prospector unit tests * Add more tests for relative prospector paths * Udpated readme --- news/3 Code Health/1836.md | 1 + src/test/common/moduleInstaller.test.ts | 2 +- src/test/linters/lint.args.test.ts | 249 ++++++++++++------------ 3 files changed, 129 insertions(+), 123 deletions(-) create mode 100644 news/3 Code Health/1836.md diff --git a/news/3 Code Health/1836.md b/news/3 Code Health/1836.md new file mode 100644 index 000000000000..6edcdecce306 --- /dev/null +++ b/news/3 Code Health/1836.md @@ -0,0 +1 @@ +Fix failing Prospector unit tests and add more tests for linters (with and without workspaces). diff --git a/src/test/common/moduleInstaller.test.ts b/src/test/common/moduleInstaller.test.ts index 27b57e2ed519..291cc6d3f534 100644 --- a/src/test/common/moduleInstaller.test.ts +++ b/src/test/common/moduleInstaller.test.ts @@ -42,7 +42,7 @@ const info: PythonInterpreter = { sysVersion: '' }; -suite('Module Installerx', () => { +suite('Module Installer', () => { [undefined, Uri.file(__filename)].forEach(resource => { let ioc: UnitTestIocContainer; let mockTerminalService: TypeMoq.IMock; diff --git a/src/test/linters/lint.args.test.ts b/src/test/linters/lint.args.test.ts index 4475ef2d94bf..98a255bf5415 100644 --- a/src/test/linters/lint.args.test.ts +++ b/src/test/linters/lint.args.test.ts @@ -3,13 +3,13 @@ 'use strict'; -// tslint:disable:no-any +// tslint:disable:no-any max-func-body-length import { expect } from 'chai'; import { Container } from 'inversify'; import * as path from 'path'; import * as TypeMoq from 'typemoq'; -import { CancellationTokenSource, OutputChannel, TextDocument, Uri } from 'vscode'; +import { CancellationTokenSource, OutputChannel, TextDocument, Uri, WorkspaceFolder } from 'vscode'; import { IDocumentManager, IWorkspaceService } from '../../client/common/application/types'; import '../../client/common/extensions'; import { IFileSystem, IPlatformService } from '../../client/common/platform/types'; @@ -29,127 +29,132 @@ import { Pylint } from '../../client/linters/pylint'; import { ILinterManager, ILintingEngine } from '../../client/linters/types'; import { initialize } from '../initialize'; -// tslint:disable-next-line:max-func-body-length suite('Linting - Arguments', () => { - let interpreterService: TypeMoq.IMock; - let engine: TypeMoq.IMock; - let configService: TypeMoq.IMock; - let docManager: TypeMoq.IMock; - let settings: TypeMoq.IMock; - let lm: ILinterManager; - let serviceContainer: ServiceContainer; - let document: TypeMoq.IMock; - let outputChannel: TypeMoq.IMock; - let workspaceService: TypeMoq.IMock; - const cancellationToken = new CancellationTokenSource().token; - - suiteSetup(initialize); - setup(async () => { - const cont = new Container(); - const serviceManager = new ServiceManager(cont); - - serviceContainer = new ServiceContainer(cont); - outputChannel = TypeMoq.Mock.ofType(); - - const fs = TypeMoq.Mock.ofType(); - fs.setup(x => x.fileExists(TypeMoq.It.isAny())).returns(() => new Promise((resolve, reject) => resolve(true))); - fs.setup(x => x.arePathsSame(TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString())).returns(() => true); - serviceManager.addSingletonInstance(IFileSystem, fs.object); - - serviceManager.addSingletonInstance(IOutputChannel, outputChannel.object); - - interpreterService = TypeMoq.Mock.ofType(); - serviceManager.addSingletonInstance(IInterpreterService, interpreterService.object); - - engine = TypeMoq.Mock.ofType(); - serviceManager.addSingletonInstance(ILintingEngine, engine.object); - - docManager = TypeMoq.Mock.ofType(); - serviceManager.addSingletonInstance(IDocumentManager, docManager.object); - - const lintSettings = TypeMoq.Mock.ofType(); - lintSettings.setup(x => x.enabled).returns(() => true); - lintSettings.setup(x => x.lintOnSave).returns(() => true); - - settings = TypeMoq.Mock.ofType(); - settings.setup(x => x.linting).returns(() => lintSettings.object); - - configService = TypeMoq.Mock.ofType(); - configService.setup(x => x.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); - serviceManager.addSingletonInstance(IConfigurationService, configService.object); - - workspaceService = TypeMoq.Mock.ofType(); - serviceManager.addSingletonInstance(IWorkspaceService, workspaceService.object); - - const logger = TypeMoq.Mock.ofType(); - serviceManager.addSingletonInstance(ILogger, logger.object); - - const installer = TypeMoq.Mock.ofType(); - serviceManager.addSingletonInstance(IInstaller, installer.object); - - const platformService = TypeMoq.Mock.ofType(); - serviceManager.addSingletonInstance(IPlatformService, platformService.object); - - lm = new LinterManager(serviceContainer); - serviceManager.addSingletonInstance(ILinterManager, lm); - document = TypeMoq.Mock.ofType(); - }); - - async function testLinter(linter: BaseLinter, fileUri: Uri, expectedArgs: string[]) { - document.setup(d => d.uri).returns(() => fileUri); - - let invoked = false; - (linter as any).run = (args, doc, token) => { - expect(args).to.deep.equal(expectedArgs); - invoked = true; - return Promise.resolve([]); - }; - await linter.lint(document.object, cancellationToken); - expect(invoked).to.be.equal(true, 'method not invoked'); - } - [Uri.file(path.join('users', 'development path to', 'one.py')), Uri.file(path.join('users', 'development', 'one.py'))].forEach(fileUri => { - test(`Flake8 (${fileUri.fsPath.indexOf(' ') > 0 ? 'with spaces' : 'without spaces'})`, async () => { - const linter = new Flake8(outputChannel.object, serviceContainer); - const expectedArgs = ['--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', fileUri.fsPath]; - await testLinter(linter, fileUri, expectedArgs); - }); - test(`Pep8 (${fileUri.fsPath.indexOf(' ') > 0 ? 'with spaces' : 'without spaces'})`, async () => { - const linter = new Pep8(outputChannel.object, serviceContainer); - const expectedArgs = ['--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', fileUri.fsPath]; - await testLinter(linter, fileUri, expectedArgs); - }); - test(`Prospector (${fileUri.fsPath.indexOf(' ') > 0 ? 'with spaces' : 'without spaces'})`, async () => { - const linter = new Prospector(outputChannel.object, serviceContainer); - const expectedArgs = ['--absolute-paths', '--output-format=json', fileUri.fsPath]; - await testLinter(linter, fileUri, expectedArgs); - }); - test(`Pylama (${fileUri.fsPath.indexOf(' ') > 0 ? 'with spaces' : 'without spaces'})`, async () => { - const linter = new PyLama(outputChannel.object, serviceContainer); - const expectedArgs = ['--format=parsable', fileUri.fsPath]; - await testLinter(linter, fileUri, expectedArgs); - }); - test(`MyPy (${fileUri.fsPath.indexOf(' ') > 0 ? 'with spaces' : 'without spaces'})`, async () => { - const linter = new MyPy(outputChannel.object, serviceContainer); - const expectedArgs = [fileUri.fsPath]; - await testLinter(linter, fileUri, expectedArgs); - }); - test(`Pydocstyle (${fileUri.fsPath.indexOf(' ') > 0 ? 'with spaces' : 'without spaces'})`, async () => { - const linter = new PyDocStyle(outputChannel.object, serviceContainer); - const expectedArgs = [fileUri.fsPath]; - await testLinter(linter, fileUri, expectedArgs); - }); - test(`Pylint (${fileUri.fsPath.indexOf(' ') > 0 ? 'with spaces' : 'without spaces'})`, async () => { - const linter = new Pylint(outputChannel.object, serviceContainer); - document.setup(d => d.uri).returns(() => fileUri); - - let invoked = false; - (linter as any).run = (args, doc, token) => { - expect(args[args.length - 1]).to.equal(fileUri.fsPath); - invoked = true; - return Promise.resolve([]); - }; - await linter.lint(document.object, cancellationToken); - expect(invoked).to.be.equal(true, 'method not invoked'); + [undefined, path.join('users', 'dev_user')].forEach(workspaceUri => { + [Uri.file(path.join('users', 'dev_user', 'development path to', 'one.py')), Uri.file(path.join('users', 'dev_user', 'development', 'one.py'))].forEach(fileUri => { + suite(`File path ${fileUri.fsPath.indexOf(' ') > 0 ? 'with' : 'without'} spaces and ${workspaceUri ? 'without' : 'with'} a workspace`, () => { + let interpreterService: TypeMoq.IMock; + let engine: TypeMoq.IMock; + let configService: TypeMoq.IMock; + let docManager: TypeMoq.IMock; + let settings: TypeMoq.IMock; + let lm: ILinterManager; + let serviceContainer: ServiceContainer; + let document: TypeMoq.IMock; + let outputChannel: TypeMoq.IMock; + let workspaceService: TypeMoq.IMock; + const cancellationToken = new CancellationTokenSource().token; + suiteSetup(initialize); + setup(async () => { + const cont = new Container(); + const serviceManager = new ServiceManager(cont); + + serviceContainer = new ServiceContainer(cont); + outputChannel = TypeMoq.Mock.ofType(); + + const fs = TypeMoq.Mock.ofType(); + fs.setup(x => x.fileExists(TypeMoq.It.isAny())).returns(() => new Promise((resolve, reject) => resolve(true))); + fs.setup(x => x.arePathsSame(TypeMoq.It.isAnyString(), TypeMoq.It.isAnyString())).returns(() => true); + serviceManager.addSingletonInstance(IFileSystem, fs.object); + + serviceManager.addSingletonInstance(IOutputChannel, outputChannel.object); + + interpreterService = TypeMoq.Mock.ofType(); + serviceManager.addSingletonInstance(IInterpreterService, interpreterService.object); + + engine = TypeMoq.Mock.ofType(); + serviceManager.addSingletonInstance(ILintingEngine, engine.object); + + docManager = TypeMoq.Mock.ofType(); + serviceManager.addSingletonInstance(IDocumentManager, docManager.object); + + const lintSettings = TypeMoq.Mock.ofType(); + lintSettings.setup(x => x.enabled).returns(() => true); + lintSettings.setup(x => x.lintOnSave).returns(() => true); + + settings = TypeMoq.Mock.ofType(); + settings.setup(x => x.linting).returns(() => lintSettings.object); + + configService = TypeMoq.Mock.ofType(); + configService.setup(x => x.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); + serviceManager.addSingletonInstance(IConfigurationService, configService.object); + + const workspaceFolder: WorkspaceFolder | undefined = workspaceUri ? { uri: Uri.file(workspaceUri), index: 0, name: '' } : undefined; + workspaceService = TypeMoq.Mock.ofType(); + workspaceService.setup(w => w.getWorkspaceFolder(TypeMoq.It.isAny())).returns(() => workspaceFolder); + serviceManager.addSingletonInstance(IWorkspaceService, workspaceService.object); + + const logger = TypeMoq.Mock.ofType(); + serviceManager.addSingletonInstance(ILogger, logger.object); + + const installer = TypeMoq.Mock.ofType(); + serviceManager.addSingletonInstance(IInstaller, installer.object); + + const platformService = TypeMoq.Mock.ofType(); + serviceManager.addSingletonInstance(IPlatformService, platformService.object); + + lm = new LinterManager(serviceContainer); + serviceManager.addSingletonInstance(ILinterManager, lm); + document = TypeMoq.Mock.ofType(); + }); + + async function testLinter(linter: BaseLinter, expectedArgs: string[]) { + document.setup(d => d.uri).returns(() => fileUri); + + let invoked = false; + (linter as any).run = (args, doc, token) => { + expect(args).to.deep.equal(expectedArgs); + invoked = true; + return Promise.resolve([]); + }; + await linter.lint(document.object, cancellationToken); + expect(invoked).to.be.equal(true, 'method not invoked'); + } + test('Flake8', async () => { + const linter = new Flake8(outputChannel.object, serviceContainer); + const expectedArgs = ['--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', fileUri.fsPath]; + await testLinter(linter, expectedArgs); + }); + test('Pep8', async () => { + const linter = new Pep8(outputChannel.object, serviceContainer); + const expectedArgs = ['--format=%(row)d,%(col)d,%(code).1s,%(code)s:%(text)s', fileUri.fsPath]; + await testLinter(linter, expectedArgs); + }); + test('Prospector', async () => { + const linter = new Prospector(outputChannel.object, serviceContainer); + const expectedPath = workspaceUri ? path.relative(workspaceUri, fileUri.fsPath) : path.basename(fileUri.fsPath); + const expectedArgs = ['--absolute-paths', '--output-format=json', expectedPath]; + await testLinter(linter, expectedArgs); + }); + test('Pylama', async () => { + const linter = new PyLama(outputChannel.object, serviceContainer); + const expectedArgs = ['--format=parsable', fileUri.fsPath]; + await testLinter(linter, expectedArgs); + }); + test('MyPy', async () => { + const linter = new MyPy(outputChannel.object, serviceContainer); + const expectedArgs = [fileUri.fsPath]; + await testLinter(linter, expectedArgs); + }); + test('Pydocstyle', async () => { + const linter = new PyDocStyle(outputChannel.object, serviceContainer); + const expectedArgs = [fileUri.fsPath]; + await testLinter(linter, expectedArgs); + }); + test('Pylint', async () => { + const linter = new Pylint(outputChannel.object, serviceContainer); + document.setup(d => d.uri).returns(() => fileUri); + + let invoked = false; + (linter as any).run = (args, doc, token) => { + expect(args[args.length - 1]).to.equal(fileUri.fsPath); + invoked = true; + return Promise.resolve([]); + }; + await linter.lint(document.object, cancellationToken); + expect(invoked).to.be.equal(true, 'method not invoked'); + }); + }); }); }); }); From 696ac55b9683a0cf90ca33ce39a72f5bb1058869 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 5 Jun 2018 00:45:52 +0200 Subject: [PATCH 284/433] Fix failing prospector path on Windows (corss plat fix for failing test) (#1862) --- src/test/linters/lint.args.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/linters/lint.args.test.ts b/src/test/linters/lint.args.test.ts index 98a255bf5415..58421ab1f9bc 100644 --- a/src/test/linters/lint.args.test.ts +++ b/src/test/linters/lint.args.test.ts @@ -122,7 +122,7 @@ suite('Linting - Arguments', () => { }); test('Prospector', async () => { const linter = new Prospector(outputChannel.object, serviceContainer); - const expectedPath = workspaceUri ? path.relative(workspaceUri, fileUri.fsPath) : path.basename(fileUri.fsPath); + const expectedPath = workspaceUri ? fileUri.fsPath.substring(workspaceUri.length + 2) : path.basename(fileUri.fsPath); const expectedArgs = ['--absolute-paths', '--output-format=json', expectedPath]; await testLinter(linter, expectedArgs); }); From 548e35f51ee83ec1bf292431ed29159b4426299c Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 5 Jun 2018 09:13:22 -0700 Subject: [PATCH 285/433] Ensure `Outline` view does not overload the symbol provider (#1858) * Ensure `Outline` view does not overload the symbol provider * Add more tests --- news/3 Code Health/1856.md | 1 + package.json | 2 + src/client/providers/symbolProvider.ts | 81 +++++++++----- src/test/providers/symbolProvider.test.ts | 129 ++++++++++++++++++++++ yarn.lock | 10 ++ 5 files changed, 198 insertions(+), 25 deletions(-) create mode 100644 news/3 Code Health/1856.md create mode 100644 src/test/providers/symbolProvider.test.ts diff --git a/news/3 Code Health/1856.md b/news/3 Code Health/1856.md new file mode 100644 index 000000000000..61e0b4966073 --- /dev/null +++ b/news/3 Code Health/1856.md @@ -0,0 +1 @@ +Ensure `Outline` view doesn't overload the language server with too many requets, while user is editing text in the editor. diff --git a/package.json b/package.json index 177c346d6c3a..48076a3751a7 100644 --- a/package.json +++ b/package.json @@ -1899,6 +1899,7 @@ }, "devDependencies": { "@types/chai": "^4.1.2", + "@types/chai-arrays": "^1.0.2", "@types/chai-as-promised": "^7.1.0", "@types/del": "^3.0.0", "@types/event-stream": "^3.3.33", @@ -1921,6 +1922,7 @@ "JSONStream": "^1.3.2", "azure-storage": "^2.8.1", "chai": "^4.1.2", + "chai-arrays": "^2.0.0", "chai-as-promised": "^7.1.1", "codecov": "^3.0.0", "colors": "^1.2.1", diff --git a/src/client/providers/symbolProvider.ts b/src/client/providers/symbolProvider.ts index 9c296edda3df..a11dca6e427d 100644 --- a/src/client/providers/symbolProvider.ts +++ b/src/client/providers/symbolProvider.ts @@ -1,48 +1,80 @@ 'use strict'; -import * as vscode from 'vscode'; +import { CancellationToken, DocumentSymbolProvider, Location, Range, SymbolInformation, TextDocument, Uri } from 'vscode'; +import { createDeferred, Deferred } from '../common/helpers'; import { JediFactory } from '../languageServices/jediProxyFactory'; import { captureTelemetry } from '../telemetry'; import { SYMBOL } from '../telemetry/constants'; import * as proxy from './jediProxy'; -export class PythonSymbolProvider implements vscode.DocumentSymbolProvider { - public constructor(private jediFactory: JediFactory) { } - private static parseData(document: vscode.TextDocument, data: proxy.ISymbolResult): vscode.SymbolInformation[] { +export class PythonSymbolProvider implements DocumentSymbolProvider { + private debounceRequest: Map }>; + public constructor(private jediFactory: JediFactory, private readonly debounceTimeoutMs = 500) { + this.debounceRequest = new Map }>(); + } + private static parseData(document: TextDocument, data?: proxy.ISymbolResult): SymbolInformation[] { if (data) { const symbols = data.definitions.filter(sym => sym.fileName === document.fileName); return symbols.map(sym => { const symbol = sym.kind; - const range = new vscode.Range( + const range = new Range( sym.range.startLine, sym.range.startColumn, sym.range.endLine, sym.range.endColumn); - const uri = vscode.Uri.file(sym.fileName); - const location = new vscode.Location(uri, range); - return new vscode.SymbolInformation(sym.text, symbol, sym.container, location); + const uri = Uri.file(sym.fileName); + const location = new Location(uri, range); + return new SymbolInformation(sym.text, symbol, sym.container, location); }); } return []; } @captureTelemetry(SYMBOL) - public provideDocumentSymbols(document: vscode.TextDocument, token: vscode.CancellationToken): Thenable { - const filename = document.fileName; + public provideDocumentSymbols(document: TextDocument, token: CancellationToken): Thenable { + const key = `${document.uri.fsPath}`; + if (this.debounceRequest.has(key)) { + const item = this.debounceRequest.get(key)!; + clearTimeout(item.timer); + item.deferred.resolve([]); + } - const cmd: proxy.ICommand = { - command: proxy.CommandType.Symbols, - fileName: filename, - columnIndex: 0, - lineIndex: 0 - }; + const deferred = createDeferred(); + const timer = setTimeout(() => { + if (token.isCancellationRequested) { + return deferred.resolve([]); + } - if (document.isDirty) { - cmd.source = document.getText(); - } + const filename = document.fileName; + const cmd: proxy.ICommand = { + command: proxy.CommandType.Symbols, + fileName: filename, + columnIndex: 0, + lineIndex: 0 + }; + + if (document.isDirty) { + cmd.source = document.getText(); + } + + this.jediFactory.getJediProxyHandler(document.uri).sendCommand(cmd, token) + .then(data => PythonSymbolProvider.parseData(document, data)) + .then(items => deferred.resolve(items)) + .catch(ex => deferred.reject(ex)); + + }, this.debounceTimeoutMs); - return this.jediFactory.getJediProxyHandler(document.uri).sendCommand(cmd, token).then(data => { - return PythonSymbolProvider.parseData(document, data); + token.onCancellationRequested(() => { + clearTimeout(timer); + deferred.resolve([]); + this.debounceRequest.delete(key); }); + + // When a document is not saved on FS, we cannot uniquely identify it, so lets not debounce, but delay the symbol provider. + if (!document.isUntitled) { + this.debounceRequest.set(key, { timer, deferred }); + } + + return deferred.promise; } - public provideDocumentSymbolsForInternalUse(document: vscode.TextDocument, token: vscode.CancellationToken): Thenable { + public provideDocumentSymbolsForInternalUse(document: TextDocument, token: CancellationToken): Thenable { const filename = document.fileName; const cmd: proxy.ICommand = { @@ -56,8 +88,7 @@ export class PythonSymbolProvider implements vscode.DocumentSymbolProvider { cmd.source = document.getText(); } - return this.jediFactory.getJediProxyHandler(document.uri).sendCommandNonCancellableCommand(cmd, token).then(data => { - return PythonSymbolProvider.parseData(document, data); - }); + return this.jediFactory.getJediProxyHandler(document.uri).sendCommandNonCancellableCommand(cmd, token) + .then(data => PythonSymbolProvider.parseData(document, data)); } } diff --git a/src/test/providers/symbolProvider.test.ts b/src/test/providers/symbolProvider.test.ts new file mode 100644 index 000000000000..a518cd7ee508 --- /dev/null +++ b/src/test/providers/symbolProvider.test.ts @@ -0,0 +1,129 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:max-func-body-length no-any no-require-imports no-var-requires + +import { expect, use } from 'chai'; +import * as TypeMoq from 'typemoq'; +import { CancellationToken, CancellationTokenSource, CompletionItemKind, DocumentSymbolProvider, SymbolKind, TextDocument, Uri } from 'vscode'; +import { JediFactory } from '../../client/languageServices/jediProxyFactory'; +import { IDefinition, ISymbolResult, JediProxyHandler } from '../../client/providers/jediProxy'; +import { PythonSymbolProvider } from '../../client/providers/symbolProvider'; +const assertArrays = require('chai-arrays'); +use(assertArrays); + +suite('Symbol Provider', () => { + let symbolProvider: DocumentSymbolProvider; + let jediHandler: TypeMoq.IMock>; + let jediFactory: TypeMoq.IMock; + setup(() => { + jediFactory = TypeMoq.Mock.ofType(JediFactory); + jediHandler = TypeMoq.Mock.ofType>(); + + jediFactory.setup(j => j.getJediProxyHandler(TypeMoq.It.isAny())) + .returns(() => jediHandler.object); + }); + + async function testDocumentation(requestId: number, fileName: string, expectedSize: number, token?: CancellationToken, isUntitled = false) { + const doc = TypeMoq.Mock.ofType(); + token = token ? token : new CancellationTokenSource().token; + const symbolResult = TypeMoq.Mock.ofType(); + + const definitions: IDefinition[] = [ + { + container: '', fileName: fileName, kind: SymbolKind.Array, + range: { endColumn: 0, endLine: 0, startColumn: 0, startLine: 0 }, + rawType: '', text: '', type: CompletionItemKind.Class + } + ]; + + doc.setup(d => d.fileName).returns(() => fileName); + doc.setup(d => d.isUntitled).returns(() => isUntitled); + doc.setup(d => d.uri).returns(() => Uri.file(fileName)); + doc.setup(d => d.getText(TypeMoq.It.isAny())).returns(() => ''); + symbolResult.setup(c => c.requestId).returns(() => requestId); + symbolResult.setup(c => c.definitions).returns(() => definitions); + symbolResult.setup((c: any) => c.then).returns(() => undefined); + jediHandler.setup(j => j.sendCommand(TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns(() => Promise.resolve(symbolResult.object)); + + const items = await symbolProvider.provideDocumentSymbols(doc.object, token); + expect(items).to.be.array(); + expect(items).to.be.ofSize(expectedSize); + } + + test('Ensure symbols are returned', async () => { + symbolProvider = new PythonSymbolProvider(jediFactory.object, 0); + await testDocumentation(1, __filename, 1); + }); + test('Ensure symbols are returned (for untitled documents)', async () => { + symbolProvider = new PythonSymbolProvider(jediFactory.object, 0); + await testDocumentation(1, __filename, 1, undefined, true); + }); + test('Ensure symbols are returned with a debounce of 100ms', async () => { + symbolProvider = new PythonSymbolProvider(jediFactory.object, 0); + await testDocumentation(1, __filename, 1); + }); + test('Ensure symbols are returned with a debounce of 100ms (for untitled documents)', async () => { + symbolProvider = new PythonSymbolProvider(jediFactory.object, 0); + await testDocumentation(1, __filename, 1, undefined, true); + }); + test('Ensure symbols are not returned when cancelled', async () => { + symbolProvider = new PythonSymbolProvider(jediFactory.object, 0); + const tokenSource = new CancellationTokenSource(); + tokenSource.cancel(); + await testDocumentation(1, __filename, 0, tokenSource.token); + }); + test('Ensure symbols are not returned when cancelled (for untitled documents)', async () => { + symbolProvider = new PythonSymbolProvider(jediFactory.object, 0); + const tokenSource = new CancellationTokenSource(); + tokenSource.cancel(); + await testDocumentation(1, __filename, 0, tokenSource.token, true); + }); + test('Ensure symbols are returned only for the last request', async () => { + symbolProvider = new PythonSymbolProvider(jediFactory.object, 100); + await Promise.all([ + testDocumentation(1, __filename, 0), + testDocumentation(2, __filename, 0), + testDocumentation(3, __filename, 1) + ]); + }); + test('Ensure symbols are returned for all the requests when the doc is untitled', async () => { + symbolProvider = new PythonSymbolProvider(jediFactory.object, 100); + await Promise.all([ + testDocumentation(1, __filename, 1, undefined, true), + testDocumentation(2, __filename, 1, undefined, true), + testDocumentation(3, __filename, 1, undefined, true) + ]); + }); + test('Ensure symbols are returned for multiple documents', async () => { + symbolProvider = new PythonSymbolProvider(jediFactory.object, 0); + await Promise.all([ + testDocumentation(1, 'file1', 1), + testDocumentation(2, 'file2', 1) + ]); + }); + test('Ensure symbols are returned for multiple untitled documents ', async () => { + symbolProvider = new PythonSymbolProvider(jediFactory.object, 0); + await Promise.all([ + testDocumentation(1, 'file1', 1, undefined, true), + testDocumentation(2, 'file2', 1, undefined, true) + ]); + }); + test('Ensure symbols are returned for multiple documents with a debounce of 100ms', async () => { + symbolProvider = new PythonSymbolProvider(jediFactory.object, 100); + await Promise.all([ + testDocumentation(1, 'file1', 1), + testDocumentation(2, 'file2', 1) + ]); + }); + test('Ensure symbols are returned for multiple untitled documents with a debounce of 100ms', async () => { + symbolProvider = new PythonSymbolProvider(jediFactory.object, 100); + await Promise.all([ + testDocumentation(1, 'file1', 1, undefined, true), + testDocumentation(2, 'file2', 1, undefined, true) + ]); + }); +}); diff --git a/yarn.lock b/yarn.lock index 5ea193766243..c12707f97ad3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -25,6 +25,12 @@ dependencies: samsam "1.3.0" +"@types/chai-arrays@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/chai-arrays/-/chai-arrays-1.0.2.tgz#1f89c183c960334c47d9f24105195c4326db0cc7" + dependencies: + "@types/chai" "*" + "@types/chai-as-promised@^7.1.0": version "7.1.0" resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.0.tgz#010b04cde78eacfb6e72bfddb3e58fe23c2e78b9" @@ -608,6 +614,10 @@ center-align@^0.1.1: align-text "^0.1.3" lazy-cache "^1.0.3" +chai-arrays@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chai-arrays/-/chai-arrays-2.0.0.tgz#d95820d1b39dc2e4abaa01f984b7f9123986a7cc" + chai-as-promised@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/chai-as-promised/-/chai-as-promised-7.1.1.tgz#08645d825deb8696ee61725dbf590c012eb00ca0" From 279beb447715036302983228cfcb317ebee246ce Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 5 Jun 2018 11:30:26 -0700 Subject: [PATCH 286/433] Final release preparations (#1866) --- CHANGELOG.md | 36 ++++++++++++++++++++++++++++-------- news/1 Enhancements/1153.md | 2 -- news/1 Enhancements/1474.md | 1 - news/1 Enhancements/1484.md | 1 - news/2 Fixes/1194.md | 1 - news/2 Fixes/1345.md | 1 - news/2 Fixes/1461.md | 1 - news/2 Fixes/1476.md | 1 - news/2 Fixes/1529.md | 1 - news/2 Fixes/1628.md | 1 - news/2 Fixes/1634.md | 1 - news/2 Fixes/1651.md | 1 - news/2 Fixes/1774.md | 1 - news/2 Fixes/1779.md | 1 - news/2 Fixes/1796.md | 1 - news/2 Fixes/180.md | 1 - news/2 Fixes/452.md | 1 - news/2 Fixes/677.md | 1 - news/2 Fixes/695.md | 1 - news/2 Fixes/980.md | 1 - news/3 Code Health/1053.md | 2 -- news/3 Code Health/1068.md | 1 - news/3 Code Health/1109.md | 1 - news/3 Code Health/1280.md | 1 - news/3 Code Health/1339.md | 1 - news/3 Code Health/1410.md | 1 - news/3 Code Health/1416.md | 1 - news/3 Code Health/1465.md | 1 - news/3 Code Health/1503.md | 1 - news/3 Code Health/1551.md | 1 - news/3 Code Health/1552.md | 1 - news/3 Code Health/1569.md | 1 - news/3 Code Health/1582.md | 1 - news/3 Code Health/1604.md | 1 - news/3 Code Health/1623.md | 1 - news/3 Code Health/1640.md | 1 - news/3 Code Health/1682.md | 2 -- news/3 Code Health/1703.md | 1 - news/3 Code Health/1719.md | 1 - news/3 Code Health/1730.md | 1 - news/3 Code Health/1732.md | 1 - news/3 Code Health/1747.md | 1 - news/3 Code Health/1794.md | 1 - news/3 Code Health/1836.md | 1 - news/3 Code Health/1856.md | 1 - package.json | 2 +- 46 files changed, 29 insertions(+), 56 deletions(-) delete mode 100644 news/1 Enhancements/1153.md delete mode 100644 news/1 Enhancements/1474.md delete mode 100644 news/1 Enhancements/1484.md delete mode 100644 news/2 Fixes/1194.md delete mode 100644 news/2 Fixes/1345.md delete mode 100644 news/2 Fixes/1461.md delete mode 100644 news/2 Fixes/1476.md delete mode 100644 news/2 Fixes/1529.md delete mode 100644 news/2 Fixes/1628.md delete mode 100644 news/2 Fixes/1634.md delete mode 100644 news/2 Fixes/1651.md delete mode 100644 news/2 Fixes/1774.md delete mode 100644 news/2 Fixes/1779.md delete mode 100644 news/2 Fixes/1796.md delete mode 100644 news/2 Fixes/180.md delete mode 100644 news/2 Fixes/452.md delete mode 100644 news/2 Fixes/677.md delete mode 100644 news/2 Fixes/695.md delete mode 100644 news/2 Fixes/980.md delete mode 100644 news/3 Code Health/1053.md delete mode 100644 news/3 Code Health/1068.md delete mode 100644 news/3 Code Health/1109.md delete mode 100644 news/3 Code Health/1280.md delete mode 100644 news/3 Code Health/1339.md delete mode 100644 news/3 Code Health/1410.md delete mode 100644 news/3 Code Health/1416.md delete mode 100644 news/3 Code Health/1465.md delete mode 100644 news/3 Code Health/1503.md delete mode 100644 news/3 Code Health/1551.md delete mode 100644 news/3 Code Health/1552.md delete mode 100644 news/3 Code Health/1569.md delete mode 100644 news/3 Code Health/1582.md delete mode 100644 news/3 Code Health/1604.md delete mode 100644 news/3 Code Health/1623.md delete mode 100644 news/3 Code Health/1640.md delete mode 100644 news/3 Code Health/1682.md delete mode 100644 news/3 Code Health/1703.md delete mode 100644 news/3 Code Health/1719.md delete mode 100644 news/3 Code Health/1730.md delete mode 100644 news/3 Code Health/1732.md delete mode 100644 news/3 Code Health/1747.md delete mode 100644 news/3 Code Health/1794.md delete mode 100644 news/3 Code Health/1836.md delete mode 100644 news/3 Code Health/1856.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dc7cbbb7cd6..da27f22d7478 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,22 +1,26 @@ # Changelog -## 2018.5.0 (28 May 2018) +## 2018.5.0 (05 Jun 2018) Thanks to the following projects which we fully rely on to provide some of our features: - [isort 4.2.15](https://pypi.org/project/isort/4.2.15/) - [jedi 0.12.0](https://pypi.org/project/jedi/0.12.0/) and [parso 0.2.0](https://pypi.org/project/parso/0.2.0/) -- [ptvsd 3.0.0](https://pypi.org/project/ptvsd/3.0.0/) and [4.1.1a1](https://pypi.org/project/ptvsd/4.1.1a1/) +- [ptvsd 3.0.0](https://pypi.org/project/ptvsd/3.0.0/) and [4.1.1a5](https://pypi.org/project/ptvsd/4.1.1a5/) - [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) - [rope](https://pypi.org/project/rope/) (user-installed) +And thanks to the many other projects which users can optionally choose from +and install to work with the extension. Without them the extension would not be +nearly as feature-rich and useful as it is. + ### Enhancements 1. Add support for the [Black formatter](https://pypi.org/project/black/) (thanks to [Josh Smeaton](https://github.com/jarshwah) for the initial patch) ([#1153](https://github.com/Microsoft/vscode-python/issues/1153)) -1. Add the command 'Discover Unit Tests'. +1. Add the command `Discover Unit Tests`. ([#1474](https://github.com/Microsoft/vscode-python/issues/1474)) 1. Auto detect `*.jinja2` and `*.j2` extensions as Jinja templates, to enable debugging of Jinja templates. ([#1484](https://github.com/Microsoft/vscode-python/issues/1484)) @@ -27,6 +31,8 @@ our features: ([#1194](https://github.com/Microsoft/vscode-python/issues/1194)) 1. Ensure debugged program is terminated when `Stop` debugging button is clicked. ([#1345](https://github.com/Microsoft/vscode-python/issues/1345)) +1. Fix indentation when function contains type hints. + ([#1461](https://github.com/Microsoft/vscode-python/issues/1461)) 1. Ensure python environment activation works as expected within a multi-root workspace. ([#1476](https://github.com/Microsoft/vscode-python/issues/1476)) 1. Close communication channel before exiting the test runner. @@ -37,20 +43,26 @@ our features: ([#1634](https://github.com/Microsoft/vscode-python/issues/1634)) 1. Ensure the display name of an interpreter does not get prefixed twice with the words `Python`. ([#1651](https://github.com/Microsoft/vscode-python/issues/1651)) +1. Enable code refactoring when using the new Analysis Engine. + ([#1774](https://github.com/Microsoft/vscode-python/issues/1774)) +1. `editor.formatOnType` no longer breaks numbers formatted with underscores. + ([#1779](https://github.com/Microsoft/vscode-python/issues/1779)) +1. `editor.formatOnType` now better handles multiline function arguments + ([#1796](https://github.com/Microsoft/vscode-python/issues/1796)) 1. `Go to Definition` now works for functions which have numbers that use `_` as a separator (as part of our Jedi 0.12.0 upgrade). ([#180](https://github.com/Microsoft/vscode-python/issues/180)) 1. Display documentation for auto completion items when the feature to automatically insert of brackets for selected item is turned on. ([#452](https://github.com/Microsoft/vscode-python/issues/452)) 1. Ensure empty paths do not get added into `sys.path` by the Jedi language server. (this was fixed in the previous release in [#1471](https://github.com/Microsoft/vscode-python/pull/1471)) ([#677](https://github.com/Microsoft/vscode-python/issues/677)) -1. Resoves rename refactor issue that remvoes the last line of the source file when the line is being refactored and source does not end with an EOL. +1. Resolves rename refactor issue that remvoes the last line of the source file when the line is being refactored and source does not end with an EOL. ([#695](https://github.com/Microsoft/vscode-python/issues/695)) 1. Ensure the prompt to install missing packages is not displayed more than once. ([#980](https://github.com/Microsoft/vscode-python/issues/980)) ### Code Health -1. Add syntax highlighting to constraints.txt file to match that of piprequirements files +1. Add syntax highlighting to `constraints.txt` files to match that of `requirements.txt` files. (thanks [Waleed Sehgal](https://github.com/waleedsehgal)) ([#1053](https://github.com/Microsoft/vscode-python/issues/1053)) 1. Refactor unit testing functionality to improve testability of individual components. @@ -65,7 +77,7 @@ our features: ([#1410](https://github.com/Microsoft/vscode-python/issues/1410)) 1. Ensure none of the npm packages (used by the extension) rely on native dependencies. ([#1416](https://github.com/Microsoft/vscode-python/issues/1416)) -1. Remove explicit initialization of PYTHONPATH with the current workspace path in unit testing of modules with the experimental debugger. +1. Remove explicit initialization of `PYTHONPATH` with the current workspace path in unit testing of modules with the experimental debugger. ([#1465](https://github.com/Microsoft/vscode-python/issues/1465)) 1. Flag `program` in `launch.json` configuration items as an optional attribute. ([#1503](https://github.com/Microsoft/vscode-python/issues/1503)) @@ -77,13 +89,13 @@ our features: ([#1569](https://github.com/Microsoft/vscode-python/issues/1569)) 1. Add tests for log points in the experimental debugger. ([#1582](https://github.com/Microsoft/vscode-python/issues/1582)) -1. Update typescript package to 2.8.3 +1. Update typescript package to 2.8.3. ([#1604](https://github.com/Microsoft/vscode-python/issues/1604)) 1. Fix typescript compilation error. ([#1623](https://github.com/Microsoft/vscode-python/issues/1623)) 1. Fix unit tests used to test flask template debugging on AppVeyor for the experimental debugger. ([#1640](https://github.com/Microsoft/vscode-python/issues/1640)) -1. Change yarn install script to include the keyword `--lock-file` +1. Change yarn install script to include the keyword `--lock-file`. (thanks [Lingyu Li](https://github.com/lingyv-li/)) ([#1682](https://github.com/Microsoft/vscode-python/issues/1682)) 1. Run unit tests as a pre-commit hook. @@ -96,6 +108,14 @@ our features: ([#1732](https://github.com/Microsoft/vscode-python/issues/1732)) 1. Prompt user to reload Visual Studio Code when toggling between the analysis engines. ([#1747](https://github.com/Microsoft/vscode-python/issues/1747)) +1. Fix typo in unit test. + ([#1794](https://github.com/Microsoft/vscode-python/issues/1794)) +1. Fix failing Prospector unit tests and add more tests for linters (with and without workspaces). + ([#1836](https://github.com/Microsoft/vscode-python/issues/1836)) +1. Ensure `Outline` view doesn't overload the language server with too many requets, while user is editing text in the editor. + ([#1856](https://github.com/Microsoft/vscode-python/issues/1856)) + + diff --git a/news/1 Enhancements/1153.md b/news/1 Enhancements/1153.md deleted file mode 100644 index 9f19892e6d51..000000000000 --- a/news/1 Enhancements/1153.md +++ /dev/null @@ -1,2 +0,0 @@ -Add support for the [Black formatter](https://pypi.org/project/black/) -(thanks to [Josh Smeaton](https://github.com/jarshwah) for the initial patch) diff --git a/news/1 Enhancements/1474.md b/news/1 Enhancements/1474.md deleted file mode 100644 index a439ceb3d20a..000000000000 --- a/news/1 Enhancements/1474.md +++ /dev/null @@ -1 +0,0 @@ -Add the command 'Discover Unit Tests'. diff --git a/news/1 Enhancements/1484.md b/news/1 Enhancements/1484.md deleted file mode 100644 index 2ed518b6abf1..000000000000 --- a/news/1 Enhancements/1484.md +++ /dev/null @@ -1 +0,0 @@ -Auto detect `*.jinja2` and `*.j2` extensions as Jinja templates, to enable debugging of Jinja templates. diff --git a/news/2 Fixes/1194.md b/news/2 Fixes/1194.md deleted file mode 100644 index 8301b2c8d5dd..000000000000 --- a/news/2 Fixes/1194.md +++ /dev/null @@ -1 +0,0 @@ -Ensure debugger breaks on `assert` failures. diff --git a/news/2 Fixes/1345.md b/news/2 Fixes/1345.md deleted file mode 100644 index eff8ca0a2b33..000000000000 --- a/news/2 Fixes/1345.md +++ /dev/null @@ -1 +0,0 @@ -Ensure debugged program is terminated when `Stop` debugging button is clicked. diff --git a/news/2 Fixes/1461.md b/news/2 Fixes/1461.md deleted file mode 100644 index 1c8c770456a9..000000000000 --- a/news/2 Fixes/1461.md +++ /dev/null @@ -1 +0,0 @@ -Fix indentation when function contains type hints. diff --git a/news/2 Fixes/1476.md b/news/2 Fixes/1476.md deleted file mode 100644 index 071cddec5a2f..000000000000 --- a/news/2 Fixes/1476.md +++ /dev/null @@ -1 +0,0 @@ -Ensure python environment activation works as expected within a multi-root workspace. diff --git a/news/2 Fixes/1529.md b/news/2 Fixes/1529.md deleted file mode 100644 index fdb8051aac0a..000000000000 --- a/news/2 Fixes/1529.md +++ /dev/null @@ -1 +0,0 @@ -Close communication channel before exiting the test runner. diff --git a/news/2 Fixes/1628.md b/news/2 Fixes/1628.md deleted file mode 100644 index 519f751b6161..000000000000 --- a/news/2 Fixes/1628.md +++ /dev/null @@ -1 +0,0 @@ -Allow for negative column numbers in messages returned by `pylint`. diff --git a/news/2 Fixes/1634.md b/news/2 Fixes/1634.md deleted file mode 100644 index b9c8cbefe658..000000000000 --- a/news/2 Fixes/1634.md +++ /dev/null @@ -1 +0,0 @@ -Modify the `FLASK_APP` environment variable in the flask debug configuration to include just the name of the application file. diff --git a/news/2 Fixes/1651.md b/news/2 Fixes/1651.md deleted file mode 100644 index fa562bc618e2..000000000000 --- a/news/2 Fixes/1651.md +++ /dev/null @@ -1 +0,0 @@ -Ensure the display name of an interpreter does not get prefixed twice with the words `Python`. diff --git a/news/2 Fixes/1774.md b/news/2 Fixes/1774.md deleted file mode 100644 index 9bf55ad96f30..000000000000 --- a/news/2 Fixes/1774.md +++ /dev/null @@ -1 +0,0 @@ -Enable code refactoring when using the new Analysis Engine. diff --git a/news/2 Fixes/1779.md b/news/2 Fixes/1779.md deleted file mode 100644 index 066f595d007c..000000000000 --- a/news/2 Fixes/1779.md +++ /dev/null @@ -1 +0,0 @@ -`editor.formatOnType` no longer breaks numbers formatted with underscores. diff --git a/news/2 Fixes/1796.md b/news/2 Fixes/1796.md deleted file mode 100644 index 41751c65168a..000000000000 --- a/news/2 Fixes/1796.md +++ /dev/null @@ -1 +0,0 @@ -`editor.formatOnType` now better handles multiline function arguments diff --git a/news/2 Fixes/180.md b/news/2 Fixes/180.md deleted file mode 100644 index 60af4c971641..000000000000 --- a/news/2 Fixes/180.md +++ /dev/null @@ -1 +0,0 @@ -`Go to Definition` now works for functions which have numbers that use `_` as a separator (as part of our Jedi 0.12.0 upgrade). diff --git a/news/2 Fixes/452.md b/news/2 Fixes/452.md deleted file mode 100644 index 69f64d85a6ab..000000000000 --- a/news/2 Fixes/452.md +++ /dev/null @@ -1 +0,0 @@ -Display documentation for auto completion items when the feature to automatically insert of brackets for selected item is turned on. diff --git a/news/2 Fixes/677.md b/news/2 Fixes/677.md deleted file mode 100644 index 8c55a40a7539..000000000000 --- a/news/2 Fixes/677.md +++ /dev/null @@ -1 +0,0 @@ -Ensure empty paths do not get added into `sys.path` by the Jedi language server. (this was fixed in the previous release in [#1471](https://github.com/Microsoft/vscode-python/pull/1471)) diff --git a/news/2 Fixes/695.md b/news/2 Fixes/695.md deleted file mode 100644 index bb5b48569222..000000000000 --- a/news/2 Fixes/695.md +++ /dev/null @@ -1 +0,0 @@ -Resoves rename refactor issue that remvoes the last line of the source file when the line is being refactored and source does not end with an EOL. \ No newline at end of file diff --git a/news/2 Fixes/980.md b/news/2 Fixes/980.md deleted file mode 100644 index 0dcbcaacd8ff..000000000000 --- a/news/2 Fixes/980.md +++ /dev/null @@ -1 +0,0 @@ -Ensure the prompt to install missing packages is not displayed more than once. diff --git a/news/3 Code Health/1053.md b/news/3 Code Health/1053.md deleted file mode 100644 index 0d281defa4c0..000000000000 --- a/news/3 Code Health/1053.md +++ /dev/null @@ -1,2 +0,0 @@ -Add syntax highlighting to constraints.txt file to match that of piprequirements files -(thanks [Waleed Sehgal](https://github.com/waleedsehgal)) diff --git a/news/3 Code Health/1068.md b/news/3 Code Health/1068.md deleted file mode 100644 index 82f60782ccd9..000000000000 --- a/news/3 Code Health/1068.md +++ /dev/null @@ -1 +0,0 @@ -Refactor unit testing functionality to improve testability of individual components. diff --git a/news/3 Code Health/1109.md b/news/3 Code Health/1109.md deleted file mode 100644 index 4dd7d9934d18..000000000000 --- a/news/3 Code Health/1109.md +++ /dev/null @@ -1 +0,0 @@ -Add unit tests for evaluating expressions in the experimental debugger. diff --git a/news/3 Code Health/1280.md b/news/3 Code Health/1280.md deleted file mode 100644 index 813fd9b8c69d..000000000000 --- a/news/3 Code Health/1280.md +++ /dev/null @@ -1 +0,0 @@ -Add tests to ensure custom arguments get passed into python program when using the experimental debugger. diff --git a/news/3 Code Health/1339.md b/news/3 Code Health/1339.md deleted file mode 100644 index e06d19240f67..000000000000 --- a/news/3 Code Health/1339.md +++ /dev/null @@ -1 +0,0 @@ -Ensure custom environment variables are always used when spawning any process from within the extension. diff --git a/news/3 Code Health/1410.md b/news/3 Code Health/1410.md deleted file mode 100644 index ae0895582c9e..000000000000 --- a/news/3 Code Health/1410.md +++ /dev/null @@ -1 +0,0 @@ -Add tests for hit count breakpoints for the experimental debugger. diff --git a/news/3 Code Health/1416.md b/news/3 Code Health/1416.md deleted file mode 100644 index 52747061cad1..000000000000 --- a/news/3 Code Health/1416.md +++ /dev/null @@ -1 +0,0 @@ -Ensure none of the npm packages (used by the extension) rely on native dependencies. diff --git a/news/3 Code Health/1465.md b/news/3 Code Health/1465.md deleted file mode 100644 index 461311cddd3a..000000000000 --- a/news/3 Code Health/1465.md +++ /dev/null @@ -1 +0,0 @@ -Remove explicit initialization of PYTHONPATH with the current workspace path in unit testing of modules with the experimental debugger. diff --git a/news/3 Code Health/1503.md b/news/3 Code Health/1503.md deleted file mode 100644 index 6d81d673e007..000000000000 --- a/news/3 Code Health/1503.md +++ /dev/null @@ -1 +0,0 @@ -Flag `program` in `launch.json` configuration items as an optional attribute. diff --git a/news/3 Code Health/1551.md b/news/3 Code Health/1551.md deleted file mode 100644 index 22d188323ffb..000000000000 --- a/news/3 Code Health/1551.md +++ /dev/null @@ -1 +0,0 @@ -Remove unused setting `disablePromptForFeatures`. diff --git a/news/3 Code Health/1552.md b/news/3 Code Health/1552.md deleted file mode 100644 index afad15c40dd3..000000000000 --- a/news/3 Code Health/1552.md +++ /dev/null @@ -1 +0,0 @@ -Remove unused Unit Test setting `debugHost`. diff --git a/news/3 Code Health/1569.md b/news/3 Code Health/1569.md deleted file mode 100644 index 46eca178ee8a..000000000000 --- a/news/3 Code Health/1569.md +++ /dev/null @@ -1 +0,0 @@ -Create a new API to retrieve interpreter details with the ability to cache the details. diff --git a/news/3 Code Health/1582.md b/news/3 Code Health/1582.md deleted file mode 100644 index 6d0ff52d4eb1..000000000000 --- a/news/3 Code Health/1582.md +++ /dev/null @@ -1 +0,0 @@ -Add tests for log points in the experimental debugger. diff --git a/news/3 Code Health/1604.md b/news/3 Code Health/1604.md deleted file mode 100644 index c3eca35501c3..000000000000 --- a/news/3 Code Health/1604.md +++ /dev/null @@ -1 +0,0 @@ -Update typescript package to 2.8.3 diff --git a/news/3 Code Health/1623.md b/news/3 Code Health/1623.md deleted file mode 100644 index 5e3a73bb56ef..000000000000 --- a/news/3 Code Health/1623.md +++ /dev/null @@ -1 +0,0 @@ -Fix typescript compilation error. diff --git a/news/3 Code Health/1640.md b/news/3 Code Health/1640.md deleted file mode 100644 index 329147d17bf9..000000000000 --- a/news/3 Code Health/1640.md +++ /dev/null @@ -1 +0,0 @@ -Fix unit tests used to test flask template debugging on AppVeyor for the experimental debugger. diff --git a/news/3 Code Health/1682.md b/news/3 Code Health/1682.md deleted file mode 100644 index 603813eaeb57..000000000000 --- a/news/3 Code Health/1682.md +++ /dev/null @@ -1,2 +0,0 @@ -Change yarn install script to include the keyword `--lock-file` -(thanks [Lingyu Li](https://github.com/lingyv-li/)) \ No newline at end of file diff --git a/news/3 Code Health/1703.md b/news/3 Code Health/1703.md deleted file mode 100644 index bfdd065f7e1d..000000000000 --- a/news/3 Code Health/1703.md +++ /dev/null @@ -1 +0,0 @@ -Run unit tests as a pre-commit hook. diff --git a/news/3 Code Health/1719.md b/news/3 Code Health/1719.md deleted file mode 100644 index 1650be424b90..000000000000 --- a/news/3 Code Health/1719.md +++ /dev/null @@ -1 +0,0 @@ -Update debug capabilities to add support for the setting `supportTerminateDebuggee` due to an upstream update from [PTVSD](https://github.com/Microsoft/ptvsd/issues). diff --git a/news/3 Code Health/1730.md b/news/3 Code Health/1730.md deleted file mode 100644 index d5d41c722fc8..000000000000 --- a/news/3 Code Health/1730.md +++ /dev/null @@ -1 +0,0 @@ -Build and upload development build of the extension to the Azure blob store even if CI tests fail on the `master` branch. \ No newline at end of file diff --git a/news/3 Code Health/1732.md b/news/3 Code Health/1732.md deleted file mode 100644 index bc6c53c11f02..000000000000 --- a/news/3 Code Health/1732.md +++ /dev/null @@ -1 +0,0 @@ -Changes to the script used to upload the extension to the Azure blob store. \ No newline at end of file diff --git a/news/3 Code Health/1747.md b/news/3 Code Health/1747.md deleted file mode 100644 index 7d098b4bf0a8..000000000000 --- a/news/3 Code Health/1747.md +++ /dev/null @@ -1 +0,0 @@ -Prompt user to reload Visual Studio Code when toggling between the analysis engines. diff --git a/news/3 Code Health/1794.md b/news/3 Code Health/1794.md deleted file mode 100644 index 54738e99dc85..000000000000 --- a/news/3 Code Health/1794.md +++ /dev/null @@ -1 +0,0 @@ -Fix typo in unit test. diff --git a/news/3 Code Health/1836.md b/news/3 Code Health/1836.md deleted file mode 100644 index 6edcdecce306..000000000000 --- a/news/3 Code Health/1836.md +++ /dev/null @@ -1 +0,0 @@ -Fix failing Prospector unit tests and add more tests for linters (with and without workspaces). diff --git a/news/3 Code Health/1856.md b/news/3 Code Health/1856.md deleted file mode 100644 index 61e0b4966073..000000000000 --- a/news/3 Code Health/1856.md +++ /dev/null @@ -1 +0,0 @@ -Ensure `Outline` view doesn't overload the language server with too many requets, while user is editing text in the editor. diff --git a/package.json b/package.json index 48076a3751a7..35f648d9d5f0 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2018.5.0-rc", + "version": "2018.5.0", "publisher": "ms-python", "author": { "name": "Microsoft Corporation" From e9643a021e2f4a7fc71a2c56ea454a2e6bd62d73 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 5 Jun 2018 12:17:59 -0700 Subject: [PATCH 287/433] Bump to alpha --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 35f648d9d5f0..0300175c8268 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2018.5.0", + "version": "2018.6.0-alpha", "publisher": "ms-python", "author": { "name": "Microsoft Corporation" From a672431bb4cecfffc528d9e64028f028e473bdf0 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 5 Jun 2018 12:22:04 -0700 Subject: [PATCH 288/433] Update links --- .github/release_plan.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/release_plan.md b/.github/release_plan.md index 699a1a30a022..bf176d309677 100644 --- a/.github/release_plan.md +++ b/.github/release_plan.md @@ -67,7 +67,7 @@ - [ ] Update the [changelog](https://github.com/Microsoft/vscode-python/blob/master/CHANGELOG.md) (including the names of external contributors & projects) - [ ] Update the [version](https://github.com/Microsoft/vscode-python/blob/master/package.json) number to be final - [ ] Make sure [CI](https://github.com/Microsoft/vscode-python/blob/master/CONTRIBUTING.md) is passing -- [ ] Create the `release-` [branch](https://github.com/Microsoft/vscode-python/branches) +- [ ] Create the `release-` [branch](https://github.com/Microsoft/vscode-python/) - [ ] Generate final `.vsix` file from the `release-` branch - [ ] Upload the final `.vsix` file to the [marketplace](https://marketplace.visualstudio.com/items?itemName=ms-python.python) - [ ] Publish [documentation](https://code.visualstudio.com/docs/python/python-tutorial) [changes](https://github.com/microsoft/vscode-docs/pulls) @@ -78,9 +78,9 @@ - [ ] Bump the [version](https://github.com/Microsoft/vscode-python/blob/master/package.json) number to the next `alpha` - [ ] Make sure the next **two** [milestones](https://github.com/Microsoft/vscode-python/milestones) exist - [ ] Lift the feature freeze -- [ ] Create a new [release plan](https://github.com/Microsoft/vscode-python/labels/release%20plan) +- [ ] Create a new [release plan](https://github.com/Microsoft/vscode-python/edit/master/.github/release_plan.md) ## Clean up after _this_ release - [ ] Clean up any straggling [fixed issues needing validation](https://github.com/Microsoft/vscode-python/issues?q=label%3A%22validate+fix%22+is%3Aclosed) -- [ ] Close the (now) old [milestone](https://github.com/Microsoft/vscode-python/labels/release%20plan) +- [ ] Close the (now) old [milestone](https://github.com/Microsoft/vscode-python/milestones) - [ ] Delete the previous releases' [branch](https://github.com/Microsoft/vscode-python/branches) From 46e4d8de49da49456277437b4532681605970568 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 5 Jun 2018 12:36:31 -0700 Subject: [PATCH 289/433] Drop the "assign everything in the milestone" step --- .github/release_plan.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/release_plan.md b/.github/release_plan.md index bf176d309677..f56081dd8c5f 100644 --- a/.github/release_plan.md +++ b/.github/release_plan.md @@ -10,7 +10,6 @@ - [ ] Evaluate [projects](https://github.com/Microsoft/vscode-python/projects) & [`meta` issues](https://github.com/Microsoft/vscode-python/labels/meta) - [ ] Go through [`needs PR` issues](https://github.com/Microsoft/vscode-python/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22needs+PR%22+-label%3A%22help+wanted%22+-label%3A%22good+first+issue%22+no%3Amilestone) to see if there's anything we want to add to this milestone - [ ] Finalize the initial set of issues for the [milestone](https://github.com/Microsoft/vscode-python/milestones) -- [ ] Make sure all issues for this [milestone](https://github.com/Microsoft/vscode-python/milestones) are assigned - [ ] Close issues that have [needed more info](https://github.com/Microsoft/vscode-python/issues?q=is%3Aopen+label%3A%22needs+more+info%22+sort%3Aupdated-asc) for over a month # Week of Monday, XXX From af2407f1771151e7ca7eb17344fd18a1d69dba3a Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 5 Jun 2018 13:04:14 -0700 Subject: [PATCH 290/433] Use dotenv package to parse environment variables definition files (#1802) --- news/3 Code Health/1376.md | 1 + package.json | 2 ++ src/client/common/variables/environment.ts | 31 +++------------------- yarn.lock | 10 +++++++ 4 files changed, 16 insertions(+), 28 deletions(-) create mode 100644 news/3 Code Health/1376.md diff --git a/news/3 Code Health/1376.md b/news/3 Code Health/1376.md new file mode 100644 index 000000000000..c884f9aa7a5c --- /dev/null +++ b/news/3 Code Health/1376.md @@ -0,0 +1 @@ +Use [dotenv](https://www.npmjs.com/package/dotenv) package to parse [environment variables definition files](https://code.visualstudio.com/docs/python/environments#_environment-variable-definitions-file). diff --git a/package.json b/package.json index 0300175c8268..d4f2a601769c 100644 --- a/package.json +++ b/package.json @@ -1864,6 +1864,7 @@ "dependencies": { "arch": "2.1.0", "diff-match-patch": "1.0.0", + "dotenv": "^5.0.1", "fs-extra": "4.0.3", "fuzzy": "0.1.3", "get-port": "3.2.0", @@ -1902,6 +1903,7 @@ "@types/chai-arrays": "^1.0.2", "@types/chai-as-promised": "^7.1.0", "@types/del": "^3.0.0", + "@types/dotenv": "^4.0.3", "@types/event-stream": "^3.3.33", "@types/fs-extra": "^5.0.1", "@types/get-port": "^3.2.0", diff --git a/src/client/common/variables/environment.ts b/src/client/common/variables/environment.ts index 0ad9443380e7..99d2de6bbaf3 100644 --- a/src/client/common/variables/environment.ts +++ b/src/client/common/variables/environment.ts @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +import * as dotenv from 'dotenv'; import * as fs from 'fs-extra'; import { inject, injectable } from 'inversify'; import * as path from 'path'; @@ -10,7 +11,7 @@ import { EnvironmentVariables, IEnvironmentVariablesService } from './types'; @injectable() export class EnvironmentVariablesService implements IEnvironmentVariablesService { private readonly pathVariable: 'PATH' | 'Path'; - constructor( @inject(IPathUtils) pathUtils: IPathUtils) { + constructor(@inject(IPathUtils) pathUtils: IPathUtils) { this.pathVariable = pathUtils.getPathVariableName(); } public async parseFile(filePath: string): Promise { @@ -21,14 +22,7 @@ export class EnvironmentVariablesService implements IEnvironmentVariablesService if (!fs.lstatSync(filePath).isFile()) { return undefined; } - return new Promise((resolve, reject) => { - fs.readFile(filePath, 'utf8', (error, data) => { - if (error) { - return reject(error); - } - resolve(parseEnvironmentVariables(data)); - }); - }); + return dotenv.parse(filePath); } public mergeVariables(source: EnvironmentVariables, target: EnvironmentVariables) { if (!target) { @@ -67,22 +61,3 @@ export class EnvironmentVariablesService implements IEnvironmentVariablesService return vars; } } - -function parseEnvironmentVariables(contents: string): EnvironmentVariables | undefined { - if (typeof contents !== 'string' || contents.length === 0) { - return undefined; - } - - const env = {} as EnvironmentVariables; - contents.split('\n').forEach(line => { - const match = line.match(/^\s*([\w\.\-]+)\s*=\s*(.*)?\s*$/); - if (match !== null) { - let value = typeof match[2] === 'string' ? match[2] : ''; - if (value.length > 0 && value.charAt(0) === '"' && value.charAt(value.length - 1) === '"') { - value = value.replace(/\\n/gm, '\n'); - } - env[match[1]] = value.replace(/(^['"]|['"]$)/g, ''); - } - }); - return env; -} diff --git a/yarn.lock b/yarn.lock index c12707f97ad3..ac469a87f2e0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -53,6 +53,12 @@ dependencies: "@types/glob" "*" +"@types/dotenv@^4.0.3": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@types/dotenv/-/dotenv-4.0.3.tgz#ebcfc40da7bc0728b705945b7db48485ec5b4b67" + dependencies: + "@types/node" "*" + "@types/event-stream@^3.3.33": version "3.3.33" resolved "https://registry.yarnpkg.com/@types/event-stream/-/event-stream-3.3.33.tgz#ca155f6e805b606175322c03e6d75bc3b940cb95" @@ -1071,6 +1077,10 @@ doctrine@0.7.2: esutils "^1.1.6" isarray "0.0.1" +dotenv@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-5.0.1.tgz#a5317459bd3d79ab88cff6e44057a6a3fbb1fcef" + duplexer2@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db" From 3a5ac11e2957b2f347c3c51abfaa997ebdcdd95a Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 5 Jun 2018 13:04:23 -0700 Subject: [PATCH 291/433] Capture telemetry for terminal creation along with misc info (#1804) --- news/3 Code Health/1542.md | 1 + src/client/common/terminal/service.ts | 16 +++++++++++++++- src/client/interpreter/contracts.ts | 12 ++++++------ src/client/providers/terminalProvider.ts | 3 +++ src/client/telemetry/constants.ts | 1 + src/client/telemetry/types.ts | 10 +++++++++- src/test/common/terminals/factory.test.ts | 2 -- src/test/common/terminals/service.test.ts | 2 -- 8 files changed, 35 insertions(+), 12 deletions(-) create mode 100644 news/3 Code Health/1542.md diff --git a/news/3 Code Health/1542.md b/news/3 Code Health/1542.md new file mode 100644 index 000000000000..9421570bde59 --- /dev/null +++ b/news/3 Code Health/1542.md @@ -0,0 +1 @@ +Capture telemetry for the usage of the `Create Terminal` command along with other instances when a terminal is created implicitly. diff --git a/src/client/common/terminal/service.ts b/src/client/common/terminal/service.ts index 0fe4ddaa5f3d..3927578f9adc 100644 --- a/src/client/common/terminal/service.ts +++ b/src/client/common/terminal/service.ts @@ -3,10 +3,14 @@ import { inject, injectable } from 'inversify'; import { Disposable, Event, EventEmitter, Terminal, Uri } from 'vscode'; +import '../../common/extensions'; +import { IInterpreterService } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; +import { captureTelemetry } from '../../telemetry'; +import { TERMINAL_CREATE } from '../../telemetry/constants'; import { ITerminalManager } from '../application/types'; import { sleep } from '../core.utils'; -import { IDisposableRegistry } from '../types'; +import { IConfigurationService, IDisposableRegistry } from '../types'; import { ITerminalHelper, ITerminalService, TerminalShellType } from './types'; @injectable() @@ -74,6 +78,8 @@ export class TerminalService implements ITerminalService, Disposable { } this.terminal!.show(preserveFocus); + + this.sendTelemetry().ignoreErrors(); } private terminalCloseHandler(terminal: Terminal) { if (terminal === this.terminal) { @@ -81,4 +87,12 @@ export class TerminalService implements ITerminalService, Disposable { this.terminal = undefined; } } + + private async sendTelemetry() { + const pythonPath = this.serviceContainer.get(IConfigurationService).getSettings(this.resource).pythonPath; + const interpreterInfo = await this.serviceContainer.get(IInterpreterService).getInterpreterDetails(pythonPath); + const pythonVersion = interpreterInfo.version_info ? interpreterInfo.version_info.join('.') : undefined; + const interpreterType = interpreterInfo.type; + captureTelemetry(TERMINAL_CREATE, { terminal: this.terminalShellType, pythonVersion, interpreterType }); + } } diff --git a/src/client/interpreter/contracts.ts b/src/client/interpreter/contracts.ts index 1b3b793230bf..6d23d28ea739 100644 --- a/src/client/interpreter/contracts.ts +++ b/src/client/interpreter/contracts.ts @@ -51,12 +51,12 @@ export interface ICondaService { } export enum InterpreterType { - Unknown = 1, - Conda = 2, - VirtualEnv = 4, - PipEnv = 8, - Pyenv = 16, - Venv = 32 + Unknown = 'Unknown', + Conda = 'Conda', + VirtualEnv = 'VirtualEnv', + PipEnv = 'PipEnv', + Pyenv = 'Pyenv', + Venv = 'Venv' } export type PythonInterpreter = InterpreterInfomation & { companyDisplayName?: string; diff --git a/src/client/providers/terminalProvider.ts b/src/client/providers/terminalProvider.ts index 2d84986ebb07..87d60070e328 100644 --- a/src/client/providers/terminalProvider.ts +++ b/src/client/providers/terminalProvider.ts @@ -6,6 +6,8 @@ import { ICommandManager, IDocumentManager, IWorkspaceService } from '../common/ import { Commands } from '../common/constants'; import { ITerminalServiceFactory } from '../common/terminal/types'; import { IServiceContainer } from '../ioc/types'; +import { captureTelemetry } from '../telemetry'; +import { TERMINAL_CREATE } from '../telemetry/constants'; export class TerminalProvider implements Disposable { private disposables: Disposable[] = []; @@ -21,6 +23,7 @@ export class TerminalProvider implements Disposable { this.disposables.push(disposable); } + @captureTelemetry(TERMINAL_CREATE, { triggeredBy: 'commandpalette' }) private async onCreateTerminal() { const terminalService = this.serviceContainer.get(ITerminalServiceFactory); const activeResource = this.getActiveResource(); diff --git a/src/client/telemetry/constants.ts b/src/client/telemetry/constants.ts index be0cdc8a2c21..bd2b87b3bd6f 100644 --- a/src/client/telemetry/constants.ts +++ b/src/client/telemetry/constants.ts @@ -34,3 +34,4 @@ export const PYTHON_ANALYSIS_ENGINE_ENABLED = 'PYTHON_ANALYSIS_ENGINE.ENABLED'; export const PYTHON_ANALYSIS_ENGINE_DOWNLOADED = 'PYTHON_ANALYSIS_ENGINE.DOWNLOADED'; export const PYTHON_ANALYSIS_ENGINE_ERROR = 'PYTHON_ANALYSIS_ENGINE.ERROR'; export const PYTHON_ANALYSIS_ENGINE_STARTUP = 'PYTHON_ANALYSIS_ENGINE.STARTUP'; +export const TERMINAL_CREATE = 'TERMINAL.CREATE'; diff --git a/src/client/telemetry/types.ts b/src/client/telemetry/types.ts index 50d017ee82fd..44524e796725 100644 --- a/src/client/telemetry/types.ts +++ b/src/client/telemetry/types.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. import { TerminalShellType } from '../common/terminal/types'; +import { InterpreterType } from '../interpreter/contracts'; import { LinterId } from '../linters/types'; export type EditorLoadTelemetry = { @@ -57,4 +58,11 @@ export type TestDiscoverytTelemetry = { export type FeedbackTelemetry = { action: 'accepted' | 'dismissed' | 'doNotShowAgain'; }; -export type TelemetryProperties = FormatTelemetry | LintingTelemetry | EditorLoadTelemetry | PythonInterpreterTelemetry | CodeExecutionTelemetry | TestRunTelemetry | TestDiscoverytTelemetry | FeedbackTelemetry; +export type TerminalTelemetry = { + terminal?: TerminalShellType; + triggeredBy?: 'commandpalette'; + pythonVersion?: string; + interpreterType?: InterpreterType; +}; +export type TelemetryProperties = FormatTelemetry | LintingTelemetry | EditorLoadTelemetry | PythonInterpreterTelemetry | + CodeExecutionTelemetry | TestRunTelemetry | TestDiscoverytTelemetry | FeedbackTelemetry | TerminalTelemetry; diff --git a/src/test/common/terminals/factory.test.ts b/src/test/common/terminals/factory.test.ts index 96518c7ccfa3..232beb41246b 100644 --- a/src/test/common/terminals/factory.test.ts +++ b/src/test/common/terminals/factory.test.ts @@ -11,14 +11,12 @@ import { ITerminalHelper, ITerminalServiceFactory } from '../../../client/common import { IDisposableRegistry } from '../../../client/common/types'; import { IInterpreterService } from '../../../client/interpreter/contracts'; import { IServiceContainer } from '../../../client/ioc/types'; -import { initialize } from '../../initialize'; // tslint:disable-next-line:max-func-body-length suite('Terminal Service Factory', () => { let factory: ITerminalServiceFactory; let disposables: Disposable[] = []; let workspaceService: TypeMoq.IMock; - suiteSetup(initialize); setup(() => { const serviceContainer = TypeMoq.Mock.ofType(); const interpreterService = TypeMoq.Mock.ofType(); diff --git a/src/test/common/terminals/service.test.ts b/src/test/common/terminals/service.test.ts index 1f71218b8094..ea05cd81711e 100644 --- a/src/test/common/terminals/service.test.ts +++ b/src/test/common/terminals/service.test.ts @@ -10,7 +10,6 @@ import { TerminalService } from '../../../client/common/terminal/service'; import { ITerminalHelper, TerminalShellType } from '../../../client/common/terminal/types'; import { IDisposableRegistry } from '../../../client/common/types'; import { IServiceContainer } from '../../../client/ioc/types'; -import { initialize } from '../../initialize'; // tslint:disable-next-line:max-func-body-length suite('Terminal Service', () => { @@ -22,7 +21,6 @@ suite('Terminal Service', () => { let workspaceService: TypeMoq.IMock; let disposables: Disposable[] = []; let mockServiceContainer: TypeMoq.IMock; - suiteSetup(initialize); setup(() => { terminal = TypeMoq.Mock.ofType(); terminalManager = TypeMoq.Mock.ofType(); From e41140e655e03c6733b39fada52fdb770be282ce Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 5 Jun 2018 13:04:30 -0700 Subject: [PATCH 292/433] Speed up githook by skipping commits not containing any `.ts` files (#1805) --- gulpfile.js | 25 +++++++++++++++++++------ news/3 Code Health/1803.md | 1 + 2 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 news/3 Code Health/1803.md diff --git a/gulpfile.js b/gulpfile.js index 0f95060a1b9c..c887f26df873 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -201,6 +201,12 @@ const hygiene = (options) => { reRunCompilation = true; return; } + const fileListToProcess = options.mode === 'compile' ? undefined : getFileListToProcess(options); + if (Array.isArray(fileListToProcess) && fileListToProcess !== all + && fileListToProcess.filter(item => item.endsWith('.ts')).length === 0) { + return; + } + const started = new Date().getTime(); compilationInProgress = true; options = options || {}; @@ -348,7 +354,7 @@ const hygiene = (options) => { return tsProject(reporter); } - const files = options.mode === 'compile' ? tsProject.src() : getFilesToProcess(options); + const files = options.mode === 'compile' ? tsProject.src() : getFilesToProcess(fileListToProcess); const dest = options.mode === 'compile' ? './out' : '.'; let result = files .pipe(filter(f => f && f.stat && !f.stat.isDirectory())); @@ -482,22 +488,29 @@ function getModifiedFilesSync() { /** * @param {hygieneOptions} options */ -function getFilesToProcess(options) { +function getFilesToProcess(fileList) { + const gulpSrcOptions = { base: '.' }; + return gulp.src(fileList, gulpSrcOptions); +} + +/** +* @param {hygieneOptions} options +*/ +function getFileListToProcess(options) { const mode = options ? options.mode : 'all'; const gulpSrcOptions = { base: '.' }; // If we need only modified files, then filter the glob. if (options && options.mode === 'changes') { - return gulp.src(getModifiedFilesSync(), gulpSrcOptions); + return getModifiedFilesSync(); } if (options && options.mode === 'staged') { - return gulp.src(getStagedFilesSync(), gulpSrcOptions); + return getStagedFilesSync(); } - return gulp.src(all, gulpSrcOptions); + return all; } - exports.hygiene = hygiene; // this allows us to run hygiene as a git pre-commit hook. diff --git a/news/3 Code Health/1803.md b/news/3 Code Health/1803.md new file mode 100644 index 000000000000..9a272209b91e --- /dev/null +++ b/news/3 Code Health/1803.md @@ -0,0 +1 @@ +Speed up githook by skipping commits not containing any `.ts` files. From a2c0619ff365c33657d5e6da1f6ae571c164c010 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 5 Jun 2018 13:05:02 -0700 Subject: [PATCH 293/433] Log Conda not existing message as an information instead of an error (#1820) Fixes #1817 Fixes #1821 --- news/3 Code Health/1817.md | 1 + news/3 Code Health/1821.md | 1 + src/client/common/logger.ts | 9 ++++++++ src/client/common/types.ts | 1 + .../configurationProviderUtils.ts | 5 ++++- .../locators/services/condaService.ts | 2 +- .../locators/services/pipEnvService.ts | 9 ++++---- .../debugger/configProvider/provider.test.ts | 8 ++++++- src/test/interpreters/condaService.test.ts | 21 +++++++++++++++---- src/test/interpreters/pipEnvService.test.ts | 10 +++++++-- 10 files changed, 53 insertions(+), 14 deletions(-) create mode 100644 news/3 Code Health/1817.md create mode 100644 news/3 Code Health/1821.md diff --git a/news/3 Code Health/1817.md b/news/3 Code Health/1817.md new file mode 100644 index 000000000000..f50a3adb42e1 --- /dev/null +++ b/news/3 Code Health/1817.md @@ -0,0 +1 @@ +Log Conda not existing message as an information instead of an error. diff --git a/news/3 Code Health/1821.md b/news/3 Code Health/1821.md new file mode 100644 index 000000000000..2ef4abc844c2 --- /dev/null +++ b/news/3 Code Health/1821.md @@ -0,0 +1 @@ +Make use of `ILogger` to log messages instead of using `console.error`. diff --git a/src/client/common/logger.ts b/src/client/common/logger.ts index e15281d68294..e07590c96625 100644 --- a/src/client/common/logger.ts +++ b/src/client/common/logger.ts @@ -1,3 +1,5 @@ +// tslint:disable:no-console + import { injectable } from 'inversify'; import { ILogger } from './types'; @@ -19,6 +21,13 @@ export class Logger implements ILogger { console.warn(`${PREFIX}${message}`); } } + public logInformation(message: string, ex?: Error) { + if (ex) { + console.info(`${PREFIX}${message}`, ex); + } else { + console.info(`${PREFIX}${message}`); + } + } } // tslint:disable-next-line:no-any export function error(title: string = '', message: any) { diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 71d39e8e4497..5791899f6492 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -41,6 +41,7 @@ export const ILogger = Symbol('ILogger'); export interface ILogger { logError(message: string, error?: Error); logWarning(message: string, error?: Error); + logInformation(message: string, error?: Error); } export enum InstallerResponse { diff --git a/src/client/debugger/configProviders/configurationProviderUtils.ts b/src/client/debugger/configProviders/configurationProviderUtils.ts index 108426f9b979..77e6d3f5de0c 100644 --- a/src/client/debugger/configProviders/configurationProviderUtils.ts +++ b/src/client/debugger/configProviders/configurationProviderUtils.ts @@ -9,6 +9,7 @@ import { Uri } from 'vscode'; import { IApplicationShell } from '../../common/application/types'; import { IFileSystem } from '../../common/platform/types'; import { IPythonExecutionFactory } from '../../common/process/types'; +import { ILogger } from '../../common/types'; import { IServiceContainer } from '../../ioc/types'; import { IConfigurationProviderUtils } from './types'; @@ -18,9 +19,11 @@ const PSERVE_SCRIPT_FILE_NAME = 'pserve.py'; export class ConfigurationProviderUtils implements IConfigurationProviderUtils { private readonly executionFactory: IPythonExecutionFactory; private readonly fs: IFileSystem; + private readonly logger: ILogger; constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { this.executionFactory = this.serviceContainer.get(IPythonExecutionFactory); this.fs = this.serviceContainer.get(IFileSystem); + this.logger = this.serviceContainer.get(ILogger); } public async getPyramidStartupScriptFilePath(resource?: Uri): Promise { try { @@ -30,7 +33,7 @@ export class ConfigurationProviderUtils implements IConfigurationProviderUtils { return await this.fs.fileExists(pserveFilePath) ? pserveFilePath : undefined; } catch (ex) { const message = 'Unable to locate \'pserve.py\' required for debugging of Pyramid applications.'; - console.error(message, ex); + this.logger.logError(message, ex); const app = this.serviceContainer.get(IApplicationShell); app.showErrorMessage(message); return; diff --git a/src/client/interpreter/locators/services/condaService.ts b/src/client/interpreter/locators/services/condaService.ts index 969b5a1ca0ff..58fe10a45045 100644 --- a/src/client/interpreter/locators/services/condaService.ts +++ b/src/client/interpreter/locators/services/condaService.ts @@ -142,7 +142,7 @@ export class CondaService implements ICondaService { // Failed because either: // 1. conda is not installed. // 2. `conda env list has changed signature. - this.logger.logError('Failed to get conda environment list from conda', ex); + this.logger.logInformation('Failed to get conda environment list from conda', ex); } } public getInterpreterPath(condaEnvironmentPath: string): string { diff --git a/src/client/interpreter/locators/services/pipEnvService.ts b/src/client/interpreter/locators/services/pipEnvService.ts index c3e737c65955..e8d69601d053 100644 --- a/src/client/interpreter/locators/services/pipEnvService.ts +++ b/src/client/interpreter/locators/services/pipEnvService.ts @@ -7,8 +7,7 @@ import { Uri } from 'vscode'; import { IApplicationShell, IWorkspaceService } from '../../../common/application/types'; import { IFileSystem } from '../../../common/platform/types'; import { IProcessServiceFactory } from '../../../common/process/types'; -import { ICurrentProcess } from '../../../common/types'; -import { IEnvironmentVariablesProvider } from '../../../common/variables/types'; +import { ICurrentProcess, ILogger } from '../../../common/types'; import { IServiceContainer } from '../../../ioc/types'; import { IInterpreterHelper, InterpreterType, IPipEnvService, PythonInterpreter } from '../../contracts'; import { CacheableLocatorService } from './cacheableLocatorService'; @@ -22,7 +21,7 @@ export class PipEnvService extends CacheableLocatorService implements IPipEnvSer private readonly processServiceFactory: IProcessServiceFactory; private readonly workspace: IWorkspaceService; private readonly fs: IFileSystem; - private readonly envVarsProvider: IEnvironmentVariablesProvider; + private readonly logger: ILogger; constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { super('PipEnvService', serviceContainer); @@ -30,7 +29,7 @@ export class PipEnvService extends CacheableLocatorService implements IPipEnvSer this.processServiceFactory = this.serviceContainer.get(IProcessServiceFactory); this.workspace = this.serviceContainer.get(IWorkspaceService); this.fs = this.serviceContainer.get(IFileSystem); - this.envVarsProvider = this.serviceContainer.get(IEnvironmentVariablesProvider); + this.logger = this.serviceContainer.get(ILogger); } // tslint:disable-next-line:no-empty public dispose() { } @@ -127,7 +126,7 @@ export class PipEnvService extends CacheableLocatorService implements IPipEnvSer } // tslint:disable-next-line:no-empty } catch (error) { - console.error(error); + this.logger.logWarning('Error in invoking PipEnv', error); const errorMessage = error.message || error; const appShell = this.serviceContainer.get(IApplicationShell); appShell.showWarningMessage(`Workspace contains pipfile but attempt to run 'pipenv --venv' failed with '${errorMessage}'. Make sure pipenv is on the PATH.`); diff --git a/src/test/debugger/configProvider/provider.test.ts b/src/test/debugger/configProvider/provider.test.ts index fbc51cd3c762..187b78e87a58 100644 --- a/src/test/debugger/configProvider/provider.test.ts +++ b/src/test/debugger/configProvider/provider.test.ts @@ -13,7 +13,7 @@ import { IApplicationShell, IDocumentManager, IWorkspaceService } from '../../.. import { PYTHON_LANGUAGE } from '../../../client/common/constants'; import { IFileSystem, IPlatformService } from '../../../client/common/platform/types'; import { IPythonExecutionFactory, IPythonExecutionService } from '../../../client/common/process/types'; -import { IConfigurationService, IPythonSettings } from '../../../client/common/types'; +import { IConfigurationService, ILogger, IPythonSettings } from '../../../client/common/types'; import { PythonDebugConfigurationProvider, PythonV2DebugConfigurationProvider } from '../../../client/debugger'; import { DebugOptions, LaunchRequestArguments } from '../../../client/debugger/Common/Contracts'; import { PythonLaunchDebugConfiguration } from '../../../client/debugger/configProviders/baseProvider'; @@ -32,6 +32,7 @@ import { IServiceContainer } from '../../../client/ioc/types'; let fileSystem: TypeMoq.IMock; let appShell: TypeMoq.IMock; let pythonExecutionService: TypeMoq.IMock; + let logger: TypeMoq.IMock; setup(() => { serviceContainer = TypeMoq.Mock.ofType(); debugProvider = new provider.class(serviceContainer.object); @@ -46,6 +47,7 @@ import { IServiceContainer } from '../../../client/ioc/types'; platformService = TypeMoq.Mock.ofType(); fileSystem = TypeMoq.Mock.ofType(); appShell = TypeMoq.Mock.ofType(); + logger = TypeMoq.Mock.ofType(); pythonExecutionService = TypeMoq.Mock.ofType(); pythonExecutionService.setup((x: any) => x.then).returns(() => undefined); @@ -58,6 +60,7 @@ import { IServiceContainer } from '../../../client/ioc/types'; serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IFileSystem))).returns(() => fileSystem.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IApplicationShell))).returns(() => appShell.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationProviderUtils))).returns(() => new ConfigurationProviderUtils(serviceContainer.object)); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ILogger))).returns(() => logger.object); const settings = TypeMoq.Mock.ofType(); settings.setup(s => s.pythonPath).returns(() => pythonPath); @@ -350,6 +353,8 @@ import { IServiceContainer } from '../../../client/ioc/types'; .verifiable(TypeMoq.Times.exactly(pyramidExists && addPyramidDebugOption ? 1 : 0)); appShell.setup(a => a.showErrorMessage(TypeMoq.It.isAny())) .verifiable(TypeMoq.Times.exactly(pyramidExists || !addPyramidDebugOption ? 0 : 1)); + logger.setup(a => a.logError(TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .verifiable(TypeMoq.Times.exactly(pyramidExists || !addPyramidDebugOption ? 0 : 1)); const options = addPyramidDebugOption ? { debugOptions: [DebugOptions.Pyramid], pyramid: true } : {}; const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, options as any as DebugConfiguration); @@ -366,6 +371,7 @@ import { IServiceContainer } from '../../../client/ioc/types'; pythonExecutionService.verifyAll(); fileSystem.verifyAll(); appShell.verifyAll(); + logger.verifyAll(); } test('Program is set for Pyramid (windows)', async () => { await testPyramidConfiguration(true, false, false); diff --git a/src/test/interpreters/condaService.test.ts b/src/test/interpreters/condaService.test.ts index 10a3db6c6fc0..d16f7512ea3c 100644 --- a/src/test/interpreters/condaService.test.ts +++ b/src/test/interpreters/condaService.test.ts @@ -37,8 +37,9 @@ suite('Interpreters Conda Service', () => { let registryInterpreterLocatorService: TypeMoq.IMock; let serviceContainer: TypeMoq.IMock; let procServiceFactory: TypeMoq.IMock; + let logger: TypeMoq.IMock; setup(async () => { - const logger = TypeMoq.Mock.ofType(); + logger = TypeMoq.Mock.ofType(); processService = TypeMoq.Mock.ofType(); platformService = TypeMoq.Mock.ofType(); registryInterpreterLocatorService = TypeMoq.Mock.ofType(); @@ -415,7 +416,6 @@ suite('Interpreters Conda Service', () => { test('Returns conda environments when conda exists', async () => { const stateFactory = TypeMoq.Mock.ofType(); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPersistentStateFactory))).returns(() => stateFactory.object); - // tslint:disable-next-line:no-any const state = new MockState(undefined); stateFactory.setup(s => s.createGlobalPersistentState(TypeMoq.It.isValue('CONDA_ENVIRONMENTS'), TypeMoq.It.isValue(undefined))).returns(() => state); @@ -425,10 +425,24 @@ suite('Interpreters Conda Service', () => { assert.equal(environments, undefined, 'Conda environments do not match'); }); + test('Logs information message when conda does not exist', async () => { + const stateFactory = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPersistentStateFactory))).returns(() => stateFactory.object); + const state = new MockState(undefined); + stateFactory.setup(s => s.createGlobalPersistentState(TypeMoq.It.isValue('CONDA_ENVIRONMENTS'), TypeMoq.It.isValue(undefined))).returns(() => state); + + processService.setup(p => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())).returns(() => Promise.reject(new Error('Not Found'))); + processService.setup(p => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['env', 'list']), TypeMoq.It.isAny())).returns(() => Promise.reject(new Error('Not Found'))); + logger.setup(l => l.logInformation(TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .verifiable(TypeMoq.Times.once()); + const environments = await condaService.getCondaEnvironments(true); + assert.equal(environments, undefined, 'Conda environments do not match'); + logger.verifyAll(); + }); + test('Returns cached conda environments', async () => { const stateFactory = TypeMoq.Mock.ofType(); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPersistentStateFactory))).returns(() => stateFactory.object); - // tslint:disable-next-line:no-any const state = new MockState({ data: 'CachedInfo' }); stateFactory.setup(s => s.createGlobalPersistentState(TypeMoq.It.isValue('CONDA_ENVIRONMENTS'), TypeMoq.It.isValue(undefined))).returns(() => state); @@ -441,7 +455,6 @@ suite('Interpreters Conda Service', () => { test('Subsequent list of environments will be retrieved from cache', async () => { const stateFactory = TypeMoq.Mock.ofType(); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPersistentStateFactory))).returns(() => stateFactory.object); - // tslint:disable-next-line:no-any const state = new MockState(undefined); stateFactory.setup(s => s.createGlobalPersistentState(TypeMoq.It.isValue('CONDA_ENVIRONMENTS'), TypeMoq.It.isValue(undefined))).returns(() => state); diff --git a/src/test/interpreters/pipEnvService.test.ts b/src/test/interpreters/pipEnvService.test.ts index 39763455f3a6..fdd6a1c6b9e0 100644 --- a/src/test/interpreters/pipEnvService.test.ts +++ b/src/test/interpreters/pipEnvService.test.ts @@ -13,7 +13,7 @@ import { IApplicationShell, IWorkspaceService } from '../../client/common/applic import { EnumEx } from '../../client/common/enumUtils'; import { IFileSystem } from '../../client/common/platform/types'; import { IProcessService, IProcessServiceFactory } from '../../client/common/process/types'; -import { ICurrentProcess, IPersistentState, IPersistentStateFactory } from '../../client/common/types'; +import { ICurrentProcess, ILogger, IPersistentState, IPersistentStateFactory } from '../../client/common/types'; import { IEnvironmentVariablesProvider } from '../../client/common/variables/types'; import { IInterpreterHelper, IInterpreterLocatorService } from '../../client/interpreter/contracts'; import { PipEnvService } from '../../client/interpreter/locators/services/pipEnvService'; @@ -39,6 +39,7 @@ suite('Interpreters - PipEnv', () => { let persistentStateFactory: TypeMoq.IMock; let envVarsProvider: TypeMoq.IMock; let procServiceFactory: TypeMoq.IMock; + let logger: TypeMoq.IMock; setup(() => { serviceContainer = TypeMoq.Mock.ofType(); const workspaceService = TypeMoq.Mock.ofType(); @@ -50,6 +51,7 @@ suite('Interpreters - PipEnv', () => { persistentStateFactory = TypeMoq.Mock.ofType(); envVarsProvider = TypeMoq.Mock.ofType(); procServiceFactory = TypeMoq.Mock.ofType(); + logger = TypeMoq.Mock.ofType(); processService.setup((x: any) => x.then).returns(() => undefined); procServiceFactory.setup(p => p.create(TypeMoq.It.isAny())).returns(() => Promise.resolve(processService.object)); @@ -73,6 +75,7 @@ suite('Interpreters - PipEnv', () => { serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IApplicationShell))).returns(() => appShell.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPersistentStateFactory))).returns(() => persistentStateFactory.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IEnvironmentVariablesProvider))).returns(() => envVarsProvider.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ILogger))).returns(() => logger.object); pipEnvService = new PipEnvService(serviceContainer.object); }); @@ -97,11 +100,12 @@ suite('Interpreters - PipEnv', () => { processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.reject('')); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(true)); appShell.setup(a => a.showWarningMessage(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('')).verifiable(TypeMoq.Times.once()); + logger.setup(l => l.logWarning(TypeMoq.It.isAny(), TypeMoq.It.isAny())).verifiable(TypeMoq.Times.once()); const environments = await pipEnvService.getInterpreters(resource); expect(environments).to.be.deep.equal([]); appShell.verifyAll(); - appShell.verifyAll(); + logger.verifyAll(); }); test(`Should display warning message if there is a \'PipFile\' but \'pipenv --venv\' failes with stderr ${testSuffix}`, async () => { const env = {}; @@ -109,10 +113,12 @@ suite('Interpreters - PipEnv', () => { processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stderr: 'PipEnv Failed', stdout: '' })); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(true)); appShell.setup(a => a.showWarningMessage(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('')).verifiable(TypeMoq.Times.once()); + logger.setup(l => l.logWarning(TypeMoq.It.isAny(), TypeMoq.It.isAny())).verifiable(TypeMoq.Times.once()); const environments = await pipEnvService.getInterpreters(resource); expect(environments).to.be.deep.equal([]); appShell.verifyAll(); + logger.verifyAll(); }); test(`Should return interpreter information${testSuffix}`, async () => { const env = {}; From 128ae488c3b911e787b2f034bc3cef52791efdb4 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 5 Jun 2018 13:05:12 -0700 Subject: [PATCH 294/433] Fix debugger issue that causes the debugger to hang and silently exit (#1823) --- news/2 Fixes/459.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/2 Fixes/459.md diff --git a/news/2 Fixes/459.md b/news/2 Fixes/459.md new file mode 100644 index 000000000000..bba743f3c26f --- /dev/null +++ b/news/2 Fixes/459.md @@ -0,0 +1 @@ +Fix debugger issue that causes the debugger to hang and silently exit stepping over a line of code instantiating an ITK vector object. From 864363769e2c536110e522e7e9e6e6ed3d24adf5 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 5 Jun 2018 13:05:28 -0700 Subject: [PATCH 295/433] When debugging unit tests, use the envFile in settings (#1825) Fixes #1759 --- news/2 Fixes/1759.md | 1 + src/client/unittests/common/debugLauncher.ts | 6 +++--- src/test/unittests/common/debugLauncher.test.ts | 7 +++++-- 3 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 news/2 Fixes/1759.md diff --git a/news/2 Fixes/1759.md b/news/2 Fixes/1759.md new file mode 100644 index 000000000000..4938502ae95f --- /dev/null +++ b/news/2 Fixes/1759.md @@ -0,0 +1 @@ +When debugging unit tests, use the `env` file configured in `settings.json` under `python.envFile`. diff --git a/src/client/unittests/common/debugLauncher.ts b/src/client/unittests/common/debugLauncher.ts index 12cc2f59cb83..79654590eaf8 100644 --- a/src/client/unittests/common/debugLauncher.ts +++ b/src/client/unittests/common/debugLauncher.ts @@ -26,13 +26,12 @@ export class DebugLauncher implements ITestDebugLauncher { } const cwd = cwdUri ? cwdUri.fsPath : workspaceFolder.uri.fsPath; - const configurationService = this.serviceContainer.get(IConfigurationService).getSettings(Uri.file(cwd)); - const useExperimentalDebugger = configurationService.unitTest.useExperimentalDebugger === true; + const configSettings = this.serviceContainer.get(IConfigurationService).getSettings(Uri.file(cwd)); + const useExperimentalDebugger = configSettings.unitTest.useExperimentalDebugger === true; const debugManager = this.serviceContainer.get(IDebugService); const debuggerType = useExperimentalDebugger ? 'pythonExperimental' : 'python'; const debugArgs = this.fixArgs(options.args, options.testProvider, useExperimentalDebugger); const program = this.getTestLauncherScript(options.testProvider, useExperimentalDebugger); - return debugManager.startDebugging(workspaceFolder, { name: 'Debug Unit Test', type: debuggerType, @@ -41,6 +40,7 @@ export class DebugLauncher implements ITestDebugLauncher { cwd, args: debugArgs, console: 'none', + envFile: configSettings.envFile, debugOptions: [DebugOptions.RedirectOutput] }).then(() => void (0)); } diff --git a/src/test/unittests/common/debugLauncher.test.ts b/src/test/unittests/common/debugLauncher.test.ts index 82a6299e4b83..c4d5a3520684 100644 --- a/src/test/unittests/common/debugLauncher.test.ts +++ b/src/test/unittests/common/debugLauncher.test.ts @@ -27,6 +27,7 @@ suite('Unit Tests - Debug Launcher', () => { let debugLauncher: DebugLauncher; let debugService: TypeMoq.IMock; let workspaceService: TypeMoq.IMock; + let settings: TypeMoq.IMock; setup(async () => { const serviceContainer = TypeMoq.Mock.ofType(); const configService = TypeMoq.Mock.ofType(); @@ -38,7 +39,7 @@ suite('Unit Tests - Debug Launcher', () => { workspaceService = TypeMoq.Mock.ofType(); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IWorkspaceService))).returns(() => workspaceService.object); - const settings = TypeMoq.Mock.ofType(); + settings = TypeMoq.Mock.ofType(); configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => settings.object); unitTestSettings = TypeMoq.Mock.ofType(); @@ -51,10 +52,12 @@ suite('Unit Tests - Debug Launcher', () => { args: string[], console, debugOptions: DebugOptions[], testProvider: TestProvider, useExperimentalDebugger: boolean) { + const envFile = __filename; + settings.setup(p => p.envFile).returns(() => envFile); const debugArgs = testProvider === 'unittest' && useExperimentalDebugger ? args.filter(item => item !== '--debug') : args; debugService.setup(d => d.startDebugging(TypeMoq.It.isValue(workspaceFolder), - TypeMoq.It.isObjectWith({ name, type, request, program, cwd, args: debugArgs, console, debugOptions }))) + TypeMoq.It.isObjectWith({ name, type, request, program, cwd, args: debugArgs, console, envFile, debugOptions }))) .returns(() => Promise.resolve(undefined as any)) .verifiable(TypeMoq.Times.once()); } From a0ad1f22e1f60ef92dafb2939ba1de517e78fbaa Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 5 Jun 2018 13:05:37 -0700 Subject: [PATCH 296/433] Add path mappings for remote debugging when attaching to the localhost (#1830) --- news/2 Fixes/1829.md | 1 + .../configProviders/pythonV2Provider.ts | 15 ++++++- .../configProvider/provider.attach.test.ts | 43 +++++++++++++++++-- 3 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 news/2 Fixes/1829.md diff --git a/news/2 Fixes/1829.md b/news/2 Fixes/1829.md new file mode 100644 index 000000000000..de0e75d4e4db --- /dev/null +++ b/news/2 Fixes/1829.md @@ -0,0 +1 @@ +Automatically add path mappings for remote debugging when attaching to the localhost. diff --git a/src/client/debugger/configProviders/pythonV2Provider.ts b/src/client/debugger/configProviders/pythonV2Provider.ts index 89ac776dddd4..8644f5daea9a 100644 --- a/src/client/debugger/configProviders/pythonV2Provider.ts +++ b/src/client/debugger/configProviders/pythonV2Provider.ts @@ -16,7 +16,7 @@ export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvide constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { super('pythonExperimental', serviceContainer); } - protected async provideLaunchDefaults(workspaceFolder: Uri, debugConfiguration: PythonLaunchDebugConfiguration): Promise { + protected async provideLaunchDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonLaunchDebugConfiguration): Promise { await super.provideLaunchDefaults(workspaceFolder, debugConfiguration); const debugOptions = debugConfiguration.debugOptions!; if (debugConfiguration.debugStdLib) { @@ -48,7 +48,8 @@ export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvide debugConfiguration.program = (await utils.getPyramidStartupScriptFilePath(workspaceFolder))!; } } - protected async provideAttachDefaults(workspaceFolder: Uri, debugConfiguration: PythonAttachDebugConfiguration): Promise { + // tslint:disable-next-line:cyclomatic-complexity + protected async provideAttachDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonAttachDebugConfiguration): Promise { await super.provideAttachDefaults(workspaceFolder, debugConfiguration); const debugOptions = debugConfiguration.debugOptions!; if (debugConfiguration.debugStdLib) { @@ -82,12 +83,22 @@ export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvide if (!debugConfiguration.pathMappings) { debugConfiguration.pathMappings = []; } + // This is for backwards compatibility. if (debugConfiguration.localRoot && debugConfiguration.remoteRoot) { debugConfiguration.pathMappings!.push({ localRoot: debugConfiguration.localRoot, remoteRoot: debugConfiguration.remoteRoot }); } + // If attaching to local host, then always map local root and remote roots. + if (workspaceFolder && debugConfiguration.host && + debugConfiguration.pathMappings!.length === 0 && + ['LOCALHOST', '127.0.0.1', '::1'].indexOf(debugConfiguration.host.toUpperCase()) >= 0) { + debugConfiguration.pathMappings!.push({ + localRoot: workspaceFolder.fsPath, + remoteRoot: workspaceFolder.fsPath + }); + } } private debugOption(debugOptions: DebugOptions[], debugOption: DebugOptions) { if (debugOptions.indexOf(debugOption) >= 0) { diff --git a/src/test/debugger/configProvider/provider.attach.test.ts b/src/test/debugger/configProvider/provider.attach.test.ts index 0c3d2b297ddb..d0e358591352 100644 --- a/src/test/debugger/configProvider/provider.attach.test.ts +++ b/src/test/debugger/configProvider/provider.attach.test.ts @@ -14,7 +14,7 @@ import { PYTHON_LANGUAGE } from '../../../client/common/constants'; import { EnumEx } from '../../../client/common/enumUtils'; import { IFileSystem, IPlatformService } from '../../../client/common/platform/types'; import { PythonDebugConfigurationProvider, PythonV2DebugConfigurationProvider } from '../../../client/debugger'; -import { DebugOptions } from '../../../client/debugger/Common/Contracts'; +import { AttachRequestArguments, DebugOptions } from '../../../client/debugger/Common/Contracts'; import { IServiceContainer } from '../../../client/ioc/types'; enum OS { @@ -164,9 +164,44 @@ enum OS { const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { localRoot, request: 'attach' } as any as DebugConfiguration); expect(debugConfig).to.have.property('localRoot', localRoot); - if (provider.debugType === 'pythonExperimental') { - expect(debugConfig!.pathMappings).to.be.lengthOf(0); - } + }); + ['localhost', '127.0.0.1', '::1'].forEach(host => { + test(`Ensure path mappings are automatically added when host is '${host}'`, async () => { + const activeFile = 'xyz.py'; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + setupActiveEditor(activeFile, PYTHON_LANGUAGE); + const defaultWorkspace = path.join('usr', 'desktop'); + setupWorkspaces([defaultWorkspace]); + + const localRoot = `Debug_PythonPath_${new Date().toString()}`; + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { localRoot, host, request: 'attach' } as any as DebugConfiguration); + + expect(debugConfig).to.have.property('localRoot', localRoot); + if (provider.debugType === 'pythonExperimental') { + const pathMappings = (debugConfig as AttachRequestArguments).pathMappings; + expect(pathMappings).to.be.lengthOf(1); + expect(pathMappings![0].localRoot).to.be.equal(workspaceFolder.uri.fsPath); + expect(pathMappings![0].remoteRoot).to.be.equal(workspaceFolder.uri.fsPath); + } + }); + }); + ['192.168.1.123', 'don.debugger.com'].forEach(host => { + test(`Ensure path mappings are not automatically added when host is '${host}'`, async () => { + const activeFile = 'xyz.py'; + const workspaceFolder = createMoqWorkspaceFolder(__dirname); + setupActiveEditor(activeFile, PYTHON_LANGUAGE); + const defaultWorkspace = path.join('usr', 'desktop'); + setupWorkspaces([defaultWorkspace]); + + const localRoot = `Debug_PythonPath_${new Date().toString()}`; + const debugConfig = await debugProvider.resolveDebugConfiguration!(workspaceFolder, { localRoot, host, request: 'attach' } as any as DebugConfiguration); + + expect(debugConfig).to.have.property('localRoot', localRoot); + if (provider.debugType === 'pythonExperimental') { + const pathMappings = (debugConfig as AttachRequestArguments).pathMappings; + expect(pathMappings).to.be.lengthOf(0); + } + }); }); test('Ensure \'localRoot\' and \'remoteRoot\' is used', async function () { if (provider.debugType !== 'pythonExperimental') { From f865da077c3f4b039dc3efbbd213a042c7eeb05c Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 5 Jun 2018 13:06:23 -0700 Subject: [PATCH 297/433] Update parso to 0.2.1 (#1841) Fixes #1833 Fixes #1721 --- news/2 Fixes/1721.md | 1 + news/3 Code Health/1833.md | 1 + pythonFiles/parso/__init__.py | 2 +- pythonFiles/parso/grammar.py | 7 +- pythonFiles/parso/python/diff.py | 5 +- pythonFiles/parso/python/issue_list.txt | 176 ------------------------ pythonFiles/parso/python/tokenize.py | 21 ++- pythonFiles/parso/python/tree.py | 8 +- 8 files changed, 29 insertions(+), 192 deletions(-) create mode 100644 news/2 Fixes/1721.md create mode 100644 news/3 Code Health/1833.md delete mode 100644 pythonFiles/parso/python/issue_list.txt diff --git a/news/2 Fixes/1721.md b/news/2 Fixes/1721.md new file mode 100644 index 000000000000..8b733365272c --- /dev/null +++ b/news/2 Fixes/1721.md @@ -0,0 +1 @@ +Fix for intellisense failing when using the new `Outline` feature. diff --git a/news/3 Code Health/1833.md b/news/3 Code Health/1833.md new file mode 100644 index 000000000000..e9a49948e14a --- /dev/null +++ b/news/3 Code Health/1833.md @@ -0,0 +1 @@ +Update `parso` package to 0.2.1. diff --git a/pythonFiles/parso/__init__.py b/pythonFiles/parso/__init__.py index c4cce53ea690..9654389dea9f 100644 --- a/pythonFiles/parso/__init__.py +++ b/pythonFiles/parso/__init__.py @@ -43,7 +43,7 @@ from parso.utils import split_lines, python_bytes_to_unicode -__version__ = '0.2.0' +__version__ = '0.2.1' def parse(code=None, **kwargs): diff --git a/pythonFiles/parso/grammar.py b/pythonFiles/parso/grammar.py index c825b5554c0e..6c13f002f90d 100644 --- a/pythonFiles/parso/grammar.py +++ b/pythonFiles/parso/grammar.py @@ -20,7 +20,7 @@ class Grammar(object): """ :py:func:`parso.load_grammar` returns instances of this class. - Creating custom grammars by calling this is not supported, yet. + Creating custom none-python grammars by calling this is not supported, yet. """ #:param text: A BNF representation of your grammar. _error_normalizer_config = None @@ -219,12 +219,13 @@ def load_grammar(**kwargs): version. :param str version: A python version string, e.g. ``version='3.3'``. + :param str path: A path to a grammar file """ - def load_grammar(language='python', version=None): + def load_grammar(language='python', version=None, path=None): if language == 'python': version_info = parse_version_string(version) - file = os.path.join( + file = path or os.path.join( 'python', 'grammar%s%s.txt' % (version_info.major, version_info.minor) ) diff --git a/pythonFiles/parso/python/diff.py b/pythonFiles/parso/python/diff.py index 96c6e5f2ca41..f8b73c75d3d5 100644 --- a/pythonFiles/parso/python/diff.py +++ b/pythonFiles/parso/python/diff.py @@ -490,6 +490,9 @@ def _copy_nodes(self, tos, nodes, until_line, line_offset): new_tos = tos for node in nodes: + if node.start_pos[0] > until_line: + break + if node.type == 'endmarker': # We basically removed the endmarker, but we are not allowed to # remove the newline at the end of the line, otherwise it's @@ -501,8 +504,6 @@ def _copy_nodes(self, tos, nodes, until_line, line_offset): # Endmarkers just distort all the checks below. Remove them. break - if node.start_pos[0] > until_line: - break # TODO this check might take a bit of time for large files. We # might want to change this to do more intelligent guessing or # binary search. diff --git a/pythonFiles/parso/python/issue_list.txt b/pythonFiles/parso/python/issue_list.txt deleted file mode 100644 index e5e2c9dda764..000000000000 --- a/pythonFiles/parso/python/issue_list.txt +++ /dev/null @@ -1,176 +0,0 @@ -A list of syntax/indentation errors I've encountered in CPython. - -# Python/compile.c - "'continue' not properly in loop" - "'continue' not supported inside 'finally' clause" # Until loop - "default 'except:' must be last" - "from __future__ imports must occur at the beginning of the file" - "'return' outside function" - "'return' with value in async generator" - "'break' outside loop" - "two starred expressions in assignment" - "asynchronous comprehension outside of an asynchronous function" - "'yield' outside function" # For both yield and yield from - "'yield from' inside async function" - "'await' outside function" - "'await' outside async function" - "starred assignment target must be in a list or tuple" - "can't use starred expression here" - "too many statically nested blocks" # Max. 20 - # This is one of the few places in the cpython code base that I really - # don't understand. It feels a bit hacky if you look at the implementation - # of UNPACK_EX. - "too many expressions in star-unpacking assignment" - - # Just ignore this one, newer versions will not be affected anymore and - # it's a limit of 2^16 - 1. - "too many annotations" # Only python 3.0 - 3.5, 3.6 is not affected. - -# Python/ast.c - # used with_item exprlist expr_stmt - "can't %s %s" % ("assign to" or "delete", - "lambda" - "function call" # foo() - "generator expression" - "list comprehension" - "set comprehension" - "dict comprehension" - "keyword" - "Ellipsis" - "comparison" - Dict: Set: Num: Str: Bytes: JoinedStr: FormattedValue: - "literal" - BoolOp: BinOp: UnaryOp: - "operator" - Yield: YieldFrom: - "yield expression" - Await: - "await expression" - IfExp: - "conditional expression" - "assignment to keyword" # (keywords + __debug__) # None = 2 - "named arguments must follow bare *" # def foo(*): pass - "non-default argument follows default argument" # def f(x=3, y): pass - "iterable unpacking cannot be used in comprehension" # [*[] for a in [1]] - "dict unpacking cannot be used in dict comprehension" # {**{} for a in [1]} - "Generator expression must be parenthesized if not sole argument" # foo(x for x in [], b) - "positional argument follows keyword argument unpacking" # f(**x, y) >= 3.5 - "positional argument follows keyword argument" # f(x=2, y) >= 3.5 - "iterable argument unpacking follows keyword argument unpacking" # foo(**kwargs, *args) - "lambda cannot contain assignment" # f(lambda: 1=1) - "keyword can't be an expression" # f(+x=1) - "keyword argument repeated" # f(x=1, x=2) - "illegal expression for augmented assignment" # x, y += 1 - "only single target (not list) can be annotated" # [x, y]: int - "only single target (not tuple) can be annotated" # x, y: str - "illegal target for annotation" # True: 1` - "trailing comma not allowed without surrounding parentheses" # from foo import a, - "bytes can only contain ASCII literal characters." # b'ä' # prob. only python 3 - "cannot mix bytes and nonbytes literals" # 's' b'' - "assignment to yield expression not possible" # x = yield 1 = 3 - - "f-string: empty expression not allowed" # f'{}' - "f-string: single '}' is not allowed" # f'}' - "f-string: expressions nested too deeply" # f'{1:{5:{3}}}' - "f-string expression part cannot include a backslash" # f'{"\"}' or f'{"\\"}' - "f-string expression part cannot include '#'" # f'{#}' - "f-string: unterminated string" # f'{"}' - "f-string: mismatched '(', '{', or '['" - "f-string: invalid conversion character: expected 's', 'r', or 'a'" # f'{1!b}' - "f-string: unexpected end of string" # Doesn't really happen?! - "f-string: expecting '}'" # f'{' - "(unicode error) unknown error - "(value error) unknown error - "(unicode error) MESSAGE - MESSAGES = { - "\\ at end of string" - "truncated \\xXX escape" - "truncated \\uXXXX escape" - "truncated \\UXXXXXXXX escape" - "illegal Unicode character" # '\Uffffffff' - "malformed \\N character escape" # '\N{}' - "unknown Unicode character name" # '\N{foo}' - } - "(value error) MESSAGE # bytes - MESSAGES = { - "Trailing \\ in string" - "invalid \\x escape at position %d" - } - - "invalid escape sequence \\%c" # Only happens when used in `python -W error` - "unexpected node" # Probably irrelevant - "Unexpected node-type in from-import" # Irrelevant, doesn't happen. - "malformed 'try' statement" # Irrelevant, doesn't happen. - -# Python/symtable.c - "duplicate argument '%U' in function definition" - "name '%U' is assigned to before global declaration" - "name '%U' is assigned to before nonlocal declaration" - "name '%U' is used prior to global declaration" - "name '%U' is used prior to nonlocal declaration" - "annotated name '%U' can't be global" - "annotated name '%U' can't be nonlocal" - "import * only allowed at module level" - - "name '%U' is parameter and global", - "name '%U' is nonlocal and global", - "name '%U' is parameter and nonlocal", - - "nonlocal declaration not allowed at module level"); - "no binding for nonlocal '%U' found", - # RecursionError. Not handled. For all human written code, this is probably - # not an issue. eval("()"*x) with x>=2998 for example fails, but that's - # more than 2000 executions on one line. - "maximum recursion depth exceeded during compilation"); - -# Python/future.c - "not a chance" - "future feature %.100s is not defined" - "from __future__ imports must occur at the beginning of the file" # Also in compile.c - -# Parser/tokenizer.c - # All the following issues seem to be irrelevant for parso, because the - # encoding stuff is done before it reaches the tokenizer. It's already - # unicode at that point. - "encoding problem: %s" - "encoding problem: %s with BOM" - "Non-UTF-8 code starting with '\\x%.2x' in file %U on line %i, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details" - -# Parser/pythonrun.c - E_SYNTAX: "invalid syntax" - E_LINECONT: "unexpected character after line continuation character" - E_IDENTIFIER: "invalid character in identifier" - # Also just use 'invalid syntax'. Happens mostly with stuff like `(`. This - # message doesn't really help the user, because it only appears very - # randomly, e.g. `(or` wouldn't yield this error. - E_EOF: "unexpected EOF while parsing" - # Even in 3.6 this is implemented kind of shaky. Not implemented, I think - # cPython needs to fix this one first. - # e.g. `ast.parse('def x():\n\t if 1:\n \t \tpass')` works :/ - E_TABSPACE: "inconsistent use of tabs and spaces in indentation" - # Ignored, just shown as "invalid syntax". The error has mostly to do with - # numbers like 0b2 everywhere or 1.6_ in Python3.6. - E_TOKEN: "invalid token" - E_EOFS: "EOF while scanning triple-quoted string literal" - E_EOLS: "EOL while scanning string literal" - - # IndentationError - E_DEDENT: "unindent does not match any outer indentation level" - E_TOODEEP: "too many levels of indentation" # 100 levels - E_SYNTAX: "expected an indented block" - "unexpected indent" - # I don't think this actually ever happens. - "unexpected unindent" - - - # Irrelevant for parso for now. - E_OVERFLOW: "expression too long" - E_DECODE: "unknown decode error" - E_BADSINGLE: "multiple statements found while compiling a single statement" - - -Version specific: -Python 3.5: - 'yield' inside async function -Python 3.3/3.4: - can use starred expression only as assignment target diff --git a/pythonFiles/parso/python/tokenize.py b/pythonFiles/parso/python/tokenize.py index 31f081d9b804..0ac8a8d5275f 100644 --- a/pythonFiles/parso/python/tokenize.py +++ b/pythonFiles/parso/python/tokenize.py @@ -28,7 +28,8 @@ TokenCollection = namedtuple( 'TokenCollection', - 'pseudo_token single_quoted triple_quoted endpats fstring_pattern_map always_break_tokens', + 'pseudo_token single_quoted triple_quoted endpats whitespace ' + 'fstring_pattern_map always_break_tokens', ) BOM_UTF8_STRING = BOM_UTF8.decode('utf-8') @@ -114,6 +115,7 @@ def _create_token_collection(version_info): # Note: we use unicode matching for names ("\w") but ascii matching for # number literals. Whitespace = r'[ \f\t]*' + whitespace = _compile(Whitespace) Comment = r'#[^\r\n]*' Name = r'\w+' @@ -225,7 +227,7 @@ def _create_token_collection(version_info): pseudo_token_compiled = _compile(PseudoToken) return TokenCollection( pseudo_token_compiled, single_quoted, triple_quoted, endpats, - fstring_pattern_map, ALWAYS_BREAK_TOKENS + whitespace, fstring_pattern_map, ALWAYS_BREAK_TOKENS ) @@ -244,7 +246,7 @@ def _get_type_name(self, exact=True): return tok_name[self.type] def __repr__(self): - return ('TokenInfo(type=%s, string=%r, start=%r, prefix=%r)' % + return ('TokenInfo(type=%s, string=%r, start_pos=%r, prefix=%r)' % self._replace(type=self._get_type_name())) @@ -354,7 +356,8 @@ def tokenize_lines(lines, version_info, start_pos=(1, 0)): token. This idea comes from lib2to3. The prefix contains all information that is irrelevant for the parser like newlines in parentheses or comments. """ - pseudo_token, single_quoted, triple_quoted, endpats, fstring_pattern_map, always_break_tokens, = \ + pseudo_token, single_quoted, triple_quoted, endpats, whitespace, \ + fstring_pattern_map, always_break_tokens, = \ _get_token_collection(version_info) paren_level = 0 # count parentheses indents = [0] @@ -435,10 +438,14 @@ def tokenize_lines(lines, version_info, start_pos=(1, 0)): pseudomatch = pseudo_token.match(line, pos) if not pseudomatch: # scan for tokens - txt = line[pos:] - if txt.endswith('\n'): + if line.endswith('\n'): new_line = True - yield PythonToken(ERRORTOKEN, txt, (lnum, pos), additional_prefix) + match = whitespace.match(line, pos) + pos = match.end() + yield PythonToken( + ERRORTOKEN, line[pos:], (lnum, pos), + additional_prefix + match.group(0) + ) additional_prefix = '' break diff --git a/pythonFiles/parso/python/tree.py b/pythonFiles/parso/python/tree.py index e2bf010bdff0..f6b4dd38fad0 100644 --- a/pythonFiles/parso/python/tree.py +++ b/pythonFiles/parso/python/tree.py @@ -60,7 +60,6 @@ _IMPORTS = set(['import_name', 'import_from']) - class DocstringMixin(object): __slots__ = () @@ -133,7 +132,6 @@ def get_start_pos_of_prefix(self): return previous_leaf.end_pos - class _LeafWithoutNewlines(PythonLeaf): """ Simply here to optimize performance. @@ -166,6 +164,10 @@ class EndMarker(_LeafWithoutNewlines): __slots__ = () type = 'endmarker' + @utf8_repr + def __repr__(self): + return "<%s: prefix=%s>" % (type(self).__name__, repr(self.prefix)) + class Newline(PythonLeaf): """Contains NEWLINE and ENDMARKER tokens.""" @@ -235,7 +237,6 @@ def get_definition(self, import_name_always=False): return None - class Literal(PythonLeaf): __slots__ = () @@ -653,6 +654,7 @@ def annotation(self): except IndexError: return None + class Lambda(Function): """ Lambdas are basically trimmed functions, so give it the same interface. From a880f575d8df411eb243938408a58aefa43ca4dc Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 5 Jun 2018 13:06:35 -0700 Subject: [PATCH 298/433] Update isort to latest version (#1843) * Delete isort * Update `isort` package to 4.3.4 --- news/3 Code Health/1842.md | 1 + pythonFiles/isort/__init__.py | 6 +- pythonFiles/isort/__main__.py | 2 + pythonFiles/isort/isort.py | 310 ++++++++++++++++++++------------- pythonFiles/isort/main.py | 238 ++++++++++++++++--------- pythonFiles/isort/natural.py | 2 +- pythonFiles/isort/pie_slice.py | 210 ++-------------------- pythonFiles/isort/settings.py | 27 ++- 8 files changed, 388 insertions(+), 408 deletions(-) create mode 100644 news/3 Code Health/1842.md mode change 100755 => 100644 pythonFiles/isort/main.py diff --git a/news/3 Code Health/1842.md b/news/3 Code Health/1842.md new file mode 100644 index 000000000000..1219cc348f7d --- /dev/null +++ b/news/3 Code Health/1842.md @@ -0,0 +1 @@ +Update `isort` package to 4.3.4 diff --git a/pythonFiles/isort/__init__.py b/pythonFiles/isort/__init__.py index 3063d1ed92d8..4f1adaa05bf0 100644 --- a/pythonFiles/isort/__init__.py +++ b/pythonFiles/isort/__init__.py @@ -22,7 +22,7 @@ from __future__ import absolute_import, division, print_function, unicode_literals -from . import settings -from .isort import SortImports +from . import settings # noqa: F401 +from .isort import SortImports # noqa: F401 -__version__ = "4.2.15" +__version__ = "4.3.4" diff --git a/pythonFiles/isort/__main__.py b/pythonFiles/isort/__main__.py index 94b1d057bb0c..186c98e85ea7 100644 --- a/pythonFiles/isort/__main__.py +++ b/pythonFiles/isort/__main__.py @@ -1,3 +1,5 @@ +from __future__ import absolute_import + from isort.main import main main() diff --git a/pythonFiles/isort/isort.py b/pythonFiles/isort/isort.py index cecd5af991a1..3ba3e7d19d9e 100644 --- a/pythonFiles/isort/isort.py +++ b/pythonFiles/isort/isort.py @@ -32,7 +32,8 @@ import os import re import sys -from collections import namedtuple +import sysconfig +from collections import OrderedDict, namedtuple from datetime import datetime from difflib import unified_diff from fnmatch import fnmatch @@ -40,7 +41,7 @@ from . import settings from .natural import nsorted -from .pie_slice import OrderedDict, OrderedSet, input, itemsview +from .pie_slice import OrderedSet, input, itemsview KNOWN_SECTION_MAPPING = { 'STDLIB': 'STANDARD_LIBRARY', @@ -87,6 +88,8 @@ def __init__(self, file_path=None, file_contents=None, write_to_stdout=False, ch indent = "\t" self.config['indent'] = indent + self.config['comment_prefix'] = self.config['comment_prefix'].strip("'").strip('"') + self.place_imports = {} self.import_placements = {} self.remove_imports = [self._format_simplified(removal) for removal in self.config['remove_imports']] @@ -108,13 +111,24 @@ def __init__(self, file_path=None, file_contents=None, write_to_stdout=False, ch elif not file_contents: self.file_path = file_path self.file_encoding = coding_check(file_path) - with io.open(file_path, encoding=self.file_encoding) as file_to_import_sort: + with io.open(file_path, encoding=self.file_encoding, newline='') as file_to_import_sort: file_contents = file_to_import_sort.read() if file_contents is None or ("isort:" + "skip_file") in file_contents: + self.skipped = True + self.output = None return - self.in_lines = file_contents.split("\n") + if self.config['line_ending']: + self.line_separator = self.config['line_ending'] + else: + if '\r\n' in file_contents: + self.line_separator = '\r\n' + elif '\r' in file_contents: + self.line_separator = '\r' + else: + self.line_separator = '\n' + self.in_lines = file_contents.split(self.line_separator) self.original_length = len(self.in_lines) if (self.original_length > 1 or self.in_lines[:1] not in ([], [""])) or self.config['force_adds']: for add_import in self.add_imports: @@ -147,22 +161,20 @@ def __init__(self, file_path=None, file_contents=None, write_to_stdout=False, ch self._parse() if self.import_index != -1: self._add_formatted_imports() - self.length_change = len(self.out_lines) - self.original_length while self.out_lines and self.out_lines[-1].strip() == "": self.out_lines.pop(-1) self.out_lines.append("") - - self.output = "\n".join(self.out_lines) + self.output = self.line_separator.join(self.out_lines) if self.config['atomic']: try: - compile(self._strip_top_comments(self.out_lines), self.file_path, 'exec', 0, 1) + compile(self._strip_top_comments(self.out_lines, self.line_separator), self.file_path, 'exec', 0, 1) except SyntaxError: self.output = file_contents self.incorrectly_sorted = True try: - compile(self._strip_top_comments(self.in_lines), self.file_path, 'exec', 0, 1) - print("ERROR: {0} isort would have introduced syntax errors, please report to the project!". \ + compile(self._strip_top_comments(self.in_lines, self.line_separator), self.file_path, 'exec', 0, 1) + print("ERROR: {0} isort would have introduced syntax errors, please report to the project!". format(self.file_path)) except SyntaxError: print("ERROR: {0} File contains syntax errors.".format(self.file_path)) @@ -172,8 +184,8 @@ def __init__(self, file_path=None, file_contents=None, write_to_stdout=False, ch check_output = self.output check_against = file_contents if self.config['ignore_whitespace']: - check_output = check_output.replace("\n", "").replace(" ", "") - check_against = check_against.replace("\n", "").replace(" ", "") + check_output = check_output.replace(self.line_separator, "").replace(" ", "") + check_against = check_against.replace(self.line_separator, "").replace(" ", "") if check_output == check_against: if self.config['verbose']: @@ -187,18 +199,20 @@ def __init__(self, file_path=None, file_contents=None, write_to_stdout=False, ch elif write_to_stdout: sys.stdout.write(self.output) elif file_name and not check: + if self.output == file_contents: + return + if ask_to_apply: - if self.output == file_contents: - return self._show_diff(file_contents) answer = None while answer not in ('yes', 'y', 'no', 'n', 'quit', 'q'): - answer = input("Apply suggested changes to '{0}' [y/n/q]?".format(self.file_path)).lower() + answer = input("Apply suggested changes to '{0}' [y/n/q]? ".format(self.file_path)).lower() if answer in ('no', 'n'): return if answer in ('quit', 'q'): sys.exit(1) - with io.open(self.file_path, encoding=self.file_encoding, mode='w') as output_file: + with io.open(self.file_path, encoding=self.file_encoding, mode='w', newline='') as output_file: + print("Fixing {0}".format(self.file_path)) output_file.write(self.output) def _show_diff(self, file_contents): @@ -214,12 +228,12 @@ def _show_diff(self, file_contents): sys.stdout.write(line) @staticmethod - def _strip_top_comments(lines): + def _strip_top_comments(lines, line_separator): """Strips # comments that exist at the top of the given lines""" lines = copy.copy(lines) while lines and lines[0].startswith("#"): lines = lines[1:] - return "\n".join(lines) + return line_separator.join(lines) def place_module(self, module_name): """Tries to determine if a module is a python std import, third party import, or project code: @@ -261,13 +275,12 @@ def place_module(self, module_name): virtual_env_src = '{0}/src/'.format(virtual_env) # handle case-insensitive paths on windows - stdlib_lib_prefix = os.path.normcase(get_stdlib_path()) + stdlib_lib_prefix = os.path.normcase(sysconfig.get_paths()['stdlib']) for prefix in paths: - module_path = "/".join((prefix, module_name.replace(".", "/"))) package_path = "/".join((prefix, module_name.split(".")[0])) - is_module = (exists_case_sensitive(module_path + ".py") or - exists_case_sensitive(module_path + ".so")) + is_module = (exists_case_sensitive(package_path + ".py") or + exists_case_sensitive(package_path + ".so")) is_package = exists_case_sensitive(package_path) and os.path.isdir(package_path) if is_module or is_package: if ('site-packages' in prefix or 'dist-packages' in prefix or @@ -323,7 +336,8 @@ def _add_comments(self, comments, original_string=""): """ Returns a string with comments added """ - return comments and "{0} # {1}".format(self._strip_comments(original_string)[0], + return comments and "{0}{1} {2}".format(self._strip_comments(original_string)[0], + self.config['comment_prefix'], "; ".join(comments)) or original_string def _wrap(self, line): @@ -332,10 +346,16 @@ def _wrap(self, line): """ wrap_mode = self.config['multi_line_output'] if len(line) > self.config['line_length'] and wrap_mode != settings.WrapModes.NOQA: - for splitter in ("import", ".", "as"): + line_without_comment = line + comment = None + if '#' in line: + line_without_comment, comment = line.split('#', 1) + for splitter in ("import ", ".", "as "): exp = r"\b" + re.escape(splitter) + r"\b" - if re.search(exp, line) and not line.strip().startswith(splitter): - line_parts = re.split(exp, line) + if re.search(exp, line_without_comment) and not line_without_comment.strip().startswith(splitter): + line_parts = re.split(exp, line_without_comment) + if comment: + line_parts[-1] = '{0}#{1}'.format(line_parts[-1], comment) next_line = [] while (len(line) + 2) > (self.config['wrap_length'] or self.config['line_length']) and line_parts: next_line.append(line_parts.pop()) @@ -345,22 +365,22 @@ def _wrap(self, line): cont_line = self._wrap(self.config['indent'] + splitter.join(next_line).lstrip()) if self.config['use_parentheses']: - output = "{0}{1} (\n{2}{3}{4})".format( - line, splitter, cont_line, + output = "{0}{1}({2}{3}{4}{5})".format( + line, splitter, self.line_separator, cont_line, "," if self.config['include_trailing_comma'] else "", - "\n" if wrap_mode in ( + self.line_separator if wrap_mode in ( settings.WrapModes.VERTICAL_HANGING_INDENT, settings.WrapModes.VERTICAL_GRID_GROUPED, ) else "") - lines = output.split('\n') - if ' #' in lines[-1] and lines[-1].endswith(')'): - line, comment = lines[-1].split(' #', 1) - lines[-1] = line + ') #' + comment[:-1] - return '\n'.join(lines) - return "{0}{1} \\\n{2}".format(line, splitter, cont_line) + lines = output.split(self.line_separator) + if self.config['comment_prefix'] in lines[-1] and lines[-1].endswith(')'): + line, comment = lines[-1].split(self.config['comment_prefix'], 1) + lines[-1] = line + ')' + self.config['comment_prefix'] + comment[:-1] + return self.line_separator.join(lines) + return "{0}{1}\\{2}{3}".format(line, splitter, self.line_separator, cont_line) elif len(line) > self.config['line_length'] and wrap_mode == settings.WrapModes.NOQA: if "# NOQA" not in line: - return "{0} # NOQA".format(line) + return "{0}{1} NOQA".format(line, self.config['comment_prefix']) return line @@ -370,7 +390,10 @@ def _add_straight_imports(self, straight_modules, section, section_output): continue if module in self.as_map: - import_definition = "import {0} as {1}".format(module, self.as_map[module]) + import_definition = '' + if self.config['keep_direct_and_as_imports']: + import_definition = "import {0}\n".format(module) + import_definition += "import {0} as {1}".format(module, self.as_map[module]) else: import_definition = "import {0}".format(module) @@ -385,44 +408,50 @@ def _add_from_imports(self, from_modules, section, section_output, ignore_case): continue import_start = "from {0} import ".format(module) - from_imports = self.imports[section]['from'][module] - from_imports = nsorted(from_imports, key=lambda key: self._module_key(key, self.config, True, ignore_case)) + from_imports = list(self.imports[section]['from'][module]) + if not self.config['no_inline_sort'] or self.config['force_single_line']: + from_imports = nsorted(from_imports, key=lambda key: self._module_key(key, self.config, True, ignore_case)) if self.remove_imports: from_imports = [line for line in from_imports if not "{0}.{1}".format(module, line) in self.remove_imports] - for from_import in copy.copy(from_imports): - submodule = module + "." + from_import - import_as = self.as_map.get(submodule, False) - if import_as: - import_definition = "{0} as {1}".format(from_import, import_as) - if self.config['combine_as_imports'] and not ("*" in from_imports and - self.config['combine_star']): - from_imports[from_imports.index(from_import)] = import_definition - else: - import_statement = import_start + import_definition - force_grid_wrap = self.config['force_grid_wrap'] - comments = self.comments['straight'].get(submodule) - import_statement = self._add_comments(comments, self._wrap(import_statement)) - from_imports.remove(from_import) - section_output.append(import_statement) - + sub_modules = ['{0}.{1}'.format(module, from_import) for from_import in from_imports] + as_imports = dict((from_import, "{0} as {1}".format(from_import, self.as_map[sub_module])) for + from_import, sub_module in zip(from_imports, sub_modules) if sub_module in self.as_map) + if self.config['combine_as_imports'] and not ("*" in from_imports and self.config['combine_star']): + for from_import in copy.copy(from_imports): + if from_import in as_imports: + from_imports[from_imports.index(from_import)] = as_imports.pop(from_import) - if from_imports: + while from_imports: comments = self.comments['from'].pop(module, ()) if "*" in from_imports and self.config['combine_star']: import_statement = self._wrap(self._add_comments(comments, "{0}*".format(import_start))) + from_imports = None elif self.config['force_single_line']: import_statements = [] - for from_import in from_imports: + while from_imports: + from_import = from_imports.pop(0) + if from_import in as_imports: + from_comments = self.comments['straight'].get('{}.{}'.format(module, from_import)) + import_statements.append(self._add_comments(from_comments, + self._wrap(import_start + as_imports[from_import]))) + continue single_import_line = self._add_comments(comments, import_start + from_import) comment = self.comments['nested'].get(module, {}).pop(from_import, None) if comment: - single_import_line += "{0} {1}".format(comments and ";" or " #", comment) + single_import_line += "{0} {1}".format(comments and ";" or self.config['comment_prefix'], + comment) import_statements.append(self._wrap(single_import_line)) comments = None - import_statement = "\n".join(import_statements) + import_statement = self.line_separator.join(import_statements) else: + while from_imports and from_imports[0] in as_imports: + from_import = from_imports.pop(0) + from_comments = self.comments['straight'].get('{}.{}'.format(module, from_import)) + section_output.append(self._add_comments(from_comments, + self._wrap(import_start + as_imports[from_import]))) + star_import = False if "*" in from_imports: section_output.append(self._add_comments(comments, "{0}*".format(import_start))) @@ -431,10 +460,13 @@ def _add_from_imports(self, from_modules, section, section_output, ignore_case): comments = None for from_import in copy.copy(from_imports): + if from_import in as_imports: + continue comment = self.comments['nested'].get(module, {}).pop(from_import, None) if comment: single_import_line = self._add_comments(comments, import_start + from_import) - single_import_line += "{0} {1}".format(comments and ";" or " #", comment) + single_import_line += "{0} {1}".format(comments and ";" or self.config['comment_prefix'], + comment) above_comments = self.comments['above']['from'].pop(module, None) if above_comments: section_output.extend(above_comments) @@ -442,29 +474,32 @@ def _add_from_imports(self, from_modules, section, section_output, ignore_case): from_imports.remove(from_import) comments = None + from_import_section = [] + while from_imports and from_imports[0] not in as_imports: + from_import_section.append(from_imports.pop(0)) if star_import: - import_statement = import_start + (", ").join(from_imports) + import_statement = import_start + (", ").join(from_import_section) else: - import_statement = self._add_comments(comments, import_start + (", ").join(from_imports)) - if not from_imports: + import_statement = self._add_comments(comments, import_start + (", ").join(from_import_section)) + if not from_import_section: import_statement = "" do_multiline_reformat = False force_grid_wrap = self.config['force_grid_wrap'] - if force_grid_wrap and len(from_imports) >= force_grid_wrap: + if force_grid_wrap and len(from_import_section) >= force_grid_wrap: do_multiline_reformat = True - if len(import_statement) > self.config['line_length'] and len(from_imports) > 1: + if len(import_statement) > self.config['line_length'] and len(from_import_section) > 1: do_multiline_reformat = True # If line too long AND have imports AND we are NOT using GRID or VERTICAL wrap modes - if (len(import_statement) > self.config['line_length'] and len(from_imports) > 0 and + if (len(import_statement) > self.config['line_length'] and len(from_import_section) > 0 and self.config['multi_line_output'] not in (1, 0)): do_multiline_reformat = True if do_multiline_reformat: - import_statement = self._multi_line_reformat(import_start, from_imports, comments) + import_statement = self._multi_line_reformat(import_start, from_import_section, comments) if not do_multiline_reformat and len(import_statement) > self.config['line_length']: import_statement = self._wrap(import_statement) @@ -483,10 +518,10 @@ def _multi_line_reformat(self, import_start, from_imports, comments): import_statement = formatter(import_start, copy.copy(from_imports), dynamic_indent, indent, line_length, comments) if self.config['balanced_wrapping']: - lines = import_statement.split("\n") + lines = import_statement.split(self.line_separator) line_count = len(lines) if len(lines) > 1: - minimum_length = min([len(line) for line in lines[:-1]]) + minimum_length = min(len(line) for line in lines[:-1]) else: minimum_length = 0 new_import_statement = import_statement @@ -496,8 +531,8 @@ def _multi_line_reformat(self, import_start, from_imports, comments): line_length -= 1 new_import_statement = formatter(import_start, copy.copy(from_imports), dynamic_indent, indent, line_length, comments) - lines = new_import_statement.split("\n") - if import_statement.count('\n') == 0: + lines = new_import_statement.split(self.line_separator) + if import_statement.count(self.line_separator) == 0: return self._wrap(import_statement) return import_statement @@ -518,6 +553,7 @@ def _add_formatted_imports(self): sections = ('no_sections', ) output = [] + prev_section_has_imports = False for section in sections: straight_modules = self.imports[section]['straight'] straight_modules = nsorted(straight_modules, key=lambda key: self._module_key(key, self.config)) @@ -550,7 +586,6 @@ def by_module(line): line = line.lower() return '{0}{1}'.format(section, line) section_output = nsorted(section_output, key=by_module) - if section_output: section_name = section if section_name in self.place_imports: @@ -560,12 +595,17 @@ def by_module(line): section_title = self.config.get('import_heading_' + str(section_name).lower(), '') if section_title: section_comment = "# {0}".format(section_title) - if not section_comment in self.out_lines[0:1] and not section_comment in self.in_lines[0:1]: + if section_comment not in self.out_lines[0:1] and section_comment not in self.in_lines[0:1]: section_output.insert(0, section_comment) + if prev_section_has_imports and section_name in self.config['no_lines_before']: + while output and output[-1].strip() == '': + output.pop() output += section_output + ([''] * self.config['lines_between_sections']) - - while [character.strip() for character in output[-1:]] == [""]: + prev_section_has_imports = bool(section_output) + while output and output[-1].strip() == '': output.pop() + while output and output[0].strip() == '': + output.pop(0) output_at = 0 if self.import_index < self.original_length: @@ -582,16 +622,23 @@ def by_module(line): next_construct = "" self._in_quote = False tail = self.out_lines[imports_tail:] + for index, line in enumerate(tail): + in_quote = self._in_quote if not self._skip_line(line) and line.strip(): if line.strip().startswith("#") and len(tail) > (index + 1) and tail[index + 1].strip(): continue next_construct = line break + elif not in_quote: + parts = line.split() + if len(parts) >= 3 and parts[1] == '=' and "'" not in parts[0] and '"' not in parts[0]: + next_construct = line + break if self.config['lines_after_imports'] != -1: self.out_lines[imports_tail:0] = ["" for line in range(self.config['lines_after_imports'])] - elif next_construct.startswith("def") or next_construct.startswith("class") or \ + elif next_construct.startswith("def ") or next_construct.startswith("class ") or \ next_construct.startswith("@") or next_construct.startswith("async def"): self.out_lines[imports_tail:0] = ["", ""] else: @@ -612,7 +659,7 @@ def _output_grid(self, statement, imports, white_space, indent, line_length, com while imports: next_import = imports.pop(0) next_statement = self._add_comments(comments, statement + ", " + next_import) - if len(next_statement.split("\n")[-1]) + 1 > line_length: + if len(next_statement.split(self.line_separator)[-1]) + 1 > line_length: lines = ['{0}{1}'.format(white_space, next_import.split(" ")[0])] for part in next_import.split(" ")[1:]: new_line = '{0} {1}'.format(lines[-1], part) @@ -620,20 +667,20 @@ def _output_grid(self, statement, imports, white_space, indent, line_length, com lines.append('{0}{1}'.format(white_space, part)) else: lines[-1] = new_line - next_import = '\n'.join(lines) + next_import = self.line_separator.join(lines) statement = (self._add_comments(comments, "{0},".format(statement)) + - "\n{0}".format(next_import)) + "{0}{1}".format(self.line_separator, next_import)) comments = None else: statement += ", " + next_import return statement + ("," if self.config['include_trailing_comma'] else "") + ")" def _output_vertical(self, statement, imports, white_space, indent, line_length, comments): - first_import = self._add_comments(comments, imports.pop(0) + ",") + "\n" + white_space + first_import = self._add_comments(comments, imports.pop(0) + ",") + self.line_separator + white_space return "{0}({1}{2}{3})".format( statement, first_import, - (",\n" + white_space).join(imports), + ("," + self.line_separator + white_space).join(imports), "," if self.config['include_trailing_comma'] else "", ) @@ -642,56 +689,69 @@ def _output_hanging_indent(self, statement, imports, white_space, indent, line_l while imports: next_import = imports.pop(0) next_statement = self._add_comments(comments, statement + ", " + next_import) - if len(next_statement.split("\n")[-1]) + 3 > line_length: + if len(next_statement.split(self.line_separator)[-1]) + 3 > line_length: next_statement = (self._add_comments(comments, "{0}, \\".format(statement)) + - "\n{0}{1}".format(indent, next_import)) + "{0}{1}{2}".format(self.line_separator, indent, next_import)) comments = None statement = next_statement return statement def _output_vertical_hanging_indent(self, statement, imports, white_space, indent, line_length, comments): - return "{0}({1}\n{2}{3}{4}\n)".format( + return "{0}({1}{2}{3}{4}{5}{2})".format( statement, self._add_comments(comments), + self.line_separator, indent, - (",\n" + indent).join(imports), + ("," + self.line_separator + indent).join(imports), "," if self.config['include_trailing_comma'] else "", ) - def _output_vertical_grid_common(self, statement, imports, white_space, indent, line_length, comments): - statement += self._add_comments(comments, "(") + "\n" + indent + imports.pop(0) + def _output_vertical_grid_common(self, statement, imports, white_space, indent, line_length, comments, + need_trailing_char): + statement += self._add_comments(comments, "(") + self.line_separator + indent + imports.pop(0) while imports: next_import = imports.pop(0) next_statement = "{0}, {1}".format(statement, next_import) - if len(next_statement.split("\n")[-1]) + 1 > line_length: - next_statement = "{0},\n{1}{2}".format(statement, indent, next_import) + current_line_length = len(next_statement.split(self.line_separator)[-1]) + if imports or need_trailing_char: + # If we have more imports we need to account for a comma after this import + # We might also need to account for a closing ) we're going to add. + current_line_length += 1 + if current_line_length > line_length: + next_statement = "{0},{1}{2}{3}".format(statement, self.line_separator, indent, next_import) statement = next_statement if self.config['include_trailing_comma']: statement += ',' return statement def _output_vertical_grid(self, statement, imports, white_space, indent, line_length, comments): - return self._output_vertical_grid_common(statement, imports, white_space, indent, line_length, comments) + ")" + return self._output_vertical_grid_common(statement, imports, white_space, indent, line_length, comments, + True) + ")" def _output_vertical_grid_grouped(self, statement, imports, white_space, indent, line_length, comments): - return self._output_vertical_grid_common(statement, imports, white_space, indent, line_length, comments) + "\n)" + return self._output_vertical_grid_common(statement, imports, white_space, indent, line_length, comments, + True) + self.line_separator + ")" + + def _output_vertical_grid_grouped_no_comma(self, statement, imports, white_space, indent, line_length, comments): + return self._output_vertical_grid_common(statement, imports, white_space, indent, line_length, comments, + False) + self.line_separator + ")" def _output_noqa(self, statement, imports, white_space, indent, line_length, comments): retval = '{0}{1}'.format(statement, ', '.join(imports)) comment_str = ' '.join(comments) if comments: - if len(retval) + 4 + len(comment_str) <= line_length: - return '{0} # {1}'.format(retval, comment_str) + if len(retval) + len(self.config['comment_prefix']) + 1 + len(comment_str) <= line_length: + return '{0}{1} {2}'.format(retval, self.config['comment_prefix'], comment_str) else: if len(retval) <= line_length: return retval if comments: if "NOQA" in comments: - return '{0} # {1}'.format(retval, comment_str) + return '{0}{1} {2}'.format(retval, self.config['comment_prefix'], comment_str) else: - return '{0} # NOQA {1}'.format(retval, comment_str) + return '{0}{1} NOQA {2}'.format(retval, self.config['comment_prefix'], comment_str) else: - return '{0} # NOQA'.format(retval) + return '{0}{1} NOQA'.format(retval, self.config['comment_prefix']) @staticmethod def _strip_comments(line, comments=None): @@ -723,7 +783,7 @@ def _format_simplified(import_line): def _format_natural(import_line): import_line = import_line.strip() if not import_line.startswith("from ") and not import_line.startswith("import "): - if not "." in import_line: + if "." not in import_line: return "import {0}".format(import_line) parts = import_line.split(".") end = parts.pop(-1) @@ -783,7 +843,10 @@ def _parse(self): self._in_quote = False self._in_top_comment = False while not self._at_end(): - line = self._get_line() + raw_line = line = self._get_line() + line = line.replace("from.import ", "from . import ") + line = line.replace("\t", " ").replace('import*', 'import *') + line = line.replace(" .import ", " . import ") statement_index = self.index skip_line = self._skip_line(line) @@ -804,7 +867,7 @@ def _parse(self): import_type = self._import_type(line) if not import_type or skip_line: - self.out_lines.append(line) + self.out_lines.append(raw_line) continue for line in (line.strip() for line in line.split(";")): @@ -813,14 +876,12 @@ def _parse(self): self.out_lines.append(line) continue - line = line.replace("\t", " ").replace('import*', 'import *') if self.import_index == -1: self.import_index = self.index - 1 nested_comments = {} import_string, comments, new_comments = self._strip_comments(line) stripped_line = [part for part in self._strip_syntax(import_string).strip().split(" ") if part] - if import_type == "from" and len(stripped_line) == 2 and stripped_line[1] != "*" and new_comments: nested_comments[stripped_line[-1]] = comments[0] @@ -828,17 +889,32 @@ def _parse(self): while not line.strip().endswith(")") and not self._at_end(): line, comments, new_comments = self._strip_comments(self._get_line(), comments) stripped_line = self._strip_syntax(line).strip() - if import_type == "from" and stripped_line and not " " in stripped_line and new_comments: + if import_type == "from" and stripped_line and " " not in stripped_line and new_comments: nested_comments[stripped_line] = comments[-1] - import_string += "\n" + line + import_string += self.line_separator + line else: while line.strip().endswith("\\"): line, comments, new_comments = self._strip_comments(self._get_line(), comments) + + # Still need to check for parentheses after an escaped line + if "(" in line.split("#")[0] and not self._at_end(): + stripped_line = self._strip_syntax(line).strip() + if import_type == "from" and stripped_line and " " not in stripped_line and new_comments: + nested_comments[stripped_line] = comments[-1] + import_string += self.line_separator + line + + while not line.strip().endswith(")") and not self._at_end(): + line, comments, new_comments = self._strip_comments(self._get_line(), comments) + stripped_line = self._strip_syntax(line).strip() + if import_type == "from" and stripped_line and " " not in stripped_line and new_comments: + nested_comments[stripped_line] = comments[-1] + import_string += self.line_separator + line + stripped_line = self._strip_syntax(line).strip() - if import_type == "from" and stripped_line and not " " in stripped_line and new_comments: + if import_type == "from" and stripped_line and " " not in stripped_line and new_comments: nested_comments[stripped_line] = comments[-1] if import_string.strip().endswith(" import") or line.strip().startswith("import "): - import_string += "\n" + line + import_string += self.line_separator + line else: import_string = import_string.rstrip().rstrip("\\") + " " + line.lstrip() @@ -866,6 +942,8 @@ def _parse(self): if import_type == "from": import_from = imports.pop(0) placed_module = self.place_module(import_from) + if self.config['verbose']: + print("from-type place_module for %s returned %s" % (import_from, placed_module)) if placed_module == '': print( "WARNING: could not place module {0} of line {1} --" @@ -882,8 +960,8 @@ def _parse(self): if len(self.out_lines) > max(self.import_index, self._first_comment_index_end + 1, 1) - 1: last = self.out_lines and self.out_lines[-1].rstrip() or "" - while (last.startswith("#") and not last.endswith('"""') and not last.endswith("'''") and not - 'isort:imports-' in last): + while (last.startswith("#") and not last.endswith('"""') and not last.endswith("'''") and + 'isort:imports-' not in last): self.comments['above']['from'].setdefault(import_from, []).insert(0, self.out_lines.pop(-1)) if len(self.out_lines) > max(self.import_index - 1, self._first_comment_index_end + 1, 1) - 1: last = self.out_lines[-1].rstrip() @@ -906,7 +984,7 @@ def _parse(self): last = self.out_lines and self.out_lines[-1].rstrip() or "" while (last.startswith("#") and not last.endswith('"""') and not last.endswith("'''") and - not 'isort:imports-' in last): + 'isort:imports-' not in last): self.comments['above']['straight'].setdefault(module, []).insert(0, self.out_lines.pop(-1)) if len(self.out_lines) > 0: @@ -916,6 +994,8 @@ def _parse(self): if self.index - 1 == self.import_index: self.import_index -= len(self.comments['above']['straight'].get(module, [])) placed_module = self.place_module(module) + if self.config['verbose']: + print("else-type place_module for %s returned %s" % (module, placed_module)) if placed_module == '': print( "WARNING: could not place module {0} of line {1} --" @@ -942,18 +1022,6 @@ def coding_check(fname, default='utf-8'): return coding -def get_stdlib_path(): - """Returns the path to the standard lib for the current path installation. - - This function can be dropped and "sysconfig.get_paths()" used directly once Python 2.6 support is dropped. - """ - if sys.version_info >= (2, 7): - import sysconfig - return sysconfig.get_paths()['stdlib'] - else: - return os.path.join(sys.prefix, 'lib') - - def exists_case_sensitive(path): """ Returns if the given path exists and also matches the case on Windows. @@ -963,7 +1031,7 @@ def exists_case_sensitive(path): can only import using the case of the real file. """ result = os.path.exists(path) - if sys.platform.startswith('win') and result: + if (sys.platform.startswith('win') or sys.platform == 'darwin') and result: directory, basename = os.path.split(path) result = basename in os.listdir(directory) return result diff --git a/pythonFiles/isort/main.py b/pythonFiles/isort/main.py old mode 100755 new mode 100644 index eae7afa53409..efb3793d0a1b --- a/pythonFiles/isort/main.py +++ b/pythonFiles/isort/main.py @@ -1,4 +1,4 @@ -#! /usr/bin/env python +#!/usr/bin/env python ''' Tool for sorting imports alphabetically, and automatically separated into sections. Copyright (C) 2013 Timothy Edmund Crosley @@ -23,7 +23,10 @@ import argparse import glob import os +import re import sys +from concurrent.futures import ProcessPoolExecutor +import functools import setuptools @@ -32,7 +35,6 @@ from .pie_slice import itemsview - INTRO = r""" /#######################################################################\ @@ -56,6 +58,36 @@ \########################################################################/ """.format(__version__) +shebang_re = re.compile(br'^#!.*\bpython[23w]?\b') + + +def is_python_file(path): + if path.endswith('.py'): + return True + + try: + with open(path, 'rb') as fp: + line = fp.readline(100) + except IOError: + return False + else: + return bool(shebang_re.match(line)) + + +class SortAttempt(object): + def __init__(self, incorrectly_sorted, skipped): + self.incorrectly_sorted = incorrectly_sorted + self.skipped = skipped + + +def sort_imports(file_name, **arguments): + try: + result = SortImports(file_name, **arguments) + return SortAttempt(result.incorrectly_sorted, result.skipped) + except IOError as e: + print("WARNING: Unable to parse file {0} due to {1}".format(file_name, e)) + return None + def iter_source_code(paths, config, skipped): """Iterate over all Python source files defined in paths.""" @@ -71,11 +103,12 @@ def iter_source_code(paths, config, skipped): skipped.append(dirname) dirnames.remove(dirname) for filename in filenames: - if filename.endswith('.py'): + filepath = os.path.join(dirpath, filename) + if is_python_file(filepath): if should_skip(filename, config, dirpath): skipped.append(filename) else: - yield os.path.join(dirpath, filename) + yield filepath else: yield path @@ -138,105 +171,119 @@ def run(self): def create_parser(): parser = argparse.ArgumentParser(description='Sort Python import definitions alphabetically ' 'within logical sections.') - parser.add_argument('files', nargs='*', help='One or more Python source files that need their imports sorted.') - parser.add_argument('-y', '--apply', dest='apply', action='store_true', - help='Tells isort to apply changes recursively without asking') - parser.add_argument('-l', '--lines', help='[Deprecated] The max length of an import line (used for wrapping ' - 'long imports).', - dest='line_length', type=int) - parser.add_argument('-w', '--line-width', help='The max length of an import line (used for wrapping long imports).', - dest='line_length', type=int) - parser.add_argument('-s', '--skip', help='Files that sort imports should skip over. If you want to skip multiple ' - 'files you should specify twice: --skip file1 --skip file2.', dest='skip', action='append') - parser.add_argument('-ns', '--dont-skip', help='Files that sort imports should never skip over.', - dest='not_skip', action='append') - parser.add_argument('-sg', '--skip-glob', help='Files that sort imports should skip over.', dest='skip_glob', - action='append') - parser.add_argument('-t', '--top', help='Force specific imports to the top of their appropriate section.', - dest='force_to_top', action='append') - parser.add_argument('-f', '--future', dest='known_future_library', action='append', - help='Force sortImports to recognize a module as part of the future compatibility libraries.') - parser.add_argument('-b', '--builtin', dest='known_standard_library', action='append', - help='Force sortImports to recognize a module as part of the python standard library.') - parser.add_argument('-o', '--thirdparty', dest='known_third_party', action='append', - help='Force sortImports to recognize a module as being part of a third party library.') - parser.add_argument('-p', '--project', dest='known_first_party', action='append', - help='Force sortImports to recognize a module as being part of the current python project.') - parser.add_argument('--virtual-env', dest='virtual_env', - help='Virtual environment to use for determining whether a package is third-party') - parser.add_argument('-m', '--multi-line', dest='multi_line_output', type=int, choices=[0, 1, 2, 3, 4, 5], - help='Multi line output (0-grid, 1-vertical, 2-hanging, 3-vert-hanging, 4-vert-grid, ' - '5-vert-grid-grouped).') - parser.add_argument('-i', '--indent', help='String to place for indents defaults to " " (4 spaces).', - dest='indent', type=str) + inline_args_group = parser.add_mutually_exclusive_group() parser.add_argument('-a', '--add-import', dest='add_imports', action='append', help='Adds the specified import line to all files, ' 'automatically determining correct placement.') + parser.add_argument('-ac', '--atomic', dest='atomic', action='store_true', + help="Ensures the output doesn't save if the resulting file contains syntax errors.") parser.add_argument('-af', '--force-adds', dest='force_adds', action='store_true', help='Forces import adds even if the original file is empty.') - parser.add_argument('-r', '--remove-import', dest='remove_imports', action='append', - help='Removes the specified import from all files.') - parser.add_argument('-ls', '--length-sort', help='Sort imports by their string length.', - dest='length_sort', action='store_true') - parser.add_argument('-d', '--stdout', help='Force resulting output to stdout, instead of in-place.', - dest='write_to_stdout', action='store_true') + parser.add_argument('-b', '--builtin', dest='known_standard_library', action='append', + help='Force sortImports to recognize a module as part of the python standard library.') parser.add_argument('-c', '--check-only', action='store_true', dest="check", help='Checks the file for unsorted / unformatted imports and prints them to the ' 'command line without modifying the file.') - parser.add_argument('-ws', '--ignore-whitespace', action='store_true', dest="ignore_whitespace", - help='Tells isort to ignore whitespace differences when --check-only is being used.') - parser.add_argument('-sl', '--force-single-line-imports', dest='force_single_line', action='store_true', - help='Forces all from imports to appear on their own line') - parser.add_argument('-ds', '--no-sections', help='Put all imports into the same section bucket', dest='no_sections', - action='store_true') - parser.add_argument('-sd', '--section-default', dest='default_section', - help='Sets the default section for imports (by default FIRSTPARTY) options: ' + - str(DEFAULT_SECTIONS)) + parser.add_argument('-ca', '--combine-as', dest='combine_as_imports', action='store_true', + help="Combines as imports on the same line.") + parser.add_argument('-cs', '--combine-star', dest='combine_star', action='store_true', + help="Ensures that if a star import is present, nothing else is imported from that namespace.") + parser.add_argument('-d', '--stdout', help='Force resulting output to stdout, instead of in-place.', + dest='write_to_stdout', action='store_true') parser.add_argument('-df', '--diff', dest='show_diff', action='store_true', help="Prints a diff of all the changes isort would make to a file, instead of " "changing it in place") - parser.add_argument('-e', '--balanced', dest='balanced_wrapping', action='store_true', - help='Balances wrapping to produce the most consistent line length possible') - parser.add_argument('-rc', '--recursive', dest='recursive', action='store_true', - help='Recursively look for Python files of which to sort imports') - parser.add_argument('-ot', '--order-by-type', dest='order_by_type', - action='store_true', help='Order imports by type in addition to alphabetically') + parser.add_argument('-ds', '--no-sections', help='Put all imports into the same section bucket', dest='no_sections', + action='store_true') parser.add_argument('-dt', '--dont-order-by-type', dest='dont_order_by_type', action='store_true', help='Only order imports alphabetically, do not attempt type ordering') - parser.add_argument('-ac', '--atomic', dest='atomic', action='store_true', - help="Ensures the output doesn't save if the resulting file contains syntax errors.") - parser.add_argument('-cs', '--combine-star', dest='combine_star', action='store_true', - help="Ensures that if a star import is present, nothing else is imported from that namespace.") - parser.add_argument('-ca', '--combine-as', dest='combine_as_imports', action='store_true', - help="Combines as imports on the same line.") - parser.add_argument('-tc', '--trailing-comma', dest='include_trailing_comma', action='store_true', - help='Includes a trailing comma on multi line imports that include parentheses.') - parser.add_argument('-v', '--version', action='store_true', dest='show_version') - parser.add_argument('-vb', '--verbose', action='store_true', dest="verbose", - help='Shows verbose output, such as when files are skipped or when a check is successful.') - parser.add_argument('-q', '--quiet', action='store_true', dest="quiet", - help='Shows extra quiet output, only errors are outputted.') - parser.add_argument('-sp', '--settings-path', dest="settings_path", - help='Explicitly set the settings path instead of auto determining based on file location.') + parser.add_argument('-e', '--balanced', dest='balanced_wrapping', action='store_true', + help='Balances wrapping to produce the most consistent line length possible') + parser.add_argument('-f', '--future', dest='known_future_library', action='append', + help='Force sortImports to recognize a module as part of the future compatibility libraries.') + parser.add_argument('-fas', '--force-alphabetical-sort', action='store_true', dest="force_alphabetical_sort", + help='Force all imports to be sorted as a single section') + parser.add_argument('-fass', '--force-alphabetical-sort-within-sections', action='store_true', + dest="force_alphabetical_sort", help='Force all imports to be sorted alphabetically within a ' + 'section') parser.add_argument('-ff', '--from-first', dest='from_first', help="Switches the typical ordering preference, showing from imports first then straight ones.") - parser.add_argument('-wl', '--wrap-length', dest='wrap_length', - help="Specifies how long lines that are wrapped should be, if not set line_length is used.") parser.add_argument('-fgw', '--force-grid-wrap', nargs='?', const=2, type=int, dest="force_grid_wrap", help='Force number of from imports (defaults to 2) to be grid wrapped regardless of line ' 'length') - parser.add_argument('-fass', '--force-alphabetical-sort-within-sections', action='store_true', - dest="force_alphabetical_sort", help='Force all imports to be sorted alphabetically within a ' - 'section') - parser.add_argument('-fas', '--force-alphabetical-sort', action='store_true', dest="force_alphabetical_sort", - help='Force all imports to be sorted as a single section') parser.add_argument('-fss', '--force-sort-within-sections', action='store_true', dest="force_sort_within_sections", help='Force imports to be sorted by module, independent of import_type') + parser.add_argument('-i', '--indent', help='String to place for indents defaults to " " (4 spaces).', + dest='indent', type=str) + parser.add_argument('-j', '--jobs', help='Number of files to process in parallel.', + dest='jobs', type=int) + parser.add_argument('-k', '--keep-direct-and-as', dest='keep_direct_and_as_imports', action='store_true', + help="Turns off default behavior that removes direct imports when as imports exist.") + parser.add_argument('-l', '--lines', help='[Deprecated] The max length of an import line (used for wrapping ' + 'long imports).', + dest='line_length', type=int) + parser.add_argument('-lai', '--lines-after-imports', dest='lines_after_imports', type=int) parser.add_argument('-lbt', '--lines-between-types', dest='lines_between_types', type=int) + parser.add_argument('-le', '--line-ending', dest='line_ending', + help="Forces line endings to the specified value. If not set, values will be guessed per-file.") + parser.add_argument('-ls', '--length-sort', help='Sort imports by their string length.', + dest='length_sort', action='store_true') + parser.add_argument('-m', '--multi-line', dest='multi_line_output', type=int, choices=[0, 1, 2, 3, 4, 5], + help='Multi line output (0-grid, 1-vertical, 2-hanging, 3-vert-hanging, 4-vert-grid, ' + '5-vert-grid-grouped, 6-vert-grid-grouped-no-comma).') + inline_args_group.add_argument('-nis', '--no-inline-sort', dest='no_inline_sort', action='store_true', + help='Leaves `from` imports with multiple imports \'as-is\' (e.g. `from foo import a, c ,b`).') + parser.add_argument('-nlb', '--no-lines-before', help='Sections which should not be split with previous by empty lines', + dest='no_lines_before', action='append') + parser.add_argument('-ns', '--dont-skip', help='Files that sort imports should never skip over.', + dest='not_skip', action='append') + parser.add_argument('-o', '--thirdparty', dest='known_third_party', action='append', + help='Force sortImports to recognize a module as being part of a third party library.') + parser.add_argument('-ot', '--order-by-type', dest='order_by_type', + action='store_true', help='Order imports by type in addition to alphabetically') + parser.add_argument('-p', '--project', dest='known_first_party', action='append', + help='Force sortImports to recognize a module as being part of the current python project.') + parser.add_argument('-q', '--quiet', action='store_true', dest="quiet", + help='Shows extra quiet output, only errors are outputted.') + parser.add_argument('-r', '--remove-import', dest='remove_imports', action='append', + help='Removes the specified import from all files.') + parser.add_argument('-rc', '--recursive', dest='recursive', action='store_true', + help='Recursively look for Python files of which to sort imports') + parser.add_argument('-s', '--skip', help='Files that sort imports should skip over. If you want to skip multiple ' + 'files you should specify twice: --skip file1 --skip file2.', dest='skip', action='append') + parser.add_argument('-sd', '--section-default', dest='default_section', + help='Sets the default section for imports (by default FIRSTPARTY) options: ' + + str(DEFAULT_SECTIONS)) + parser.add_argument('-sg', '--skip-glob', help='Files that sort imports should skip over.', dest='skip_glob', + action='append') + inline_args_group.add_argument('-sl', '--force-single-line-imports', dest='force_single_line', action='store_true', + help='Forces all from imports to appear on their own line') + parser.add_argument('-sp', '--settings-path', dest="settings_path", + help='Explicitly set the settings path instead of auto determining based on file location.') + parser.add_argument('-t', '--top', help='Force specific imports to the top of their appropriate section.', + dest='force_to_top', action='append') + parser.add_argument('-tc', '--trailing-comma', dest='include_trailing_comma', action='store_true', + help='Includes a trailing comma on multi line imports that include parentheses.') parser.add_argument('-up', '--use-parentheses', dest='use_parentheses', action='store_true', - help='Use parenthesis for line continuation on lenght limit instead of slashes.') + help='Use parenthesis for line continuation on length limit instead of slashes.') + parser.add_argument('-v', '--version', action='store_true', dest='show_version') + parser.add_argument('-vb', '--verbose', action='store_true', dest="verbose", + help='Shows verbose output, such as when files are skipped or when a check is successful.') + parser.add_argument('--virtual-env', dest='virtual_env', + help='Virtual environment to use for determining whether a package is third-party') + parser.add_argument('-vn', '--version-number', action='version', version=__version__, + help='Returns just the current version number without the logo') + parser.add_argument('-w', '--line-width', help='The max length of an import line (used for wrapping long imports).', + dest='line_length', type=int) + parser.add_argument('-wl', '--wrap-length', dest='wrap_length', + help="Specifies how long lines that are wrapped should be, if not set line_length is used.") + parser.add_argument('-ws', '--ignore-whitespace', action='store_true', dest="ignore_whitespace", + help='Tells isort to ignore whitespace differences when --check-only is being used.') + parser.add_argument('-y', '--apply', dest='apply', action='store_true', + help='Tells isort to apply changes recursively without asking') + parser.add_argument('files', nargs='*', help='One or more Python source files that need their imports sorted.') - arguments = dict((key, value) for (key, value) in itemsview(vars(parser.parse_args())) if value) + arguments = {key: value for key, value in itemsview(vars(parser.parse_args())) if value} if 'dont_order_by_type' in arguments: arguments['order_by_type'] = False return arguments @@ -251,6 +298,14 @@ def main(): if 'settings_path' in arguments: sp = arguments['settings_path'] arguments['settings_path'] = os.path.abspath(sp) if os.path.isdir(sp) else os.path.dirname(os.path.abspath(sp)) + if not os.path.isdir(arguments['settings_path']): + print("WARNING: settings_path dir does not exist: {0}".format(arguments['settings_path'])) + + if 'virtual_env' in arguments: + venv = arguments['virtual_env'] + arguments['virtual_env'] = os.path.abspath(venv) + if not os.path.isdir(arguments['virtual_env']): + print("WARNING: virtual_env dir does not exist: {0}".format(arguments['virtual_env'])) file_names = arguments.pop('files', []) if file_names == ['-']: @@ -270,16 +325,29 @@ def main(): num_skipped = 0 if config['verbose'] or config.get('show_logo', False): print(INTRO) - for file_name in file_names: - try: - sort_attempt = SortImports(file_name, **arguments) + jobs = arguments.get('jobs') + if jobs: + executor = ProcessPoolExecutor(max_workers=jobs) + + for sort_attempt in executor.map(functools.partial(sort_imports, **arguments), file_names): + if not sort_attempt: + continue incorrectly_sorted = sort_attempt.incorrectly_sorted if arguments.get('check', False) and incorrectly_sorted: wrong_sorted_files = True if sort_attempt.skipped: num_skipped += 1 - except IOError as e: - print("WARNING: Unable to parse file {0} due to {1}".format(file_name, e)) + else: + for file_name in file_names: + try: + sort_attempt = SortImports(file_name, **arguments) + incorrectly_sorted = sort_attempt.incorrectly_sorted + if arguments.get('check', False) and incorrectly_sorted: + wrong_sorted_files = True + if sort_attempt.skipped: + num_skipped += 1 + except IOError as e: + print("WARNING: Unable to parse file {0} due to {1}".format(file_name, e)) if wrong_sorted_files: exit(1) diff --git a/pythonFiles/isort/natural.py b/pythonFiles/isort/natural.py index aac8c4a36157..c02b42c37e48 100644 --- a/pythonFiles/isort/natural.py +++ b/pythonFiles/isort/natural.py @@ -8,7 +8,7 @@ Copyright (C) 2013 Timothy Edmund Crosley Implementation originally from @HappyLeapSecond stack overflow user in response to: - http://stackoverflow.com/questions/5967500/how-to-correctly-sort-a-string-with-a-number-inside + https://stackoverflow.com/questions/5967500/how-to-correctly-sort-a-string-with-a-number-inside Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation diff --git a/pythonFiles/isort/pie_slice.py b/pythonFiles/isort/pie_slice.py index bfb341a7f7ec..178cf33e87c3 100644 --- a/pythonFiles/isort/pie_slice.py +++ b/pythonFiles/isort/pie_slice.py @@ -21,11 +21,8 @@ """ from __future__ import absolute_import -import abc import collections -import functools import sys -from numbers import Integral __version__ = "1.1.0" @@ -47,7 +44,7 @@ common = ['native_dict', 'native_round', 'native_filter', 'native_map', 'native_range', 'native_str', 'native_chr', 'native_input', 'PY2', 'PY3', 'u', 'itemsview', 'valuesview', 'keysview', 'execute', 'integer_types', - 'native_next', 'native_object', 'with_metaclass', 'OrderedDict', 'lru_cache'] + 'native_next', 'native_object', 'with_metaclass', 'lru_cache'] def with_metaclass(meta, *bases): @@ -85,32 +82,12 @@ def unmodified_isinstance(*bases): """ class UnmodifiedIsInstance(type): - if sys.version_info[0] == 2 and sys.version_info[1] <= 6: - - @classmethod - def __instancecheck__(cls, instance): - if cls.__name__ in (str(base.__name__) for base in bases): - return isinstance(instance, bases) - - subclass = getattr(instance, '__class__', None) - subtype = type(instance) - instance_type = getattr(abc, '_InstanceType', None) - if not instance_type: - class test_object: - pass - instance_type = type(test_object) - if subtype is instance_type: - subtype = subclass - if subtype is subclass or subclass is None: - return cls.__subclasscheck__(subtype) - return (cls.__subclasscheck__(subclass) or cls.__subclasscheck__(subtype)) - else: - @classmethod - def __instancecheck__(cls, instance): - if cls.__name__ in (str(base.__name__) for base in bases): - return isinstance(instance, bases) + @classmethod + def __instancecheck__(cls, instance): + if cls.__name__ in (str(base.__name__) for base in bases): + return isinstance(instance, bases) - return type.__instancecheck__(cls, instance) + return type.__instancecheck__(cls, instance) return with_metaclass(UnmodifiedIsInstance, *bases) @@ -148,12 +125,11 @@ def callable(entity): __all__ = common + ['urllib'] else: - from itertools import ifilter as filter - from itertools import imap as map - from itertools import izip as zip + from itertools import ifilter as filter # noqa: F401 + from itertools import imap as map # noqa: F401 + from itertools import izip as zip # noqa: F401 from decimal import Decimal, ROUND_HALF_EVEN - import codecs str = unicode chr = unichr input = raw_input @@ -281,28 +257,10 @@ def __new__(cls, name, bases, dct): dct['__str__'] = lambda self: self.__unicode__().encode('utf-8') return type.__new__(cls, name, bases, dct) - if sys.version_info[1] <= 6: - def __instancecheck__(cls, instance): - if cls.__name__ == "object": - return isinstance(instance, native_object) - - subclass = getattr(instance, '__class__', None) - subtype = type(instance) - instance_type = getattr(abc, '_InstanceType', None) - if not instance_type: - class test_object: - pass - instance_type = type(test_object) - if subtype is instance_type: - subtype = subclass - if subtype is subclass or subclass is None: - return cls.__subclasscheck__(subtype) - return (cls.__subclasscheck__(subclass) or cls.__subclasscheck__(subtype)) - else: - def __instancecheck__(cls, instance): - if cls.__name__ == "object": - return isinstance(instance, native_object) - return type.__instancecheck__(cls, instance) + def __instancecheck__(cls, instance): + if cls.__name__ == "object": + return isinstance(instance, native_object) + return type.__instancecheck__(cls, instance) class object(with_metaclass(FixStr, object)): pass @@ -310,138 +268,6 @@ class object(with_metaclass(FixStr, object)): __all__ = common + ['round', 'dict', 'apply', 'cmp', 'coerce', 'execfile', 'raw_input', 'unpacks', 'str', 'chr', 'input', 'range', 'filter', 'map', 'zip', 'object'] -if sys.version_info[0] == 2 and sys.version_info[1] < 7: - # OrderedDict - # Copyright (c) 2009 Raymond Hettinger - # - # Permission is hereby granted, free of charge, to any person - # obtaining a copy of this software and associated documentation files - # (the "Software"), to deal in the Software without restriction, - # including without limitation the rights to use, copy, modify, merge, - # publish, distribute, sublicense, and/or sell copies of the Software, - # and to permit persons to whom the Software is furnished to do so, - # subject to the following conditions: - # - # The above copyright notice and this permission notice shall be - # included in all copies or substantial portions of the Software. - # - # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - # OTHER DEALINGS IN THE SOFTWARE. - - from UserDict import DictMixin - - class OrderedDict(dict, DictMixin): - - def __init__(self, *args, **kwds): - if len(args) > 1: - raise TypeError('expected at most 1 arguments, got %d' % len(args)) - try: - self.__end - except AttributeError: - self.clear() - self.update(*args, **kwds) - - def clear(self): - self.__end = end = [] - end += [None, end, end] # sentinel node for doubly linked list - self.__map = {} # key --> [key, prev, next] - dict.clear(self) - - def __setitem__(self, key, value): - if key not in self: - end = self.__end - curr = end[1] - curr[2] = end[1] = self.__map[key] = [key, curr, end] - dict.__setitem__(self, key, value) - - def __delitem__(self, key): - dict.__delitem__(self, key) - key, prev, next = self.__map.pop(key) - prev[2] = next - next[1] = prev - - def __iter__(self): - end = self.__end - curr = end[2] - while curr is not end: - yield curr[0] - curr = curr[2] - - def __reversed__(self): - end = self.__end - curr = end[1] - while curr is not end: - yield curr[0] - curr = curr[1] - - def popitem(self, last=True): - if not self: - raise KeyError('dictionary is empty') - if last: - key = reversed(self).next() - else: - key = iter(self).next() - value = self.pop(key) - return key, value - - def __reduce__(self): - items = [[k, self[k]] for k in self] - tmp = self.__map, self.__end - del self.__map, self.__end - inst_dict = vars(self).copy() - self.__map, self.__end = tmp - if inst_dict: - return (self.__class__, (items,), inst_dict) - return self.__class__, (items,) - - def keys(self): - return list(self) - - setdefault = DictMixin.setdefault - update = DictMixin.update - pop = DictMixin.pop - values = DictMixin.values - items = DictMixin.items - iterkeys = DictMixin.iterkeys - itervalues = DictMixin.itervalues - iteritems = DictMixin.iteritems - - def __repr__(self): - if not self: - return '%s()' % (self.__class__.__name__,) - return '%s(%r)' % (self.__class__.__name__, self.items()) - - def copy(self): - return self.__class__(self) - - @classmethod - def fromkeys(cls, iterable, value=None): - d = cls() - for key in iterable: - d[key] = value - return d - - def __eq__(self, other): - if isinstance(other, OrderedDict): - if len(self) != len(other): - return False - for p, q in zip(self.items(), other.items()): - if p != q: - return False - return True - return dict.__eq__(self, other) - - def __ne__(self, other): - return not self == other -else: - from collections import OrderedDict - if sys.version_info < (3, 2): try: @@ -451,6 +277,8 @@ def __ne__(self, other): from functools import wraps + _CacheInfo = collections.namedtuple("CacheInfo", "hits misses maxsize currsize") + def lru_cache(maxsize=100): """Least-recently-used cache decorator. Taking from: https://github.com/MiCHiLU/python-functools32/blob/master/functools32/functools32.py @@ -461,7 +289,7 @@ def lru_cache(maxsize=100): View the cache statistics named tuple (hits, misses, maxsize, currsize) with f.cache_info(). Clear the cache and statistics with f.cache_clear(). Access the underlying function with f.__wrapped__. - See: http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used + See: https://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used """ def decorating_function(user_function, tuple=tuple, sorted=sorted, len=len, KeyError=KeyError): @@ -470,7 +298,7 @@ def decorating_function(user_function, tuple=tuple, sorted=sorted, len=len, KeyE lock = Lock() if maxsize is None: - CACHE = dict() + CACHE = {} @wraps(user_function) def wrapper(*args, **kwds): @@ -488,7 +316,7 @@ def wrapper(*args, **kwds): misses[0] += 1 return result else: - CACHE = OrderedDict() + CACHE = collections.OrderedDict() @wraps(user_function) def wrapper(*args, **kwds): @@ -528,7 +356,7 @@ def cache_clear(): return decorating_function else: - from functools import lru_cache + from functools import lru_cache # noqa: F401 class OrderedSet(collections.MutableSet): diff --git a/pythonFiles/isort/settings.py b/pythonFiles/isort/settings.py index 15cdb21094ac..dd0a71102405 100644 --- a/pythonFiles/isort/settings.py +++ b/pythonFiles/isort/settings.py @@ -25,8 +25,10 @@ from __future__ import absolute_import, division, print_function, unicode_literals import fnmatch +import io import os import posixpath +import sys from collections import namedtuple from .pie_slice import itemsview, lru_cache, native_str @@ -36,10 +38,11 @@ except ImportError: import ConfigParser as configparser -MAX_CONFIG_SEARCH_DEPTH = 25 # The number of parent directories isort will look for a config file within +MAX_CONFIG_SEARCH_DEPTH = 25 # The number of parent directories isort will look for a config file within DEFAULT_SECTIONS = ('FUTURE', 'STDLIB', 'THIRDPARTY', 'FIRSTPARTY', 'LOCALFOLDER') -WrapModes = ('GRID', 'VERTICAL', 'HANGING_INDENT', 'VERTICAL_HANGING_INDENT', 'VERTICAL_GRID', 'VERTICAL_GRID_GROUPED', 'NOQA') +WrapModes = ('GRID', 'VERTICAL', 'HANGING_INDENT', 'VERTICAL_HANGING_INDENT', 'VERTICAL_GRID', 'VERTICAL_GRID_GROUPED', + 'VERTICAL_GRID_GROUPED_NO_COMMA', 'NOQA') WrapModes = namedtuple('WrapModes', WrapModes)(*range(len(WrapModes))) # Note that none of these lists must be complete as they are simply fallbacks for when included auto-detection fails. @@ -48,6 +51,7 @@ 'skip_glob': [], 'line_length': 79, 'wrap_length': 0, + 'line_ending': None, 'sections': DEFAULT_SECTIONS, 'no_sections': False, 'known_future_library': ['__future__'], @@ -101,6 +105,7 @@ 'multi_line_output': WrapModes.GRID, 'forced_separate': [], 'indent': ' ' * 4, + 'comment_prefix': ' #', 'length_sort': False, 'add_imports': [], 'remove_imports': [], @@ -120,6 +125,7 @@ 'lines_between_types': 0, 'combine_as_imports': False, 'combine_star': False, + 'keep_direct_and_as_imports': False, 'include_trailing_comma': False, 'from_first': False, 'verbose': False, @@ -130,7 +136,9 @@ 'force_grid_wrap': 0, 'force_sort_within_sections': False, 'show_diff': False, - 'ignore_whitespace': False} + 'ignore_whitespace': False, + 'no_lines_before': [], + 'no_inline_sort': False} @lru_cache() @@ -214,7 +222,7 @@ def _as_list(value): @lru_cache() def _get_config_data(file_path, sections): - with open(file_path, 'rU') as config_file: + with io.open(file_path, 'r') as config_file: if file_path.endswith('.editorconfig'): line = '\n' last_position = config_file.tell() @@ -225,9 +233,14 @@ def _get_config_data(file_path, sections): break last_position = config_file.tell() - config = configparser.SafeConfigParser() - config.readfp(config_file) - settings = dict() + if sys.version_info >= (3, 2): + config = configparser.ConfigParser() + config.read_file(config_file) + else: + config = configparser.SafeConfigParser() + config.readfp(config_file) + + settings = {} for section in sections: if config.has_section(section): settings.update(dict(config.items(section))) From 06f28b56e2783fba2e930a196e304e094b4480bb Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 5 Jun 2018 13:06:45 -0700 Subject: [PATCH 299/433] Display all interpreters when a workspace contains a Pipfile (#1844) --- news/2 Fixes/1800.md | 1 + src/client/interpreter/locators/index.ts | 7 +------ 2 files changed, 2 insertions(+), 6 deletions(-) create mode 100644 news/2 Fixes/1800.md diff --git a/news/2 Fixes/1800.md b/news/2 Fixes/1800.md new file mode 100644 index 000000000000..a4fb662e2cd3 --- /dev/null +++ b/news/2 Fixes/1800.md @@ -0,0 +1 @@ +Fix to display all interpreters in the interpreter list when a workspace contains a `Pipfile`. diff --git a/src/client/interpreter/locators/index.ts b/src/client/interpreter/locators/index.ts index e5e05ca49d8a..45fa5fb18999 100644 --- a/src/client/interpreter/locators/index.ts +++ b/src/client/interpreter/locators/index.ts @@ -31,12 +31,6 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi this.platform = serviceContainer.get(IPlatformService); } public async getInterpreters(resource?: Uri): Promise { - // Pipenv always wins - const pipenv = this.serviceContainer.get(IInterpreterLocatorService, PIPENV_SERVICE); - const interpreters = await pipenv.getInterpreters(resource); - if (interpreters.length > 0) { - return interpreters; - } return this.getInterpretersPerResource(resource); } public dispose() { @@ -87,6 +81,7 @@ export class PythonInterpreterLocatorService implements IInterpreterLocatorServi locators.push(this.serviceContainer.get(IInterpreterLocatorService, KNOWN_PATH_SERVICE)); } locators.push(this.serviceContainer.get(IInterpreterLocatorService, CURRENT_PATH_SERVICE)); + locators.push(this.serviceContainer.get(IInterpreterLocatorService, PIPENV_SERVICE)); return locators; } From 5623e8bc09f30d24b3b917e564b13ee47f993702 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 5 Jun 2018 13:07:14 -0700 Subject: [PATCH 300/433] Aadd better error handling for parsing responses received from jedi (#1868) --- news/3 Code Health/1867.md | 1 + src/client/providers/jediProxy.ts | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 news/3 Code Health/1867.md diff --git a/news/3 Code Health/1867.md b/news/3 Code Health/1867.md new file mode 100644 index 000000000000..57590fb445fb --- /dev/null +++ b/news/3 Code Health/1867.md @@ -0,0 +1 @@ +Add better exception handling when parsing responses received from the Jedi language service. diff --git a/src/client/providers/jediProxy.ts b/src/client/providers/jediProxy.ts index 8d7811e161b8..73e1dbe44992 100644 --- a/src/client/providers/jediProxy.ts +++ b/src/client/providers/jediProxy.ts @@ -391,9 +391,15 @@ export class JediProxy implements Disposable { } responses.forEach((response) => { + if (!response) { + return; + } const responseId = JediProxy.getProperty(response, 'id'); - const cmd = >this.commands.get(responseId); - if (cmd === null) { + if (!this.commands.has(responseId)) { + return; + } + const cmd = this.commands.get(responseId); + if (!cmd) { return; } this.lastCmdIdProcessed = cmd.id; From b561665dd57ca102b6e30713da3fc4a86978c8e6 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 5 Jun 2018 13:07:23 -0700 Subject: [PATCH 301/433] Update typescript package to 2.9.1 (#1816) --- news/3 Code Health/1815.md | 1 + package.json | 2 +- yarn.lock | 6 +++--- 3 files changed, 5 insertions(+), 4 deletions(-) create mode 100644 news/3 Code Health/1815.md diff --git a/news/3 Code Health/1815.md b/news/3 Code Health/1815.md new file mode 100644 index 000000000000..9ce860c5f6d6 --- /dev/null +++ b/news/3 Code Health/1815.md @@ -0,0 +1 @@ +Update typescript package to 2.9.1 diff --git a/package.json b/package.json index d4f2a601769c..0e59e807a20c 100644 --- a/package.json +++ b/package.json @@ -1955,7 +1955,7 @@ "tslint-eslint-rules": "^5.1.0", "tslint-microsoft-contrib": "^5.0.3", "typemoq": "^2.1.0", - "typescript": "2.8.3", + "typescript": "^2.9.1", "typescript-formatter": "^7.1.0", "vscode": "^1.1.5", "vscode-debugadapter-testsupport": "^1.27.0" diff --git a/yarn.lock b/yarn.lock index ac469a87f2e0..8e935a591761 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4616,9 +4616,9 @@ typescript-formatter@^7.1.0: commandpost "^1.0.0" editorconfig "^0.15.0" -typescript@2.8.3: - version "2.8.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.8.3.tgz#5d817f9b6f31bb871835f4edf0089f21abe6c170" +typescript@^2.9.1: + version "2.9.1" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.1.tgz#fdb19d2c67a15d11995fd15640e373e09ab09961" uglify-js@^2.6: version "2.8.29" From 8167de88a50a607f915819fbd1702e649051ceeb Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 5 Jun 2018 13:48:28 -0700 Subject: [PATCH 302/433] Add Tests to measure activation times of extension (#1813) * Tests to measure activation times of extension * Code review fixes * Run performance tests on master --- .gitignore | 1 + .travis.yml | 8 + .vscodeignore | 1 + news/3 Code Health/932.md | 1 + package.json | 4 + src/test/index.ts | 8 +- src/test/performance/load.perf.test.ts | 72 ++++ src/test/performance/sample.py | 0 src/test/performance/settings.json | 1 + src/test/performanceTest.ts | 177 ++++++++ src/test/standardTest.ts | 2 +- src/test/testRunner.ts | 27 +- yarn.lock | 538 ++++++++++++++++++++++++- 13 files changed, 819 insertions(+), 21 deletions(-) create mode 100644 news/3 Code Health/932.md create mode 100644 src/test/performance/load.perf.test.ts create mode 100644 src/test/performance/sample.py create mode 100644 src/test/performance/settings.json create mode 100644 src/test/performanceTest.ts diff --git a/.gitignore b/.gitignore index d872c09b46b9..428c08f2a9d2 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ analysis/** bin/** obj/** .pytest_cache +tmp/** diff --git a/.travis.yml b/.travis.yml index 2e61eb19954d..ca1394f3ea83 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,6 +27,9 @@ matrix: - os: linux python: "3.6-dev" env: MULTIROOT_WORKSPACE_TEST=true + - os: linux + python: "3.6-dev" + env: PERFORMANCE_TEST=true allow_failures: - os: linux python: "2.7" @@ -111,6 +114,11 @@ script: - if [ $TRAVIS_UPLOAD_COVERAGE == "true" ]; then bash <(curl -s https://codecov.io/bash); fi + - if [[ "$TRAVIS_BRANCH" == "master" && "$TRAVIS_PULL_REQUEST" == "false" && "$PERFORMANCE_TEST" == "true" ]]; then + yarn run clean; + yarn run vscode:prepublish; + yarn run testPerformance --silent; + fi - if [ "$TRAVIS_PYTHON_VERSION" != "2.7" ]; then python3 -m pip install --upgrade -r news/requirements.txt; python3 news/announce.py --dry_run; diff --git a/.vscodeignore b/.vscodeignore index 9e3e7a4c7326..f6ba7c39ff26 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -45,5 +45,6 @@ requirements.txt scripts/** src/** test/** +tmp/** typings/** vsc-extension-quickstart.md diff --git a/news/3 Code Health/932.md b/news/3 Code Health/932.md new file mode 100644 index 000000000000..6e2f9c6ace41 --- /dev/null +++ b/news/3 Code Health/932.md @@ -0,0 +1 @@ +Create tests to measure activation times for the extension. diff --git a/package.json b/package.json index 0e59e807a20c..1dabfa1e8ac0 100644 --- a/package.json +++ b/package.json @@ -1853,6 +1853,7 @@ "testSingleWorkspace": "node ./out/test/standardTest.js", "testMultiWorkspace": "node ./out/test/multiRootTest.js", "testAnalysisEngine": "node ./out/test/analysisEngineTest.js", + "testPerformance": "node ./out/test/performanceTest.js", "precommit": "node gulpfile.js", "lint-staged": "node gulpfile.js", "lint": "tslint src/**/*.ts -t verbose", @@ -1903,6 +1904,7 @@ "@types/chai-arrays": "^1.0.2", "@types/chai-as-promised": "^7.1.0", "@types/del": "^3.0.0", + "@types/download": "^6.2.2", "@types/dotenv": "^4.0.3", "@types/event-stream": "^3.3.33", "@types/fs-extra": "^5.0.1", @@ -1914,6 +1916,7 @@ "@types/md5": "^2.1.32", "@types/mocha": "^2.2.48", "@types/node": "^9.4.7", + "@types/request": "^2.47.0", "@types/semver": "^5.5.0", "@types/shortid": "^0.0.29", "@types/sinon": "^4.3.0", @@ -1931,6 +1934,7 @@ "debounce": "^1.1.0", "decache": "^4.4.0", "del": "^3.0.0", + "download": "^7.0.0", "event-stream": "^3.3.4", "flat": "^4.0.0", "gulp": "^3.9.1", diff --git a/src/test/index.ts b/src/test/index.ts index 135ca5d8b6c1..c80bdf385aac 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -3,7 +3,6 @@ if ((Reflect as any).metadata === undefined) { // tslint:disable-next-line:no-require-imports no-var-requires require('reflect-metadata'); } -import { MochaSetupOptions } from 'vscode/lib/testrunner'; import { IS_CI_SERVER, IS_CI_SERVER_TEST_DEBUGGER, IS_MULTI_ROOT_TEST } from './constants'; import * as testRunner from './testRunner'; @@ -15,15 +14,18 @@ process.env.IS_MULTI_ROOT_TEST = IS_MULTI_ROOT_TEST.toString(); // So the solution is to run them separately and first on CI. const grep = IS_CI_SERVER && IS_CI_SERVER_TEST_DEBUGGER ? 'Debug' : undefined; +const testFilesSuffix = process.env.TEST_FILES_SUFFIX; + // You can directly control Mocha options by uncommenting the following lines. // See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info. // Hack, as retries is not supported as setting in tsd. -const options: MochaSetupOptions & { retries: number } = { +const options: testRunner.SetupOptions & { retries: number } = { ui: 'tdd', useColors: true, timeout: 25000, retries: 3, - grep + grep, + testFilesSuffix }; testRunner.configure(options, { coverageConfig: '../coverconfig.json' }); module.exports = testRunner; diff --git a/src/test/performance/load.perf.test.ts b/src/test/performance/load.perf.test.ts new file mode 100644 index 000000000000..d68ce859d597 --- /dev/null +++ b/src/test/performance/load.perf.test.ts @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:no-invalid-this no-console + +import { expect } from 'chai'; +import * as fs from 'fs-extra'; +import { EOL } from 'os'; +import * as path from 'path'; +import { commands, extensions } from 'vscode'; +import { StopWatch } from '../../client/common/stopWatch'; + +const AllowedIncreaseInActivationDelayInMS = 500; + +suite('Activation Times', () => { + if (process.env.ACTIVATION_TIMES_LOG_FILE_PATH) { + const logFile = process.env.ACTIVATION_TIMES_LOG_FILE_PATH; + const sampleCounter = fs.existsSync(logFile) ? fs.readFileSync(logFile, { encoding: 'utf8' }).toString().split(/\r?\n/g).length : 1; + if (sampleCounter > 10) { + return; + } + test(`Capture Extension Activation Times (Version: ${process.env.ACTIVATION_TIMES_EXT_VERSION}, sample: ${sampleCounter})`, async () => { + const pythonExtension = extensions.getExtension('ms-python.python'); + if (pythonExtension) { + throw new Error('Python Extension not found'); + } + const stopWatch = new StopWatch(); + await pythonExtension!.activate(); + const elapsedTime = stopWatch.elapsedTime; + if (elapsedTime > 10) { + await fs.ensureDir(path.dirname(logFile)); + await fs.appendFile(logFile, `${elapsedTime}${EOL}`, { encoding: 'utf8' }); + console.log(`Loaded in ${elapsedTime}ms`); + } + commands.executeCommand('workbench.action.reloadWindow'); + }); + } + + if (process.env.ACTIVATION_TIMES_DEV_LOG_FILE_PATHS && + process.env.ACTIVATION_TIMES_RELEASE_LOG_FILE_PATHS && + process.env.ACTIVATION_TIMES_DEV_ANALYSIS_LOG_FILE_PATHS) { + + test('Test activation times of Dev vs Release Extension', async () => { + function getActivationTimes(files: string[]) { + const activationTimes: number[] = []; + for (const file of files) { + fs.readFileSync(file, { encoding: 'utf8' }).toString() + .split(/\r?\n/g) + .map(line => line.trim()) + .filter(line => line.length > 0) + .map(line => parseInt(line, 10)) + .forEach(item => activationTimes.push(item)); + } + return activationTimes; + } + const devActivationTimes = getActivationTimes(JSON.parse(process.env.ACTIVATION_TIMES_DEV_LOG_FILE_PATHS!)); + const releaseActivationTimes = getActivationTimes(JSON.parse(process.env.ACTIVATION_TIMES_RELEASE_LOG_FILE_PATHS!)); + const analysisEngineActivationTimes = getActivationTimes(JSON.parse(process.env.ACTIVATION_TIMES_DEV_ANALYSIS_LOG_FILE_PATHS!)); + const devActivationAvgTime = devActivationTimes.reduce((sum, item) => sum + item, 0) / devActivationTimes.length; + const releaseActivationAvgTime = releaseActivationTimes.reduce((sum, item) => sum + item, 0) / releaseActivationTimes.length; + const analysisEngineActivationAvgTime = analysisEngineActivationTimes.reduce((sum, item) => sum + item, 0) / analysisEngineActivationTimes.length; + + console.log(`Dev version Loaded in ${devActivationAvgTime}ms`); + console.log(`Release version Loaded in ${releaseActivationAvgTime}ms`); + console.log(`Analysis Engine Loaded in ${analysisEngineActivationAvgTime}ms`); + + expect(devActivationAvgTime - releaseActivationAvgTime).to.be.lessThan(AllowedIncreaseInActivationDelayInMS, 'Activation times have increased above allowed threshold.'); + }); + } +}); diff --git a/src/test/performance/sample.py b/src/test/performance/sample.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/test/performance/settings.json b/src/test/performance/settings.json new file mode 100644 index 000000000000..809ebf6ab2f4 --- /dev/null +++ b/src/test/performance/settings.json @@ -0,0 +1 @@ +{ "python.jediEnabled": true } diff --git a/src/test/performanceTest.ts b/src/test/performanceTest.ts new file mode 100644 index 000000000000..c84adbc93ce8 --- /dev/null +++ b/src/test/performanceTest.ts @@ -0,0 +1,177 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +/* +Comparing performance metrics is not easy (the metrics can and always get skewed). +One approach is to run the tests multile times and gather multiple sample data. +For Extension activation times, we load both extensions x times, and re-load the window y times in each x load. +I.e. capture averages by giving the extensions sufficient time to warm up. +This block of code merely launches the tests by using either the dev or release version of the extension, +and spawning the tests (mimic user starting tests from command line), this way we can run tests multiple times. +*/ + +// tslint:disable:no-console no-require-imports no-var-requires + +import { spawn } from 'child_process'; +import * as download from 'download'; +import * as fs from 'fs-extra'; +import * as path from 'path'; +import * as request from 'request'; +import { EXTENSION_ROOT_DIR } from '../client/common/constants'; + +const NamedRegexp = require('named-js-regexp'); +const StreamZip = require('node-stream-zip'); +const del = require('del'); + +const tmpFolder = path.join(EXTENSION_ROOT_DIR, 'tmp'); +const publishedExtensionPath = path.join(tmpFolder, 'ext', 'testReleaseExtensionsFolder'); +const logFilesPath = path.join(tmpFolder, 'test', 'logs'); + +enum Version { + Dev, Release +} + +class TestRunner { + public async start() { + await del([path.join(tmpFolder, '**')]); + await this.extractLatestExtension(publishedExtensionPath); + + const timesToLoadEachVersion = 3; + const devLogFiles: string[] = []; + const releaseLogFiles: string[] = []; + const newAnalysisEngineLogFiles: string[] = []; + + for (let i = 0; i < timesToLoadEachVersion; i += 1) { + await this.enableNewAnalysisEngine(false); + + const devLogFile = path.join(logFilesPath, `dev_loadtimes${i}.txt`); + await this.capturePerfTimes(Version.Dev, devLogFile); + devLogFiles.push(devLogFile); + + const releaseLogFile = path.join(logFilesPath, `release_loadtimes${i}.txt`); + await this.capturePerfTimes(Version.Release, releaseLogFile); + releaseLogFiles.push(releaseLogFile); + + // New Analysis engine. + await this.enableNewAnalysisEngine(true); + const newAnalysisEngineLogFile = path.join(logFilesPath, `newAnalysisEngine_loadtimes${i}.txt`); + await this.capturePerfTimes(Version.Release, newAnalysisEngineLogFile); + newAnalysisEngineLogFiles.push(newAnalysisEngineLogFile); + } + + await this.runPerfTest(devLogFiles, releaseLogFiles, newAnalysisEngineLogFiles); + } + private async enableNewAnalysisEngine(enable: boolean) { + const settings = `{ "python.jediEnabled": ${!enable} }`; + await fs.writeFile(path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'performance', 'settings.json'), settings); + } + + private async capturePerfTimes(version: Version, logFile: string) { + const releaseVersion = await this.getReleaseVersion(); + const devVersion = await this.getDevVersion(); + await fs.ensureDir(path.dirname(logFile)); + const env: { [key: string]: {} } = { + ACTIVATION_TIMES_LOG_FILE_PATH: logFile, + ACTIVATION_TIMES_EXT_VERSION: version === Version.Release ? releaseVersion : devVersion, + CODE_EXTENSIONS_PATH: version === Version.Release ? publishedExtensionPath : EXTENSION_ROOT_DIR + }; + + await this.launchTest(env); + } + private async runPerfTest(devLogFiles: string[], releaseLogFiles: string[], newAnalysisEngineLogFiles: string[]) { + const env: { [key: string]: {} } = { + ACTIVATION_TIMES_DEV_LOG_FILE_PATHS: JSON.stringify(devLogFiles), + ACTIVATION_TIMES_RELEASE_LOG_FILE_PATHS: JSON.stringify(releaseLogFiles), + ACTIVATION_TIMES_DEV_ANALYSIS_LOG_FILE_PATHS: JSON.stringify(newAnalysisEngineLogFiles) + }; + + await this.launchTest(env); + } + + private async launchTest(customEnvVars: { [key: string]: {} }) { + await new Promise((resolve, reject) => { + const env: { [key: string]: {} } = { + TEST_FILES_SUFFIX: 'perf.test', + CODE_TESTS_WORKSPACE: path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'performance'), + ...process.env, + ...customEnvVars + }; + + const proc = spawn('node', [path.join(__dirname, 'standardTest.js')], { cwd: EXTENSION_ROOT_DIR, env }); + proc.stdout.pipe(process.stdout); + proc.stderr.pipe(process.stderr); + proc.on('error', reject); + proc.on('close', code => { + if (code === 0) { + resolve(); + } else { + reject(`Failed with code ${code}.`); + } + }); + }); + } + + private async extractLatestExtension(targetDir: string): Promise { + const extensionFile = await this.downloadExtension(); + await this.unzip(extensionFile, targetDir); + } + + private async getReleaseVersion(): Promise { + const url = 'https://marketplace.visualstudio.com/items?itemName=ms-python.python'; + const content = await new Promise((resolve, reject) => { + request(url, (error, response, body) => { + if (error) { + return reject(error); + } + if (response.statusCode === 200) { + return resolve(body); + } + reject(`Status code of ${response.statusCode} received.`); + }); + }); + const re = NamedRegexp('"version"\S?:\S?"(:\\d{4}\\.\\d{1,2}\\.\\d{1,2})"', 'g'); + const matches = re.exec(content); + return matches.groups().version; + } + + private async getDevVersion(): Promise { + // tslint:disable-next-line:non-literal-require + return require(path.join(EXTENSION_ROOT_DIR, 'package.json')).version; + } + + private async unzip(zipFile: string, targetFolder: string): Promise { + await fs.ensureDir(targetFolder); + return new Promise((resolve, reject) => { + const zip = new StreamZip({ + file: zipFile, + storeEntries: true + }); + zip.on('ready', async () => { + zip.extract('extension', targetFolder, err => { + if (err) { + reject(err); + } else { + resolve(); + } + zip.close(); + }); + }); + }); + } + + private async downloadExtension(): Promise { + const version = await this.getReleaseVersion(); + const url = `https://marketplace.visualstudio.com/_apis/public/gallery/publishers/ms-python/vsextensions/python/${version}/vspackage`; + const destination = path.join(__dirname, `extension${version}.zip`); + if (await fs.pathExists(destination)) { + return destination; + } + + await download(url, path.dirname(destination), { filename: path.basename(destination) }); + return destination; + } +} + +new TestRunner().start().catch(ex => console.error('Error in running Performance Tests', ex)); diff --git a/src/test/standardTest.ts b/src/test/standardTest.ts index b64b3074f577..b147d0a156b8 100644 --- a/src/test/standardTest.ts +++ b/src/test/standardTest.ts @@ -2,7 +2,7 @@ import * as path from 'path'; -process.env.CODE_TESTS_WORKSPACE = path.join(__dirname, '..', '..', 'src', 'test'); +process.env.CODE_TESTS_WORKSPACE = process.env.CODE_TESTS_WORKSPACE ? process.env.CODE_TESTS_WORKSPACE : path.join(__dirname, '..', '..', 'src', 'test'); process.env.IS_CI_SERVER_TEST_DEBUGGER = ''; function start() { diff --git a/src/test/testRunner.ts b/src/test/testRunner.ts index 7699cc9cc8fb..7c90ef5f1a13 100644 --- a/src/test/testRunner.ts +++ b/src/test/testRunner.ts @@ -25,13 +25,13 @@ interface ITestRunnerOptions { // http://gotwarlost.github.io/istanbul/public/apidocs/files/lib_instrumenter.js.html#l478. type CoverState = { - path: string, - s: {}, - b: {}, - f: {}, - fnMap: {}, - statementMap: {}, - branchMap: {} + path: string; + s: {}; + b: {}; + f: {}; + fnMap: {}; + statementMap: {}; + branchMap: {}; }; type Instrumenter = istanbul.Instrumenter & { coverState: CoverState }; @@ -49,10 +49,15 @@ let mocha = new Mocha({ useColors: true }); +export type SetupOptions = MochaSetupOptions & { testFilesSuffix?: string }; +let testFilesGlob = 'test'; let coverageOptions: { coverageConfig: string } | undefined; -export function configure(mochaOpts: MochaSetupOptions, coverageOpts?: { coverageConfig: string }): void { - mocha = new Mocha(mochaOpts); +export function configure(setupOptions: SetupOptions, coverageOpts?: { coverageConfig: string }): void { + if (setupOptions.testFilesSuffix) { + testFilesGlob = setupOptions.testFilesSuffix; + } + mocha = new Mocha(setupOptions); coverageOptions = coverageOpts; } @@ -70,7 +75,7 @@ export function run(testsRoot: string, callback: TestCallback): void { } // Run the tests. - glob('**/**.test.js', { cwd: testsRoot }, (error, files) => { + glob(`**/**.${testFilesGlob}.js`, { cwd: testsRoot }, (error, files) => { if (error) { return callback(error); } @@ -94,7 +99,7 @@ function getCoverageOptions(testsRoot: string): ITestRunnerOptions | undefined { class CoverageRunner { private coverageVar: string = `$$cov_${new Date().getTime()}$$`; private sourceFiles: string[] = []; - private instrumenter: Instrumenter; + private instrumenter!: Instrumenter; private get coverage(): { [key: string]: CoverState } { if (global[this.coverageVar] === undefined || Object.keys(global[this.coverageVar]).length === 0) { diff --git a/yarn.lock b/yarn.lock index 8e935a591761..8426a5bbe67e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19,12 +19,20 @@ normalize-path "^2.0.1" through2 "^2.0.3" +"@sindresorhus/is@^0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" + "@sinonjs/formatio@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-2.0.0.tgz#84db7e9eb5531df18a8c5e0bfb6e449e55e654b2" dependencies: samsam "1.3.0" +"@types/caseless@*": + version "0.12.1" + resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.1.tgz#9794c69c8385d0192acc471a540d1f8e0d16218a" + "@types/chai-arrays@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@types/chai-arrays/-/chai-arrays-1.0.2.tgz#1f89c183c960334c47d9f24105195c4326db0cc7" @@ -47,12 +55,25 @@ dependencies: commander "*" +"@types/decompress@*": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@types/decompress/-/decompress-4.2.2.tgz#38a299e981862a898e5ac84eb1adc9329a0bad56" + dependencies: + "@types/node" "*" + "@types/del@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/del/-/del-3.0.0.tgz#1c8cd8b6e38da3b572352ca8eaf5527931426288" dependencies: "@types/glob" "*" +"@types/download@^6.2.2": + version "6.2.2" + resolved "https://registry.yarnpkg.com/@types/download/-/download-6.2.2.tgz#11385a3fd6e1f3300362fb52db119420ecc0fb34" + dependencies: + "@types/decompress" "*" + "@types/got" "*" + "@types/dotenv@^4.0.3": version "4.0.3" resolved "https://registry.yarnpkg.com/@types/dotenv/-/dotenv-4.0.3.tgz#ebcfc40da7bc0728b705945b7db48485ec5b4b67" @@ -69,6 +90,12 @@ version "1.2.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" +"@types/form-data@*": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-2.2.1.tgz#ee2b3b8eaa11c0938289953606b745b738c54b1e" + dependencies: + "@types/node" "*" + "@types/fs-extra@^5.0.1": version "5.0.1" resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.0.1.tgz#cd856fbbdd6af2c11f26f8928fd8644c9e9616c9" @@ -87,6 +114,12 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/got@*": + version "8.3.1" + resolved "https://registry.yarnpkg.com/@types/got/-/got-8.3.1.tgz#823ded0ce469895be3d01553fec31ff86339ff9f" + dependencies: + "@types/node" "*" + "@types/iconv-lite@^0.0.1": version "0.0.1" resolved "https://registry.yarnpkg.com/@types/iconv-lite/-/iconv-lite-0.0.1.tgz#aa3b8bda2be512b1ae0a057b942e869c370a5569" @@ -119,6 +152,15 @@ version "9.4.7" resolved "https://registry.yarnpkg.com/@types/node/-/node-9.4.7.tgz#57d81cd98719df2c9de118f2d5f3b1120dcd7275" +"@types/request@^2.47.0": + version "2.47.0" + resolved "https://registry.yarnpkg.com/@types/request/-/request-2.47.0.tgz#76a666cee4cb85dcffea6cd4645227926d9e114e" + dependencies: + "@types/caseless" "*" + "@types/form-data" "*" + "@types/node" "*" + "@types/tough-cookie" "*" + "@types/semver@^5.4.0", "@types/semver@^5.5.0": version "5.5.0" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45" @@ -131,6 +173,10 @@ version "4.3.0" resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-4.3.0.tgz#7f53915994a00ccea24f4e0c24709822ed11a3b1" +"@types/tough-cookie@*": + version "2.3.3" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.3.tgz#7f226d67d654ec9070e755f46daebf014628e9d9" + "@types/untildify@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/untildify/-/untildify-3.0.0.tgz#cd3e6624e46ccf292d3823fb48fa90dda0deaec0" @@ -462,6 +508,10 @@ balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" +base64-js@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978" + base@^0.11.1: version "0.11.2" resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" @@ -488,6 +538,13 @@ binary-extensions@^1.0.0: version "1.11.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205" +bl@^1.0.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c" + dependencies: + readable-stream "^2.3.5" + safe-buffer "^5.1.1" + block-stream@*: version "0.0.9" resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" @@ -560,6 +617,17 @@ browserify-mime@~1.2.9: version "1.2.9" resolved "https://registry.yarnpkg.com/browserify-mime/-/browserify-mime-1.2.9.tgz#aeb1af28de6c0d7a6a2ce40adb68ff18422af31f" +buffer-alloc-unsafe@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" + +buffer-alloc@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec" + dependencies: + buffer-alloc-unsafe "^1.1.0" + buffer-fill "^1.0.0" + buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" @@ -568,6 +636,18 @@ buffer-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" +buffer-fill@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" + +buffer@^3.0.1: + version "3.6.0" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-3.6.0.tgz#a72c936f77b96bf52f5f7e7b467180628551defb" + dependencies: + base64-js "0.0.8" + ieee754 "^1.1.4" + isarray "^1.0.0" + builtin-modules@^1.0.0, builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -586,6 +666,18 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" +cacheable-request@^2.1.1: + version "2.1.4" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d" + dependencies: + clone-response "1.0.2" + get-stream "3.0.0" + http-cache-semantics "3.8.1" + keyv "3.0.0" + lowercase-keys "1.0.0" + normalize-url "2.0.1" + responselike "1.0.2" + callsite@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" @@ -613,6 +705,15 @@ caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" +caw@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/caw/-/caw-2.0.1.tgz#6c3ca071fc194720883c2dc5da9b074bfc7e9e95" + dependencies: + get-proxy "^2.0.0" + isurl "^1.0.0-alpha5" + tunnel-agent "^0.6.0" + url-to-options "^1.0.1" + center-align@^0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" @@ -739,6 +840,12 @@ clone-buffer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" +clone-response@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" + dependencies: + mimic-response "^1.0.0" + clone-stats@^0.0.1, clone-stats@~0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1" @@ -822,6 +929,12 @@ commander@2.11.0: version "2.11.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" +commander@~2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4" + dependencies: + graceful-readlink ">= 1.0.0" + commandpost@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/commandpost/-/commandpost-1.3.0.tgz#e0654e4933abf58406c7d3b77ce747083da178c4" @@ -834,7 +947,7 @@ concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" -config-chain@~1.1.5: +config-chain@^1.1.11, config-chain@~1.1.5: version "1.1.11" resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.11.tgz#aba09747dfbe4c3e70e766a6e41586e1859fc6f2" dependencies: @@ -845,6 +958,10 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" +content-disposition@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" + convert-source-map@1.X, convert-source-map@^1.1.1, convert-source-map@^1.5.0: version "1.5.1" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" @@ -953,6 +1070,60 @@ decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" +decompress-response@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" + dependencies: + mimic-response "^1.0.0" + +decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1" + dependencies: + file-type "^5.2.0" + is-stream "^1.1.0" + tar-stream "^1.5.2" + +decompress-tarbz2@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz#3082a5b880ea4043816349f378b56c516be1a39b" + dependencies: + decompress-tar "^4.1.0" + file-type "^6.1.0" + is-stream "^1.1.0" + seek-bzip "^1.0.5" + unbzip2-stream "^1.0.9" + +decompress-targz@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/decompress-targz/-/decompress-targz-4.1.1.tgz#c09bc35c4d11f3de09f2d2da53e9de23e7ce1eee" + dependencies: + decompress-tar "^4.1.1" + file-type "^5.2.0" + is-stream "^1.1.0" + +decompress-unzip@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69" + dependencies: + file-type "^3.8.0" + get-stream "^2.2.0" + pify "^2.3.0" + yauzl "^2.4.2" + +decompress@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/decompress/-/decompress-4.2.0.tgz#7aedd85427e5a92dacfe55674a7c505e96d01f9d" + dependencies: + decompress-tar "^4.0.0" + decompress-tarbz2 "^4.0.0" + decompress-targz "^4.0.0" + decompress-unzip "^4.0.1" + graceful-fs "^4.1.10" + make-dir "^1.0.0" + pify "^2.3.0" + strip-dirs "^2.0.0" + deep-assign@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/deep-assign/-/deep-assign-1.0.0.tgz#b092743be8427dc621ea0067cdec7e70dd19f37b" @@ -1077,6 +1248,22 @@ doctrine@0.7.2: esutils "^1.1.6" isarray "0.0.1" +download@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/download/-/download-7.0.0.tgz#8711f09174b11d8ded14840d1881e3d05190195c" + dependencies: + caw "^2.0.1" + content-disposition "^0.5.2" + decompress "^4.2.0" + ext-name "^5.0.0" + file-type "^7.7.1" + filenamify "^2.0.0" + get-stream "^3.0.0" + got "^8.3.1" + make-dir "^1.2.0" + p-event "^1.3.0" + pify "^3.0.0" + dotenv@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-5.0.1.tgz#a5317459bd3d79ab88cff6e44057a6a3fbb1fcef" @@ -1087,6 +1274,10 @@ duplexer2@0.0.2: dependencies: readable-stream "~1.1.9" +duplexer3@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" + duplexer@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" @@ -1260,6 +1451,19 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2: dependencies: homedir-polyfill "^1.0.1" +ext-list@^2.0.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/ext-list/-/ext-list-2.2.2.tgz#0b98e64ed82f5acf0f2931babf69212ef52ddd37" + dependencies: + mime-db "^1.28.0" + +ext-name@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ext-name/-/ext-name-5.0.0.tgz#70781981d183ee15d13993c8822045c506c8f0a6" + dependencies: + ext-list "^2.0.0" + sort-keys-length "^1.0.0" + extend-shallow@^1.1.2: version "1.1.4" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-1.1.4.tgz#19d6bf94dfc09d76ba711f39b872d21ff4dd9071" @@ -1340,10 +1544,38 @@ fd-slicer@~1.0.1: dependencies: pend "~1.2.0" +file-type@^3.8.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9" + +file-type@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6" + +file-type@^6.1.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919" + +file-type@^7.7.1: + version "7.7.1" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-7.7.1.tgz#91c2f5edb8ce70688b9b68a90d931bbb6cb21f65" + filename-regex@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" +filename-reserved-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229" + +filenamify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-2.0.0.tgz#bd162262c0b6e94bfbcdcf19a3bbb3764f785695" + dependencies: + filename-reserved-regex "^2.0.0" + strip-outer "^1.0.0" + trim-repeated "^1.0.0" + fill-range@^2.1.0: version "2.2.3" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" @@ -1466,10 +1698,21 @@ fragment-cache@^0.2.1: dependencies: map-cache "^0.2.2" +from2@^2.1.1: + version "2.3.0" + resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" + dependencies: + inherits "^2.0.1" + readable-stream "^2.0.0" + from@~0: version "0.1.7" resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + fs-extra@4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" @@ -1564,10 +1807,27 @@ get-port@3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" +get-proxy@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/get-proxy/-/get-proxy-2.1.0.tgz#349f2b4d91d44c4d4d4e9cba2ad90143fac5ef93" + dependencies: + npm-conf "^1.1.0" + get-stdin@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" +get-stream@3.0.0, get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + +get-stream@^2.2.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" + dependencies: + object-assign "^4.0.1" + pinkie-promise "^2.0.0" + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -1729,7 +1989,29 @@ glogg@^1.0.0: dependencies: sparkles "^1.0.0" -graceful-fs@4.X, graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6: +got@^8.3.1: + version "8.3.1" + resolved "https://registry.yarnpkg.com/got/-/got-8.3.1.tgz#093324403d4d955f5a16a7a8d39955d055ae10ed" + dependencies: + "@sindresorhus/is" "^0.7.0" + cacheable-request "^2.1.1" + decompress-response "^3.3.0" + duplexer3 "^0.1.4" + get-stream "^3.0.0" + into-stream "^3.1.0" + is-retry-allowed "^1.1.0" + isurl "^1.0.0-alpha5" + lowercase-keys "^1.0.0" + mimic-response "^1.0.0" + p-cancelable "^0.4.0" + p-timeout "^2.0.1" + pify "^3.0.0" + safe-buffer "^5.1.1" + timed-out "^4.0.1" + url-parse-lax "^3.0.0" + url-to-options "^1.0.1" + +graceful-fs@4.X, graceful-fs@^4.0.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -1743,6 +2025,10 @@ graceful-fs@~1.2.0: version "1.2.3" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-1.2.3.tgz#15a4806a57547cb2d2dbf27f42e89a8c3451b364" +"graceful-readlink@>= 1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" + growl@1.10.3: version "1.10.3" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.3.tgz#1926ba90cf3edfe2adb4927f5880bc22c66c790f" @@ -2037,10 +2323,20 @@ has-gulplog@^0.1.0: dependencies: sparkles "^1.0.0" +has-symbol-support-x@^1.4.1: + version "1.4.2" + resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455" + has-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" +has-to-string-tag-x@^1.2.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d" + dependencies: + has-symbol-support-x "^1.4.1" + has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -2119,6 +2415,10 @@ hosted-git-info@^2.1.4: version "2.6.0" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.6.0.tgz#23235b29ab230c576aab0d4f13fc046b0b038222" +http-cache-semantics@3.8.1: + version "3.8.1" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" + http-signature@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" @@ -2149,6 +2449,10 @@ iconv-lite@0.4.21: dependencies: safer-buffer "^2.1.0" +ieee754@^1.1.4: + version "1.1.11" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.11.tgz#c16384ffe00f5b7835824e67b6f2bd44a5229455" + indent-string@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" @@ -2178,6 +2482,13 @@ interpret@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" +into-stream@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6" + dependencies: + from2 "^2.1.1" + p-is-promise "^1.1.0" + inversify@4.11.1: version "4.11.1" resolved "https://registry.yarnpkg.com/inversify/-/inversify-4.11.1.tgz#9a10635d1fd347da11da96475b3608babd5945a6" @@ -2327,6 +2638,10 @@ is-my-json-valid@^2.12.4: jsonpointer "^4.0.0" xtend "^4.0.0" +is-natural-number@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" + is-negated-glob@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" @@ -2351,6 +2666,10 @@ is-obj@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" +is-object@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" + is-odd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-2.0.0.tgz#7646624671fd7ea558ccd9a2795182f2958f1b24" @@ -2373,6 +2692,10 @@ is-path-inside@^1.0.0: dependencies: path-is-inside "^1.0.1" +is-plain-obj@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" @@ -2401,6 +2724,10 @@ is-relative@^1.0.0: dependencies: is-unc-path "^1.0.0" +is-retry-allowed@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" + is-running@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-running/-/is-running-2.1.0.tgz#30a73ff5cc3854e4fc25490809e9f5abf8de09e0" @@ -2447,7 +2774,7 @@ isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" -isarray@1.0.0, isarray@~1.0.0: +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -2488,6 +2815,13 @@ istanbul@0.4.5, istanbul@^0.4.5: which "^1.1.1" wordwrap "^1.0.0" +isurl@^1.0.0-alpha5: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67" + dependencies: + has-to-string-tag-x "^1.2.0" + is-object "^1.0.1" + js-beautify@^1.7.5: version "1.7.5" resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.7.5.tgz#69d9651ef60dbb649f65527b53674950138a7919" @@ -2512,6 +2846,10 @@ jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" +json-buffer@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" + json-edm-parser@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/json-edm-parser/-/json-edm-parser-0.1.2.tgz#1e60b0fef1bc0af67bc0d146dfdde5486cd615b4" @@ -2571,6 +2909,12 @@ just-extend@^1.1.27: version "1.1.27" resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-1.1.27.tgz#ec6e79410ff914e472652abfa0e603c03d60e905" +keyv@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.0.0.tgz#44923ba39e68b12a7cec7df6c3268c031f2ef373" + dependencies: + json-buffer "3.0.0" + kind-of@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-1.1.0.tgz#140a3d2d41a36d2efcfa9377b62c24f8495a5c44" @@ -2912,6 +3256,14 @@ loud-rejection@^1.0.0: currently-unhandled "^0.4.1" signal-exit "^3.0.0" +lowercase-keys@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" + +lowercase-keys@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" + lru-cache@2: version "2.7.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" @@ -2935,6 +3287,12 @@ lru-queue@0.1: dependencies: es5-ext "~0.10.2" +make-dir@^1.0.0, make-dir@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" + dependencies: + pify "^3.0.0" + make-iterator@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.0.tgz#57bef5dc85d23923ba23767324d8e8f8f3d9694b" @@ -3044,7 +3402,7 @@ micromatch@^3.0.4, micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" -mime-db@~1.33.0: +mime-db@^1.28.0, mime-db@~1.33.0: version "1.33.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" @@ -3054,6 +3412,10 @@ mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.7: dependencies: mime-db "~1.33.0" +mimic-response@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.0.tgz#df3d3652a73fded6b9b0b24146e6fd052353458e" + "minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" @@ -3258,12 +3620,27 @@ normalize-path@^2.0.0, normalize-path@^2.0.1, normalize-path@^2.1.1: dependencies: remove-trailing-separator "^1.0.1" +normalize-url@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6" + dependencies: + prepend-http "^2.0.0" + query-string "^5.0.1" + sort-keys "^2.0.0" + now-and-later@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.0.tgz#bc61cbb456d79cb32207ce47ca05136ff2e7d6ee" dependencies: once "^1.3.2" +npm-conf@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9" + dependencies: + config-chain "^1.1.11" + pify "^3.0.0" + npmlog@^4.0.2: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" @@ -3421,10 +3798,40 @@ osenv@^0.1.4: os-homedir "^1.0.0" os-tmpdir "^1.0.0" +p-cancelable@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0" + +p-event@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-event/-/p-event-1.3.0.tgz#8e6b4f4f65c72bc5b6fe28b75eda874f96a4a085" + dependencies: + p-timeout "^1.1.1" + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + +p-is-promise@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e" + p-map@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" +p-timeout@^1.1.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-1.2.1.tgz#5eb3b353b7fce99f101a1038880bb054ebbea386" + dependencies: + p-finally "^1.0.0" + +p-timeout@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038" + dependencies: + p-finally "^1.0.0" + parse-filepath@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" @@ -3577,6 +3984,10 @@ prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" +prepend-http@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" + preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" @@ -3632,6 +4043,14 @@ qs@~6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" +query-string@^5.0.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" + dependencies: + decode-uri-component "^0.2.0" + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" + querystringify@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-1.0.0.tgz#6286242112c5b712fa654e526652bf6a13ff05cb" @@ -3700,6 +4119,18 @@ readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable string_decoder "~1.0.3" util-deprecate "~1.0.1" +readable-stream@^2.3.0: + version "2.3.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + readable-stream@~1.1.9: version "1.1.14" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" @@ -3960,6 +4391,12 @@ resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.2: dependencies: path-parse "^1.0.5" +responselike@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" + dependencies: + lowercase-keys "^1.0.0" + ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" @@ -4012,6 +4449,12 @@ sax@>=0.6.0: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" +seek-bzip@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.5.tgz#cfe917cb3d274bcffac792758af53173eb1fabdc" + dependencies: + commander "~2.8.1" + "semver@2 || 3 || 4 || 5", semver@5.5.0, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1: version "5.5.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" @@ -4117,6 +4560,24 @@ sntp@2.x.x: dependencies: hoek "4.x.x" +sort-keys-length@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/sort-keys-length/-/sort-keys-length-1.0.1.tgz#9cb6f4f4e9e48155a6aa0671edd336ff1479a188" + dependencies: + sort-keys "^1.0.0" + +sort-keys@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + dependencies: + is-plain-obj "^1.0.0" + +sort-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" + dependencies: + is-plain-obj "^1.0.0" + source-map-resolve@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.3.1.tgz#610f6122a445b8dd51535a2a71b783dfc1248761" @@ -4267,6 +4728,10 @@ streamifier@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/streamifier/-/streamifier-0.1.1.tgz#97e98d8fa4d105d62a2691d1dc07e820db8dfc4f" +strict-uri-encode@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" + string-width@^1.0.1, string-width@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -4285,6 +4750,12 @@ string_decoder@~1.0.3: dependencies: safe-buffer "~5.1.0" +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + dependencies: + safe-buffer "~5.1.0" + stringstream@~0.0.4, stringstream@~0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" @@ -4332,6 +4803,12 @@ strip-bom@^2.0.0: dependencies: is-utf8 "^0.2.0" +strip-dirs@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5" + dependencies: + is-natural-number "^4.0.1" + strip-indent@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" @@ -4346,6 +4823,12 @@ strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" +strip-outer@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-1.0.1.tgz#b2fd2abf6604b9d1e6013057195df836b8a9d631" + dependencies: + escape-string-regexp "^1.0.2" + sudo-prompt@8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-8.2.0.tgz#bcd4aaacdb367b77b4bffcce1c658c2b1dd327f3" @@ -4393,6 +4876,18 @@ tar-pack@^3.4.0: tar "^2.2.1" uid-number "^0.0.6" +tar-stream@^1.5.2: + version "1.6.1" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.1.tgz#f84ef1696269d6223ca48f6e1eeede3f7e81f395" + dependencies: + bl "^1.0.0" + buffer-alloc "^1.1.0" + end-of-stream "^1.0.0" + fs-constants "^1.0.0" + readable-stream "^2.3.0" + to-buffer "^1.1.0" + xtend "^4.0.0" + tar@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" @@ -4444,7 +4939,7 @@ through2@^0.6.0, through2@^0.6.1, through2@~0.6.5: readable-stream ">=1.0.33-1 <1.1.0-0" xtend ">=4.0.0 <4.1.0-0" -through@2, "through@>=2.2.7 <3", through@~2.3, through@~2.3.1: +through@2, "through@>=2.2.7 <3", through@^2.3.6, through@~2.3, through@~2.3.1: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -4458,6 +4953,10 @@ time-stamp@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" +timed-out@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" + timers-ext@^0.1.2: version "0.1.5" resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.5.tgz#77147dd4e76b660c2abb8785db96574cbbd12922" @@ -4484,6 +4983,10 @@ to-absolute-glob@^2.0.0: is-absolute "^1.0.0" is-negated-glob "^1.0.0" +to-buffer@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" + to-object-path@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" @@ -4526,6 +5029,12 @@ trim-newlines@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" +trim-repeated@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/trim-repeated/-/trim-repeated-1.0.0.tgz#e3646a2ea4e891312bf7eace6cfb05380bc01c21" + dependencies: + escape-string-regexp "^1.0.2" + tslib@1.9.0, tslib@^1.7.1, tslib@^1.8.0, tslib@^1.8.1: version "1.9.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8" @@ -4641,6 +5150,13 @@ uint64be@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/uint64be/-/uint64be-1.0.1.tgz#1f7154202f2a1b8af353871dda651bf34ce93e95" +unbzip2-stream@^1.0.9: + version "1.2.5" + resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.2.5.tgz#73a033a567bbbde59654b193c44d48a7e4f43c47" + dependencies: + buffer "^3.0.1" + through "^2.3.6" + unc-path-regex@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" @@ -4696,6 +5212,12 @@ urix@^0.1.0, urix@~0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" +url-parse-lax@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" + dependencies: + prepend-http "^2.0.0" + url-parse@^1.1.9: version "1.2.0" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.2.0.tgz#3a19e8aaa6d023ddd27dcc44cb4fc8f7fec23986" @@ -4703,6 +5225,10 @@ url-parse@^1.1.9: querystringify "~1.0.0" requires-port "~1.0.0" +url-to-options@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" + urlgrey@0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/urlgrey/-/urlgrey-0.4.4.tgz#892fe95960805e85519f1cd4389f2cb4cbb7652f" @@ -5061,7 +5587,7 @@ yargs@~3.10.0: decamelize "^1.0.0" window-size "0.1.0" -yauzl@^2.2.1: +yauzl@^2.2.1, yauzl@^2.4.2: version "2.9.1" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.9.1.tgz#a81981ea70a57946133883f029c5821a89359a7f" dependencies: From d23885157198baa1406b7a19a5b328f53de5ead3 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 5 Jun 2018 13:50:57 -0700 Subject: [PATCH 303/433] Capture telemetry for the new debugger (#1845) --- news/3 Code Health/1767.md | 1 + .../configProviders/pythonV2Provider.ts | 33 +++++++++++++++++-- src/client/telemetry/types.ts | 17 ++++++++-- 3 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 news/3 Code Health/1767.md diff --git a/news/3 Code Health/1767.md b/news/3 Code Health/1767.md new file mode 100644 index 000000000000..b8bfe4bd2839 --- /dev/null +++ b/news/3 Code Health/1767.md @@ -0,0 +1 @@ +Capture telemetry for the new debugger. diff --git a/src/client/debugger/configProviders/pythonV2Provider.ts b/src/client/debugger/configProviders/pythonV2Provider.ts index 8644f5daea9a..c7be4d331e1a 100644 --- a/src/client/debugger/configProviders/pythonV2Provider.ts +++ b/src/client/debugger/configProviders/pythonV2Provider.ts @@ -7,6 +7,9 @@ import { inject, injectable } from 'inversify'; import { Uri } from 'vscode'; import { IPlatformService } from '../../common/platform/types'; import { IServiceContainer } from '../../ioc/types'; +import { sendTelemetryEvent } from '../../telemetry'; +import { DEBUGGER } from '../../telemetry/constants'; +import { DebuggerTelemetryV2 } from '../../telemetry/types'; import { AttachRequestArguments, DebugOptions, LaunchRequestArguments } from '../Common/Contracts'; import { BaseConfigurationProvider, PythonAttachDebugConfiguration, PythonLaunchDebugConfiguration } from './baseProvider'; import { IConfigurationProviderUtils } from './types'; @@ -37,7 +40,7 @@ export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvide if (this.serviceContainer.get(IPlatformService).isWindows) { this.debugOption(debugOptions, DebugOptions.FixFilePathCase); } - const isFlask = debugConfiguration.module && debugConfiguration.module.toUpperCase() === 'FLASK'; + const isFlask = this.isDebuggingFlask(debugConfiguration); if ((debugConfiguration.pyramid || isFlask) && debugOptions.indexOf(DebugOptions.Jinja) === -1 && debugConfiguration.jinja !== false) { @@ -47,6 +50,7 @@ export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvide const utils = this.serviceContainer.get(IConfigurationProviderUtils); debugConfiguration.program = (await utils.getPyramidStartupScriptFilePath(workspaceFolder))!; } + this.sendTelemetry('launch', debugConfiguration); } // tslint:disable-next-line:cyclomatic-complexity protected async provideAttachDefaults(workspaceFolder: Uri | undefined, debugConfiguration: PythonAttachDebugConfiguration): Promise { @@ -72,7 +76,7 @@ export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvide // We'll need paths to be fixed only in the case where local and remote hosts are the same // I.e. only if hostName === 'localhost' or '127.0.0.1' or '' - const isLocalHost = !debugConfiguration.host || debugConfiguration.host === 'localhost' || debugConfiguration.host === '127.0.0.1'; + const isLocalHost = this.isLocalHost(debugConfiguration.host); if (this.serviceContainer.get(IPlatformService).isWindows && isLocalHost) { this.debugOption(debugOptions, DebugOptions.FixFilePathCase); } @@ -99,6 +103,7 @@ export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvide remoteRoot: workspaceFolder.fsPath }); } + this.sendTelemetry('attach', debugConfiguration); } private debugOption(debugOptions: DebugOptions[], debugOption: DebugOptions) { if (debugOptions.indexOf(debugOption) >= 0) { @@ -106,4 +111,28 @@ export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvide } debugOptions.push(debugOption); } + private isLocalHost(hostName?: string) { + const LocalHosts = ['localhost', '127.0.0.1', '::1']; + return (hostName && LocalHosts.indexOf(hostName.toLowerCase()) >= 0) ? true : false; + } + private isDebuggingFlask(debugConfiguration: PythonAttachDebugConfiguration>) { + return (debugConfiguration.module && debugConfiguration.module.toUpperCase() === 'FLASK') ? true : false; + } + private sendTelemetry(trigger: 'launch' | 'attach', debugConfiguration: PythonAttachDebugConfiguration>) { + const telemetryProps: DebuggerTelemetryV2 = { + trigger, + console: debugConfiguration.console, + hasEnvVars: typeof debugConfiguration.env === 'object' && Object.keys(debugConfiguration.env).length > 0, + django: !!debugConfiguration.django, + flask: this.isDebuggingFlask(debugConfiguration), + hasArgs: Array.isArray(debugConfiguration.args) && debugConfiguration.args.length > 0, + isLocalhost: this.isLocalHost(debugConfiguration.host), + isModule: typeof debugConfiguration.module === 'string' && debugConfiguration.module.length > 0, + isSudo: !!debugConfiguration.sudo, + jinja: !!debugConfiguration.jinja, + pyramid: !!debugConfiguration.pyramid, + stopOnEntry: !!debugConfiguration.stopOnEntry + }; + sendTelemetryEvent(DEBUGGER, undefined, telemetryProps); + } } diff --git a/src/client/telemetry/types.ts b/src/client/telemetry/types.ts index 44524e796725..2e1753522bf0 100644 --- a/src/client/telemetry/types.ts +++ b/src/client/telemetry/types.ts @@ -39,6 +39,20 @@ export type DebuggerTelemetry = { pyspark?: boolean; hasEnvVars?: boolean; }; +export type DebuggerTelemetryV2 = { + trigger: 'launch' | 'attach'; + console?: 'none' | 'integratedTerminal' | 'externalTerminal'; + hasEnvVars: boolean; + hasArgs: boolean; + django: boolean; + flask: boolean; + jinja: boolean; + isLocalhost: boolean; + isModule: boolean; + isSudo: boolean; + stopOnEntry: boolean; + pyramid: boolean; +}; export type DebuggerPerformanceTelemetry = { duration: number; action: 'stepIn' | 'stepOut' | 'continue' | 'next' | 'launch'; @@ -64,5 +78,4 @@ export type TerminalTelemetry = { pythonVersion?: string; interpreterType?: InterpreterType; }; -export type TelemetryProperties = FormatTelemetry | LintingTelemetry | EditorLoadTelemetry | PythonInterpreterTelemetry | - CodeExecutionTelemetry | TestRunTelemetry | TestDiscoverytTelemetry | FeedbackTelemetry | TerminalTelemetry; +export type TelemetryProperties = FormatTelemetry | LintingTelemetry | EditorLoadTelemetry | PythonInterpreterTelemetry | CodeExecutionTelemetry | TestRunTelemetry | TestDiscoverytTelemetry | FeedbackTelemetry | TerminalTelemetry | DebuggerTelemetryV2; From 4e0e18ea28f76e1a3de79ec741cf4b6bc0adbae7 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 5 Jun 2018 13:51:25 -0700 Subject: [PATCH 304/433] Additional telemetry captured during startup (#1864) * Additional telemetry captured during startup * Add telemetry to capture type of python interpreter used in workspace --- news/3 Code Health/1237.md | 1 + news/3 Code Health/1545.md | 1 + src/client/extension.ts | 32 ++++++++++++++++++++++---------- 3 files changed, 24 insertions(+), 10 deletions(-) create mode 100644 news/3 Code Health/1237.md create mode 100644 news/3 Code Health/1545.md diff --git a/news/3 Code Health/1237.md b/news/3 Code Health/1237.md new file mode 100644 index 000000000000..782e955854c6 --- /dev/null +++ b/news/3 Code Health/1237.md @@ -0,0 +1 @@ +Add telemetry to capture type of python interpreter used in workspace. diff --git a/news/3 Code Health/1545.md b/news/3 Code Health/1545.md new file mode 100644 index 000000000000..58ecc563d484 --- /dev/null +++ b/news/3 Code Health/1545.md @@ -0,0 +1 @@ +Add telemetry to capture availability of Python 3, version of Python used in workspace and the number of workspace folders. diff --git a/src/client/extension.ts b/src/client/extension.ts index eb405f591159..2228cca81495 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -5,14 +5,15 @@ if ((Reflect as any).metadata === undefined) { // tslint:disable-next-line:no-require-imports no-var-requires require('reflect-metadata'); } +import { StopWatch } from './common/stopWatch'; +// Do not move this linne of code (used to measure extension load times). +const stopWatch = new StopWatch(); + import { Container } from 'inversify'; -import { - debug, Disposable, ExtensionContext, - extensions, IndentAction, languages, Memento, - OutputChannel, window -} from 'vscode'; +import { debug, Disposable, ExtensionContext, extensions, IndentAction, languages, Memento, OutputChannel, window } from 'vscode'; import { registerTypes as activationRegisterTypes } from './activation/serviceRegistry'; import { IExtensionActivationService } from './activation/types'; +import { IWorkspaceService } from './common/application/types'; import { PythonSettings } from './common/configSettings'; import { PYTHON, PYTHON_LANGUAGE, STANDARD_OUTPUT_CHANNEL } from './common/constants'; import { FeatureDeprecationManager } from './common/featureDeprecationManager'; @@ -22,7 +23,6 @@ import { registerTypes as installerRegisterTypes } from './common/installer/serv import { registerTypes as platformRegisterTypes } from './common/platform/serviceRegistry'; import { registerTypes as processRegisterTypes } from './common/process/serviceRegistry'; import { registerTypes as commonRegisterTypes } from './common/serviceRegistry'; -import { StopWatch } from './common/stopWatch'; import { ITerminalHelper } from './common/terminal/types'; import { GLOBAL_MEMENTO, IConfigurationService, IDisposableRegistry, IExtensionContext, ILogger, IMemento, IOutputChannel, IPersistentStateFactory, WORKSPACE_MEMENTO } from './common/types'; import { registerTypes as variableRegisterTypes } from './common/variables/serviceRegistry'; @@ -32,7 +32,7 @@ import { registerTypes as debugConfigurationRegisterTypes } from './debugger/con import { IDebugConfigurationProvider } from './debugger/types'; import { registerTypes as formattersRegisterTypes } from './formatters/serviceRegistry'; import { IInterpreterSelector } from './interpreter/configuration/types'; -import { ICondaService, IInterpreterService } from './interpreter/contracts'; +import { ICondaService, IInterpreterService, PythonInterpreter } from './interpreter/contracts'; import { registerTypes as interpretersRegisterTypes } from './interpreter/serviceRegistry'; import { ServiceContainer } from './ioc/container'; import { ServiceManager } from './ioc/serviceManager'; @@ -180,7 +180,6 @@ function registerServices(context: ExtensionContext, serviceManager: ServiceMana } async function sendStartupTelemetry(activatedPromise: Promise, serviceContainer: IServiceContainer) { - const stopWatch = new StopWatch(); const logger = serviceContainer.get(ILogger); try { await activatedPromise; @@ -188,8 +187,21 @@ async function sendStartupTelemetry(activatedPromise: Promise, serviceCont const terminalShellType = terminalHelper.identifyTerminalShell(terminalHelper.getTerminalShellPath()); const duration = stopWatch.elapsedTime; const condaLocator = serviceContainer.get(ICondaService); - const condaVersion = await condaLocator.getCondaVersion().catch(() => undefined); - const props = { condaVersion, terminal: terminalShellType }; + const interpreterService = serviceContainer.get(IInterpreterService); + const [condaVersion, interpreter, interpreters] = await Promise.all([ + condaLocator.getCondaVersion().catch(() => undefined), + interpreterService.getActiveInterpreter().catch(() => undefined), + interpreterService.getInterpreters().catch(() => []) + ]); + const workspaceService = serviceContainer.get(IWorkspaceService); + const workspaceFolderCount = workspaceService.hasWorkspaceFolders ? workspaceService.workspaceFolders!.length : 0; + const pythonVersion = interpreter ? interpreter.version_info.join('.') : undefined; + const interpreterType = interpreter ? interpreter.type : undefined; + const hasPython3 = interpreters + .filter(item => item && Array.isArray(item.version_info) ? item.version_info[0] === 3 : false) + .length > 0; + + const props = { condaVersion, terminal: terminalShellType, pythonVersion, interpreterType, workspaceFolderCount, hasPython3 }; sendTelemetryEvent(EDITOR_LOAD, duration, props); } catch (ex) { logger.logError('sendStartupTelemetry failed.', ex); From 8734e231d4b0658dfca0e4b863d4d6840b4ee4c7 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 5 Jun 2018 13:52:45 -0700 Subject: [PATCH 305/433] Capture telemetry for usage of the setting autocomplete.addBrackets (#1846) * Capture telemetry for usage of the setting autocomplete.addBrackets * Capture telemetry for formatOnType setting --- news/3 Code Health/1766.md | 1 + news/3 Code Health/1770.md | 1 + src/client/common/configSettings.ts | 6 +++++- src/client/telemetry/constants.ts | 2 ++ src/client/telemetry/types.ts | 5 ++++- 5 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 news/3 Code Health/1766.md create mode 100644 news/3 Code Health/1770.md diff --git a/news/3 Code Health/1766.md b/news/3 Code Health/1766.md new file mode 100644 index 000000000000..6af23252b987 --- /dev/null +++ b/news/3 Code Health/1766.md @@ -0,0 +1 @@ +Capture telemetry for the usage of the feature that formats a line as you type (`editor.formatOnType`). diff --git a/news/3 Code Health/1770.md b/news/3 Code Health/1770.md new file mode 100644 index 000000000000..0c7d35e39b22 --- /dev/null +++ b/news/3 Code Health/1770.md @@ -0,0 +1 @@ +Capture telemetry for usage of the setting `python.autocomplete.addBrackets` diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index 033a33096cab..73128e89ba18 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -4,6 +4,8 @@ import * as child_process from 'child_process'; import { EventEmitter } from 'events'; import * as path from 'path'; import { ConfigurationTarget, DiagnosticSeverity, Disposable, Uri, workspace } from 'vscode'; +import { sendTelemetryEvent } from '../telemetry'; +import { COMPLETION_ADD_BRACKETS, FORMAT_ON_TYPE } from '../telemetry/constants'; import { isTestExecution } from './constants'; import { IAutoCompleteSettings, @@ -69,6 +71,9 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { if (!PythonSettings.pythonSettings.has(workspaceFolderKey)) { const settings = new PythonSettings(workspaceFolderUri); PythonSettings.pythonSettings.set(workspaceFolderKey, settings); + const formatOnType = workspace.getConfiguration('editor', resource).get('formatOnType', false); + sendTelemetryEvent(COMPLETION_ADD_BRACKETS, undefined, { enabled: settings.autoComplete.addBrackets }); + sendTelemetryEvent(FORMAT_ON_TYPE, undefined, { enabled: formatOnType }); } // tslint:disable-next-line:no-non-null-assertion return PythonSettings.pythonSettings.get(workspaceFolderKey)!; @@ -101,7 +106,6 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { this.disposables.forEach(disposable => disposable.dispose()); this.disposables = []; } - // tslint:disable-next-line:cyclomatic-complexity max-func-body-length private initializeSettings() { const workspaceRoot = this.workspaceRoot.fsPath; diff --git a/src/client/telemetry/constants.ts b/src/client/telemetry/constants.ts index bd2b87b3bd6f..b3c3dfe4f0c9 100644 --- a/src/client/telemetry/constants.ts +++ b/src/client/telemetry/constants.ts @@ -4,6 +4,7 @@ 'use strict'; export const COMPLETION = 'COMPLETION'; +export const COMPLETION_ADD_BRACKETS = 'COMPLETION.ADD_BRACKETS'; export const DEFINITION = 'DEFINITION'; export const HOVER_DEFINITION = 'HOVER_DEFINITION'; export const REFERENCE = 'REFERENCE'; @@ -11,6 +12,7 @@ export const SIGNATURE = 'SIGNATURE'; export const SYMBOL = 'SYMBOL'; export const FORMAT_SORT_IMPORTS = 'FORMAT.SORT_IMPORTS'; export const FORMAT = 'FORMAT.FORMAT'; +export const FORMAT_ON_TYPE = 'FORMAT.FORMAT_ON_TYPE'; export const EDITOR_LOAD = 'EDITOR.LOAD'; export const LINTING = 'LINTING'; export const GO_TO_OBJECT_DEFINITION = 'GO_TO_OBJECT_DEFINITION'; diff --git a/src/client/telemetry/types.ts b/src/client/telemetry/types.ts index 2e1753522bf0..7865a5ea4c94 100644 --- a/src/client/telemetry/types.ts +++ b/src/client/telemetry/types.ts @@ -72,10 +72,13 @@ export type TestDiscoverytTelemetry = { export type FeedbackTelemetry = { action: 'accepted' | 'dismissed' | 'doNotShowAgain'; }; +export type SettingsTelemetry = { + enabled: boolean; +}; export type TerminalTelemetry = { terminal?: TerminalShellType; triggeredBy?: 'commandpalette'; pythonVersion?: string; interpreterType?: InterpreterType; }; -export type TelemetryProperties = FormatTelemetry | LintingTelemetry | EditorLoadTelemetry | PythonInterpreterTelemetry | CodeExecutionTelemetry | TestRunTelemetry | TestDiscoverytTelemetry | FeedbackTelemetry | TerminalTelemetry | DebuggerTelemetryV2; +export type TelemetryProperties = FormatTelemetry | LintingTelemetry | EditorLoadTelemetry | PythonInterpreterTelemetry | CodeExecutionTelemetry | TestRunTelemetry | TestDiscoverytTelemetry | FeedbackTelemetry | TerminalTelemetry | DebuggerTelemetryV2 | SettingsTelemetry; From 1bb736593e152ef799624840133d5bd42a2dc894 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 5 Jun 2018 13:53:42 -0700 Subject: [PATCH 306/433] Use file system API for path comparisons when performing code navigation (#1812) Fixes #1811 --- news/2 Fixes/1811.md | 2 + src/client/activation/classic.ts | 4 +- src/client/providers/symbolProvider.ts | 40 ++++++----- src/test/providers/symbolProvider.test.ts | 84 ++++++++++++++++++----- 4 files changed, 94 insertions(+), 36 deletions(-) create mode 100644 news/2 Fixes/1811.md diff --git a/news/2 Fixes/1811.md b/news/2 Fixes/1811.md new file mode 100644 index 000000000000..c33287129520 --- /dev/null +++ b/news/2 Fixes/1811.md @@ -0,0 +1,2 @@ +Use file system API to perform file path comparisons when performing code navigation. +(thanks to [bstaint](https://github.com/bstaint) for the initial patch) diff --git a/src/client/activation/classic.ts b/src/client/activation/classic.ts index a2da07d38cf9..17ca5c929047 100644 --- a/src/client/activation/classic.ts +++ b/src/client/activation/classic.ts @@ -6,7 +6,7 @@ import { DocumentFilter, languages } from 'vscode'; import { PYTHON } from '../common/constants'; import { IConfigurationService, IExtensionContext, ILogger } from '../common/types'; import { IShebangCodeLensProvider } from '../interpreter/contracts'; -import { IServiceManager } from '../ioc/types'; +import { IServiceContainer, IServiceManager } from '../ioc/types'; import { JediFactory } from '../languageServices/jediProxyFactory'; import { PythonCompletionItemProvider } from '../providers/completionProvider'; import { PythonDefinitionProvider } from '../providers/definitionProvider'; @@ -46,7 +46,7 @@ export class ClassicExtensionActivator implements IExtensionActivator { context.subscriptions.push(languages.registerCompletionItemProvider(this.documentSelector, new PythonCompletionItemProvider(jediFactory, this.serviceManager), '.')); context.subscriptions.push(languages.registerCodeLensProvider(this.documentSelector, this.serviceManager.get(IShebangCodeLensProvider))); - const symbolProvider = new PythonSymbolProvider(jediFactory); + const symbolProvider = new PythonSymbolProvider(this.serviceManager.get(IServiceContainer), jediFactory); context.subscriptions.push(languages.registerDocumentSymbolProvider(this.documentSelector, symbolProvider)); const pythonSettings = this.serviceManager.get(IConfigurationService).getSettings(); diff --git a/src/client/providers/symbolProvider.ts b/src/client/providers/symbolProvider.ts index a11dca6e427d..5f279e0f787a 100644 --- a/src/client/providers/symbolProvider.ts +++ b/src/client/providers/symbolProvider.ts @@ -2,6 +2,8 @@ import { CancellationToken, DocumentSymbolProvider, Location, Range, SymbolInformation, TextDocument, Uri } from 'vscode'; import { createDeferred, Deferred } from '../common/helpers'; +import { IFileSystem } from '../common/platform/types'; +import { IServiceContainer } from '../ioc/types'; import { JediFactory } from '../languageServices/jediProxyFactory'; import { captureTelemetry } from '../telemetry'; import { SYMBOL } from '../telemetry/constants'; @@ -9,23 +11,10 @@ import * as proxy from './jediProxy'; export class PythonSymbolProvider implements DocumentSymbolProvider { private debounceRequest: Map }>; - public constructor(private jediFactory: JediFactory, private readonly debounceTimeoutMs = 500) { + private readonly fs: IFileSystem; + public constructor(serviceContainer: IServiceContainer, private jediFactory: JediFactory, private readonly debounceTimeoutMs = 500) { this.debounceRequest = new Map }>(); - } - private static parseData(document: TextDocument, data?: proxy.ISymbolResult): SymbolInformation[] { - if (data) { - const symbols = data.definitions.filter(sym => sym.fileName === document.fileName); - return symbols.map(sym => { - const symbol = sym.kind; - const range = new Range( - sym.range.startLine, sym.range.startColumn, - sym.range.endLine, sym.range.endColumn); - const uri = Uri.file(sym.fileName); - const location = new Location(uri, range); - return new SymbolInformation(sym.text, symbol, sym.container, location); - }); - } - return []; + this.fs = serviceContainer.get(IFileSystem); } @captureTelemetry(SYMBOL) public provideDocumentSymbols(document: TextDocument, token: CancellationToken): Thenable { @@ -55,7 +44,7 @@ export class PythonSymbolProvider implements DocumentSymbolProvider { } this.jediFactory.getJediProxyHandler(document.uri).sendCommand(cmd, token) - .then(data => PythonSymbolProvider.parseData(document, data)) + .then(data => this.parseData(document, data)) .then(items => deferred.resolve(items)) .catch(ex => deferred.reject(ex)); @@ -89,6 +78,21 @@ export class PythonSymbolProvider implements DocumentSymbolProvider { } return this.jediFactory.getJediProxyHandler(document.uri).sendCommandNonCancellableCommand(cmd, token) - .then(data => PythonSymbolProvider.parseData(document, data)); + .then(data => this.parseData(document, data)); + } + private parseData(document: TextDocument, data?: proxy.ISymbolResult): SymbolInformation[] { + if (data) { + const symbols = data.definitions.filter(sym => this.fs.arePathsSame(sym.fileName, document.fileName)); + return symbols.map(sym => { + const symbol = sym.kind; + const range = new Range( + sym.range.startLine, sym.range.startColumn, + sym.range.endLine, sym.range.endColumn); + const uri = Uri.file(sym.fileName); + const location = new Location(uri, range); + return new SymbolInformation(sym.text, symbol, sym.container, location); + }); + } + return []; } } diff --git a/src/test/providers/symbolProvider.test.ts b/src/test/providers/symbolProvider.test.ts index a518cd7ee508..8ff4d46bccd6 100644 --- a/src/test/providers/symbolProvider.test.ts +++ b/src/test/providers/symbolProvider.test.ts @@ -8,26 +8,39 @@ import { expect, use } from 'chai'; import * as TypeMoq from 'typemoq'; import { CancellationToken, CancellationTokenSource, CompletionItemKind, DocumentSymbolProvider, SymbolKind, TextDocument, Uri } from 'vscode'; +import { IFileSystem } from '../../client/common/platform/types'; +import { IServiceContainer } from '../../client/ioc/types'; import { JediFactory } from '../../client/languageServices/jediProxyFactory'; import { IDefinition, ISymbolResult, JediProxyHandler } from '../../client/providers/jediProxy'; import { PythonSymbolProvider } from '../../client/providers/symbolProvider'; + const assertArrays = require('chai-arrays'); use(assertArrays); suite('Symbol Provider', () => { - let symbolProvider: DocumentSymbolProvider; + let serviceContainer: TypeMoq.IMock; let jediHandler: TypeMoq.IMock>; let jediFactory: TypeMoq.IMock; + let fileSystem: TypeMoq.IMock; + let provider: DocumentSymbolProvider; + let uri: Uri; + let doc: TypeMoq.IMock; setup(() => { + serviceContainer = TypeMoq.Mock.ofType(); jediFactory = TypeMoq.Mock.ofType(JediFactory); jediHandler = TypeMoq.Mock.ofType>(); + fileSystem = TypeMoq.Mock.ofType(); + doc = TypeMoq.Mock.ofType(); jediFactory.setup(j => j.getJediProxyHandler(TypeMoq.It.isAny())) .returns(() => jediHandler.object); + + serviceContainer.setup(c => c.get(IFileSystem)).returns(() => fileSystem.object); }); async function testDocumentation(requestId: number, fileName: string, expectedSize: number, token?: CancellationToken, isUntitled = false) { - const doc = TypeMoq.Mock.ofType(); + fileSystem.setup(fs => fs.arePathsSame(TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns(() => true); token = token ? token : new CancellationTokenSource().token; const symbolResult = TypeMoq.Mock.ofType(); @@ -39,9 +52,10 @@ suite('Symbol Provider', () => { } ]; + uri = Uri.file(fileName); + doc.setup(d => d.uri).returns(() => uri); doc.setup(d => d.fileName).returns(() => fileName); doc.setup(d => d.isUntitled).returns(() => isUntitled); - doc.setup(d => d.uri).returns(() => Uri.file(fileName)); doc.setup(d => d.getText(TypeMoq.It.isAny())).returns(() => ''); symbolResult.setup(c => c.requestId).returns(() => requestId); symbolResult.setup(c => c.definitions).returns(() => definitions); @@ -49,41 +63,41 @@ suite('Symbol Provider', () => { jediHandler.setup(j => j.sendCommand(TypeMoq.It.isAny(), TypeMoq.It.isAny())) .returns(() => Promise.resolve(symbolResult.object)); - const items = await symbolProvider.provideDocumentSymbols(doc.object, token); + const items = await provider.provideDocumentSymbols(doc.object, token); expect(items).to.be.array(); expect(items).to.be.ofSize(expectedSize); } test('Ensure symbols are returned', async () => { - symbolProvider = new PythonSymbolProvider(jediFactory.object, 0); + provider = new PythonSymbolProvider(serviceContainer.object, jediFactory.object, 0); await testDocumentation(1, __filename, 1); }); test('Ensure symbols are returned (for untitled documents)', async () => { - symbolProvider = new PythonSymbolProvider(jediFactory.object, 0); + provider = new PythonSymbolProvider(serviceContainer.object, jediFactory.object, 0); await testDocumentation(1, __filename, 1, undefined, true); }); test('Ensure symbols are returned with a debounce of 100ms', async () => { - symbolProvider = new PythonSymbolProvider(jediFactory.object, 0); + provider = new PythonSymbolProvider(serviceContainer.object, jediFactory.object, 0); await testDocumentation(1, __filename, 1); }); test('Ensure symbols are returned with a debounce of 100ms (for untitled documents)', async () => { - symbolProvider = new PythonSymbolProvider(jediFactory.object, 0); + provider = new PythonSymbolProvider(serviceContainer.object, jediFactory.object, 0); await testDocumentation(1, __filename, 1, undefined, true); }); test('Ensure symbols are not returned when cancelled', async () => { - symbolProvider = new PythonSymbolProvider(jediFactory.object, 0); + provider = new PythonSymbolProvider(serviceContainer.object, jediFactory.object, 0); const tokenSource = new CancellationTokenSource(); tokenSource.cancel(); await testDocumentation(1, __filename, 0, tokenSource.token); }); test('Ensure symbols are not returned when cancelled (for untitled documents)', async () => { - symbolProvider = new PythonSymbolProvider(jediFactory.object, 0); + provider = new PythonSymbolProvider(serviceContainer.object, jediFactory.object, 0); const tokenSource = new CancellationTokenSource(); tokenSource.cancel(); await testDocumentation(1, __filename, 0, tokenSource.token, true); }); test('Ensure symbols are returned only for the last request', async () => { - symbolProvider = new PythonSymbolProvider(jediFactory.object, 100); + provider = new PythonSymbolProvider(serviceContainer.object, jediFactory.object, 100); await Promise.all([ testDocumentation(1, __filename, 0), testDocumentation(2, __filename, 0), @@ -91,7 +105,7 @@ suite('Symbol Provider', () => { ]); }); test('Ensure symbols are returned for all the requests when the doc is untitled', async () => { - symbolProvider = new PythonSymbolProvider(jediFactory.object, 100); + provider = new PythonSymbolProvider(serviceContainer.object, jediFactory.object, 100); await Promise.all([ testDocumentation(1, __filename, 1, undefined, true), testDocumentation(2, __filename, 1, undefined, true), @@ -99,31 +113,69 @@ suite('Symbol Provider', () => { ]); }); test('Ensure symbols are returned for multiple documents', async () => { - symbolProvider = new PythonSymbolProvider(jediFactory.object, 0); + provider = new PythonSymbolProvider(serviceContainer.object, jediFactory.object, 0); await Promise.all([ testDocumentation(1, 'file1', 1), testDocumentation(2, 'file2', 1) ]); }); test('Ensure symbols are returned for multiple untitled documents ', async () => { - symbolProvider = new PythonSymbolProvider(jediFactory.object, 0); + provider = new PythonSymbolProvider(serviceContainer.object, jediFactory.object, 0); await Promise.all([ testDocumentation(1, 'file1', 1, undefined, true), testDocumentation(2, 'file2', 1, undefined, true) ]); }); test('Ensure symbols are returned for multiple documents with a debounce of 100ms', async () => { - symbolProvider = new PythonSymbolProvider(jediFactory.object, 100); + provider = new PythonSymbolProvider(serviceContainer.object, jediFactory.object, 100); await Promise.all([ testDocumentation(1, 'file1', 1), testDocumentation(2, 'file2', 1) ]); }); test('Ensure symbols are returned for multiple untitled documents with a debounce of 100ms', async () => { - symbolProvider = new PythonSymbolProvider(jediFactory.object, 100); + provider = new PythonSymbolProvider(serviceContainer.object, jediFactory.object, 100); await Promise.all([ testDocumentation(1, 'file1', 1, undefined, true), testDocumentation(2, 'file2', 1, undefined, true) ]); }); + test('Ensure IFileSystem.arePathsSame is used', async () => { + doc.setup(d => d.getText()) + .returns(() => '') + .verifiable(TypeMoq.Times.once()); + doc.setup(d => d.isDirty) + .returns(() => true) + .verifiable(TypeMoq.Times.once()); + doc.setup(d => d.fileName) + .returns(() => __filename); + + const symbols = TypeMoq.Mock.ofType(); + symbols.setup((s: any) => s.then).returns(() => undefined); + const definitions: IDefinition[] = []; + for (let counter = 0; counter < 3; counter += 1) { + const def = TypeMoq.Mock.ofType(); + def.setup(d => d.fileName).returns(() => counter.toString()); + definitions.push(def.object); + + fileSystem.setup(fs => fs.arePathsSame(TypeMoq.It.isValue(counter.toString()), TypeMoq.It.isValue(__filename))) + .returns(() => false) + .verifiable(TypeMoq.Times.exactly(1)); + } + symbols.setup(s => s.definitions) + .returns(() => definitions) + .verifiable(TypeMoq.Times.atLeastOnce()); + + jediHandler.setup(j => j.sendCommand(TypeMoq.It.isAny(), TypeMoq.It.isAny())) + .returns(() => Promise.resolve(symbols.object)) + .verifiable(TypeMoq.Times.once()); + + provider = new PythonSymbolProvider(serviceContainer.object, jediFactory.object, 0); + await provider.provideDocumentSymbols(doc.object, new CancellationTokenSource().token); + + doc.verifyAll(); + symbols.verifyAll(); + fileSystem.verifyAll(); + jediHandler.verifyAll(); + }); }); From 555dd3e612092398e6a53c65918f2f0fb3096fa5 Mon Sep 17 00:00:00 2001 From: Peter Law Date: Tue, 5 Jun 2018 22:04:43 +0100 Subject: [PATCH 307/433] Ensure navigation to definitons follows imports and is transparent to decoration (#1712) * Add baseline function definition navigation tests This provides a starting point for more interesting tests. * Add tests for current cases Specifically this covers some basic cases as well as the reproductions of #1638 and #1033 plus the variant of #1033 which always worked. * Make navigation to definitions follow imports This fixes #1638 and simplifies the code (to what I believe that @davidhalter had in mind in https://github.com/Microsoft/vscode-python/issues/1033#issuecomment-378733832). This change means that all of the test cases recently added to 'navigation.tests.ts' now pass, meaning that navigtion to the definition of functions works through imports and goes to the original function, even when that function is decorated. * Add news entry for PR * Improve framing of this --- news/2 Fixes/1638.md | 1 + pythonFiles/completion.py | 16 +-- src/test/definitions/navigation.test.ts | 126 ++++++++++++++++++ .../definition/navigation/__init__.py | 0 .../definition/navigation/definitions.py | 31 +++++ .../definition/navigation/usages.py | 16 +++ 6 files changed, 175 insertions(+), 15 deletions(-) create mode 100644 news/2 Fixes/1638.md create mode 100644 src/test/definitions/navigation.test.ts create mode 100644 src/test/pythonFiles/definition/navigation/__init__.py create mode 100644 src/test/pythonFiles/definition/navigation/definitions.py create mode 100644 src/test/pythonFiles/definition/navigation/usages.py diff --git a/news/2 Fixes/1638.md b/news/2 Fixes/1638.md new file mode 100644 index 000000000000..9a493c62233f --- /dev/null +++ b/news/2 Fixes/1638.md @@ -0,0 +1 @@ +Ensure navigation to definitons follows imports and is transparent to decoration ([#1638](https://github.com/Microsoft/vscode-python/issues/1638); thanks [Peter Law](https://github.com/PeterJCLaw)) diff --git a/pythonFiles/completion.py b/pythonFiles/completion.py index c130673c6b06..5ab933715689 100644 --- a/pythonFiles/completion.py +++ b/pythonFiles/completion.py @@ -570,21 +570,7 @@ def _process_request(self, request): sys_path=sys.path, environment=self.environment) if lookup == 'definitions': - defs = [] - try: - defs = self._get_definitionsx(script.goto_definitions(follow_imports=False), request['id']) - except: - pass - try: - if len(defs) == 0: - defs = self._get_definitionsx(script.goto_definitions(), request['id']) - except: - pass - try: - if len(defs) == 0: - defs = self._get_definitionsx(script.goto_assignments(), request['id']) - except: - pass + defs = self._get_definitionsx(script.goto_assignments(follow_imports=True), request['id']) return json.dumps({'id': request['id'], 'results': defs}) if lookup == 'tooltip': if jediPreview: diff --git a/src/test/definitions/navigation.test.ts b/src/test/definitions/navigation.test.ts new file mode 100644 index 000000000000..24d4a716d39c --- /dev/null +++ b/src/test/definitions/navigation.test.ts @@ -0,0 +1,126 @@ +// Licensed under the MIT License. + +'use strict'; +import * as assert from 'assert'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { closeActiveWindows, initialize, initializeTest } from '../initialize'; + +const decoratorsPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'definition', 'navigation'); +const fileDefinitions = path.join(decoratorsPath, 'definitions.py'); +const fileUsages = path.join(decoratorsPath, 'usages.py'); + +// tslint:disable-next-line:max-func-body-length +suite('Definition Navigation', () => { + suiteSetup(initialize); + setup(initializeTest); + suiteTeardown(closeActiveWindows); + teardown(closeActiveWindows); + + const assertFile = (expectedLocation: string, location: vscode.Uri) => { + const relLocation = vscode.workspace.asRelativePath(location); + const expectedRelLocation = vscode.workspace.asRelativePath(expectedLocation); + assert.equal(expectedRelLocation, relLocation, 'Position is in wrong file'); + }; + + const formatPosition = (position: vscode.Position) => { + return `${position.line},${position.character}`; + }; + + const assertRange = (expectedRange: vscode.Range, range: vscode.Range) => { + assert.equal(formatPosition(expectedRange.start), formatPosition(range.start), 'Start position is incorrect'); + assert.equal(formatPosition(expectedRange.end), formatPosition(range.end), 'End position is incorrect'); + }; + + const buildTest = (startFile: string, startPosition: vscode.Position, expectedFile: string, expectedRange: vscode.Range) => { + return async () => { + const textDocument = await vscode.workspace.openTextDocument(startFile); + await vscode.window.showTextDocument(textDocument); + assert(vscode.window.activeTextEditor, 'No active editor'); + + const locations = await vscode.commands.executeCommand('vscode.executeDefinitionProvider', textDocument.uri, startPosition); + assert.equal(1, locations!.length, 'Wrong number of results'); + + const def = locations![0]; + assertFile(expectedFile, def.uri); + assertRange(expectedRange, def.range!); + }; + }; + + test('From own definition', buildTest( + fileDefinitions, + new vscode.Position(2, 6), + fileDefinitions, + new vscode.Range(2, 0, 11, 17) + )); + + test('Nested function', buildTest( + fileDefinitions, + new vscode.Position(11, 16), + fileDefinitions, + new vscode.Range(6, 4, 10, 16) + )); + + test('Decorator usage', buildTest( + fileDefinitions, + new vscode.Position(13, 1), + fileDefinitions, + new vscode.Range(2, 0, 11, 17) + )); + + test('Function decorated by stdlib', buildTest( + fileDefinitions, + new vscode.Position(29, 6), + fileDefinitions, + new vscode.Range(21, 0, 27, 17) + )); + + test('Function decorated by local decorator', buildTest( + fileDefinitions, + new vscode.Position(30, 6), + fileDefinitions, + new vscode.Range(14, 0, 18, 7) + )); + + test('Module imported decorator usage', buildTest( + fileUsages, + new vscode.Position(3, 15), + fileDefinitions, + new vscode.Range(2, 0, 11, 17) + )); + + test('Module imported function decorated by stdlib', buildTest( + fileUsages, + new vscode.Position(11, 19), + fileDefinitions, + new vscode.Range(21, 0, 27, 17) + )); + + test('Module imported function decorated by local decorator', buildTest( + fileUsages, + new vscode.Position(12, 19), + fileDefinitions, + new vscode.Range(14, 0, 18, 7) + )); + + test('Specifically imported decorator usage', buildTest( + fileUsages, + new vscode.Position(7, 1), + fileDefinitions, + new vscode.Range(2, 0, 11, 17) + )); + + test('Specifically imported function decorated by stdlib', buildTest( + fileUsages, + new vscode.Position(14, 6), + fileDefinitions, + new vscode.Range(21, 0, 27, 17) + )); + + test('Specifically imported function decorated by local decorator', buildTest( + fileUsages, + new vscode.Position(15, 6), + fileDefinitions, + new vscode.Range(14, 0, 18, 7) + )); +}); diff --git a/src/test/pythonFiles/definition/navigation/__init__.py b/src/test/pythonFiles/definition/navigation/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/test/pythonFiles/definition/navigation/definitions.py b/src/test/pythonFiles/definition/navigation/definitions.py new file mode 100644 index 000000000000..a8379a49f960 --- /dev/null +++ b/src/test/pythonFiles/definition/navigation/definitions.py @@ -0,0 +1,31 @@ +from contextlib import contextmanager + +def my_decorator(fn): + """ + This is my decorator. + """ + def wrapper(*args, **kwargs): + """ + This is the wrapper. + """ + return 42 + return wrapper + +@my_decorator +def thing(arg): + """ + Thing which is decorated. + """ + pass + +@contextmanager +def my_context_manager(): + """ + This is my context manager. + """ + print("before") + yield + print("after") + +with my_context_manager(): + thing(19) diff --git a/src/test/pythonFiles/definition/navigation/usages.py b/src/test/pythonFiles/definition/navigation/usages.py new file mode 100644 index 000000000000..deb6d78edc15 --- /dev/null +++ b/src/test/pythonFiles/definition/navigation/usages.py @@ -0,0 +1,16 @@ +import definitions +from .definitions import my_context_manager, my_decorator, thing + +@definitions.my_decorator +def one(): + pass + +@my_decorator +def two(): + pass + +with definitions.my_context_manager(): + definitions.thing(19) + +with my_context_manager(): + thing(19) From ad9f9190e6b91f74564f435b9a1e7ddfca107b70 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 5 Jun 2018 14:09:06 -0700 Subject: [PATCH 308/433] Provide examples of potential enviroments to test --- .github/test_plan.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/test_plan.md b/.github/test_plan.md index 5bea2fcd552b..a6473bdab852 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -2,11 +2,12 @@ ## Environment -- OS: XXX +- OS: XXX (Windows, macOS, latest Ubuntu LTS) + - Shell: XXX (Command Prompt, PowerShell, bash, fish) - Python - - Distribution: XXX - - Version: XXX -- VS Code: XXX + - Distribution: XXX (CPython, miniconda) + - Version: XXX (2.7, latest 3.x) +- VS Code: XXX (Insiders) ## Tests From c30701a6d6d45ef28e72fce403662f63cfc3341b Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 5 Jun 2018 14:18:36 -0700 Subject: [PATCH 309/433] Touch up some info --- .github/test_plan.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/test_plan.md b/.github/test_plan.md index a6473bdab852..41b8c7fc97bd 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -37,7 +37,8 @@ #### Virtual environments **ALWAYS**: -- Use the latest version of Anconda +- Use the latest version of Anaconda +- Realize that `conda` is slow - Create an environment with a space in their path somewhere - Make sure that you do not have `python.pythonPath` specified in your `settings.json` when testing automatic detection - Do note that the `Select Interpreter` drop-down window scrolls @@ -47,7 +48,7 @@ - [ ] [`Create Terminal`](https://code.visualstudio.com/docs/python/environments#_activating-an-environment-in-the-terminal) works - [ ] Steals focus - [ ] `"python.terminal.activateEnvironment": false` deactivates automatically running the activation script in the terminal -- [ ] Detect multiple virtual environments in a directory specified by `"python.venvPath"` +- [ ] Detect multiple virtual environments contained in the directory specified in `"python.venvPath"` - [ ] Detected all [conda environments created with an interpreter](https://code.visualstudio.com/docs/python/environments#_conda-environments) - [ ] Appropriate suffix label specified in status bar (e.g. `(condaenv)`) - [ ] Prompted to install Pylint From b4cc4553dcabfef9a874b2d5c3a03272ccf95490 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 5 Jun 2018 15:47:12 -0700 Subject: [PATCH 310/433] Move from yarn to npm (#1771) --- .appveyor.yml | 19 +- .travis.yml | 47 +- CONTRIBUTING.md | 4 +- news/3 Code Health/1402.md | 1 + package-lock.json | 9519 ++++++++++++++++++++++++++++++++++++ package.json | 2 +- yarn.lock | 5605 --------------------- 7 files changed, 9556 insertions(+), 5641 deletions(-) create mode 100644 news/3 Code Health/1402.md create mode 100644 package-lock.json delete mode 100644 yarn.lock diff --git a/.appveyor.yml b/.appveyor.yml index 6eae0dad42b7..10e71d558beb 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -72,8 +72,7 @@ init: install: - ps: Install-Product node $env:nodejs_version - - npm i -g yarn - - yarn --frozen-lockfile + - npm ci - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - python -m pip install -U pip - pip install -t ./pythonFiles/experimental/ptvsd git+https://github.com/Microsoft/ptvsd/ @@ -93,17 +92,17 @@ build: off # - "xcopy /S /I c:\\projects\\PTVS\\BuildOutput\\VsCode\\raw analysis" test_script: - - yarn run clean - - yarn run vscode:prepublish + - npm run clean + - npm run vscode:prepublish - if [%DEBUGGER_TEST%]==[true] ( - yarn run testDebugger --silent) - - yarn run clean:ptvsd + npm run testDebugger --silent) + - npm run clean:ptvsd - pip install -t ./pythonFiles/experimental/ptvsd ptvsd --pre --no-cache-dir - if [%DEBUGGER_TEST_RELEASE%]==[true] ( - yarn run testDebugger --silent) + npm run testDebugger --silent) - if [%SINGLE_WORKSPACE_TEST%]==[true] ( - yarn run testSingleWorkspace --silent) + npm run testSingleWorkspace --silent) - if [%MULTIROOT_WORKSPACE_TEST%]==[true] ( - yarn run testMultiWorkspace --silent) + npm run testMultiWorkspace --silent) # - if [%ANALYSIS_TEST%]==[true] ( - # yarn run testAnalysisEngine --silent) + # npm run testAnalysisEngine --silent) diff --git a/.travis.yml b/.travis.yml index ca1394f3ea83..35b577f75773 100644 --- a/.travis.yml +++ b/.travis.yml @@ -65,51 +65,52 @@ before_install: | source ./.nvm/nvm.sh nvm install 8.9.1 nvm use 8.9.1 - yarn global add vsce - yarn global add azure-cli + npm install npm@latest -g + npm install -g vsce + npm install -g azure-cli export TRAVIS_PYTHON_PATH=`which python` install: - python -m pip install --upgrade -r requirements.txt - python -m pip install -t ./pythonFiles/experimental/ptvsd git+https://github.com/Microsoft/ptvsd/ - - yarn --frozen-lockfile + - npm ci script: - if [ $DEBUGGER_TEST == "true" ]; then - yarn run clean; - yarn run vscode:prepublish; - yarn run cover:enable; - yarn run testDebugger --silent; + npm run clean; + npm run vscode:prepublish; + npm run cover:enable; + npm run testDebugger --silent; fi - - yarn run debugger-coverage + - npm run debugger-coverage - if [ $TRAVIS_UPLOAD_COVERAGE == "true" ]; then bash <(curl -s https://codecov.io/bash); fi - - yarn run clean:ptvsd + - npm run clean:ptvsd - pip install -t ./pythonFiles/experimental/ptvsd ptvsd --pre --no-cache-dir; - if [ $DEBUGGER_TEST_RELEASE == "true" ]; then - yarn run clean; - yarn run vscode:prepublish; - yarn run cover:enable; - yarn run testDebugger --silent; + npm run clean; + npm run vscode:prepublish; + npm run cover:enable; + npm run testDebugger --silent; fi - - yarn run debugger-coverage + - npm run debugger-coverage - if [ $TRAVIS_UPLOAD_COVERAGE == "true" ]; then bash <(curl -s https://codecov.io/bash); fi - if [ $SINGLE_WORKSPACE_TEST == "true" ]; then - yarn run clean; - yarn run vscode:prepublish; - yarn run cover:enable; - yarn run testSingleWorkspace --silent; + npm run clean; + npm run vscode:prepublish; + npm run cover:enable; + npm run testSingleWorkspace --silent; fi - if [ $TRAVIS_UPLOAD_COVERAGE == "true" ]; then bash <(curl -s https://codecov.io/bash); fi - if [ $MULTIROOT_WORKSPACE_TEST == "true" ]; then - yarn run clean; - yarn run vscode:prepublish; - yarn run cover:enable; - yarn run testMultiWorkspace --silent; + npm run clean; + npm run vscode:prepublish; + npm run cover:enable; + npm run testMultiWorkspace --silent; fi - if [ $TRAVIS_UPLOAD_COVERAGE == "true" ]; then bash <(curl -s https://codecov.io/bash); @@ -124,7 +125,7 @@ script: python3 news/announce.py --dry_run; fi - if [[ $AZURE_STORAGE_ACCOUNT && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_PULL_REQUEST" == "false" ]]; then - yarn run clean; + npm run clean; vsce package; azure storage blob upload python*.vsix $AZURE_STORAGE_CONTAINER ms-python-insiders.vsix --account-name $AZURE_STORAGE_ACCOUNT --account-key $AZURE_STORAGE_ACCESS_KEY --quiet; fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e85d0d1e1529..cded8ef3f303 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,7 +9,7 @@ ### Prerequisites -1. Node.js (>= 8.9.1, < 9.0.0), and [Yarn](https://yarnpkg.com/lang/en/docs/install/) +1. Node.js (>= 8.9.1, < 9.0.0) 2. Python 2.7 or later (required only for testing the extension and running unit tests) 3. Windows, macOS, or Linux 4. Visual Studio Code @@ -22,7 +22,7 @@ ```shell git clone https://github.com/microsoft/vscode-python cd vscode-python -yarn install --lock-file +npm install ``` You may see warnings that ```The engine "vscode" appears to be invalid.```, you can ignore these. diff --git a/news/3 Code Health/1402.md b/news/3 Code Health/1402.md new file mode 100644 index 000000000000..6e174acb433d --- /dev/null +++ b/news/3 Code Health/1402.md @@ -0,0 +1 @@ +Move from yarn to npm. diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000000..698b873fd15b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,9519 @@ +{ + "name": "python", + "version": "2018.6.0-alpha", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@gulp-sourcemaps/identity-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-1.0.1.tgz", + "integrity": "sha1-z6I7xYQPkQTOMqZedNt+epdLvuE=", + "dev": true, + "requires": { + "acorn": "^5.0.3", + "css": "^2.2.1", + "normalize-path": "^2.1.1", + "source-map": "^0.5.6", + "through2": "^2.0.3" + } + }, + "@gulp-sourcemaps/map-sources": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz", + "integrity": "sha1-iQrnxdjId/bThIYCFazp1+yUW9o=", + "dev": true, + "requires": { + "normalize-path": "^2.0.1", + "through2": "^2.0.3" + } + }, + "@sindresorhus/is": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", + "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==", + "dev": true + }, + "@sinonjs/formatio": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz", + "integrity": "sha512-ls6CAMA6/5gG+O/IdsBcblvnd8qcO/l1TYoNeAzp3wcISOxlPXQEus0mLcdwazEkWjaBdaJ3TaxmNgCLWwvWzg==", + "dev": true, + "requires": { + "samsam": "1.3.0" + } + }, + "@types/caseless": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.1.tgz", + "integrity": "sha512-FhlMa34NHp9K5MY1Uz8yb+ZvuX0pnvn3jScRSNAb75KHGB8d3rEU6hqMs3Z2vjuytcMfRg6c5CHMc3wtYyD2/A==", + "dev": true + }, + "@types/chai": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.3.tgz", + "integrity": "sha512-f5dXGzOJycyzSMdaXVhiBhauL4dYydXwVpavfQ1mVCaGjR56a9QfklXObUxlIY9bGTmCPHEEZ04I16BZ/8w5ww==", + "dev": true + }, + "@types/chai-arrays": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/chai-arrays/-/chai-arrays-1.0.2.tgz", + "integrity": "sha512-/kgYvj5Pwiv/bOlJ6c5GlRF/W6lUGSLrpQGl/7Gg6w7tvBYcf0iF91+wwyuwDYGO2zM0wNpcoPixZVif8I/r6g==", + "dev": true, + "requires": { + "@types/chai": "*" + } + }, + "@types/chai-as-promised": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.0.tgz", + "integrity": "sha512-MFiW54UOSt+f2bRw8J7LgQeIvE/9b4oGvwU7XW30S9QGAiHGnU/fmiOprsyMkdmH2rl8xSPc0/yrQw8juXU6bQ==", + "dev": true, + "requires": { + "@types/chai": "*" + } + }, + "@types/commander": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/@types/commander/-/commander-2.12.2.tgz", + "integrity": "sha512-0QEFiR8ljcHp9bAbWxecjVRuAMr16ivPiGOw6KFQBVrVd0RQIcM3xKdRisH2EDWgVWujiYtHwhSkSUoAAGzH7Q==", + "dev": true, + "requires": { + "commander": "*" + } + }, + "@types/decompress": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/decompress/-/decompress-4.2.2.tgz", + "integrity": "sha512-2jlSsNAVhrWJtgOV3V85MJ09yRoeUTUWQeeusNYAcJVkUmoVRVElvmkWN0TK+Lgdlyd9pIRyja/DTBcyqD8xyA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/del": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/del/-/del-3.0.1.tgz", + "integrity": "sha512-y6qRq6raBuu965clKgx6FHuiPu3oHdtmzMPXi8Uahsjdq1L6DL5fS/aY5/s71YwM7k6K1QIWvem5vNwlnNGIkQ==", + "dev": true, + "requires": { + "@types/glob": "*" + } + }, + "@types/dotenv": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/dotenv/-/dotenv-4.0.3.tgz", + "integrity": "sha512-mmhpINC/HcLGQK5ikFJlLXINVvcxhlrV+ZOUJSN7/ottYl+8X4oSXzS9lBtDkmWAl96EGyGyLrNvk9zqdSH8Fw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/download": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/@types/download/-/download-6.2.2.tgz", + "integrity": "sha512-gwRnrp1yFweJhPGBR01nfesxYcml8SayxHEwA6x+1T+Lqez5iMdCRJgt/I9HqpjMi5Mmtb/7MswY6FN4bMypNg==", + "dev": true, + "requires": { + "@types/decompress": "*", + "@types/got": "*", + "@types/node": "*" + } + }, + "@types/event-stream": { + "version": "3.3.34", + "resolved": "https://registry.npmjs.org/@types/event-stream/-/event-stream-3.3.34.tgz", + "integrity": "sha512-LLiivgWKii4JeMzFy3trrxqkRrVSdue8WmbXyHuSJLwNrhIQU5MTrc65jhxEPwMyh5HR1xevSdD+k2nnSRKw9g==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/events": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", + "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==", + "dev": true + }, + "@types/form-data": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz", + "integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/fs-extra": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-5.0.2.tgz", + "integrity": "sha512-Q3FWsbdmkQd1ib11A4XNWQvRD//5KpPoGawA8aB2DR7pWKoW9XQv3+dGxD/Z1eVFze23Okdo27ZQytVFlweKvQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/get-port": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@types/get-port/-/get-port-3.2.0.tgz", + "integrity": "sha512-TiNg8R1kjDde5Pub9F9vCwZA/BNW9HeXP5b9j7Qucqncy/McfPZ6xze/EyBdXS5FhMIGN6Fx3vg75l5KHy3V1Q==", + "dev": true + }, + "@types/glob": { + "version": "5.0.35", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-5.0.35.tgz", + "integrity": "sha512-wc+VveszMLyMWFvXLkloixT4n0harUIVZjnpzztaZ0nKLuul7Z32iMt2fUFGAaZ4y1XWjFRMtCI5ewvyh4aIeg==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/got": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/@types/got/-/got-8.3.1.tgz", + "integrity": "sha512-CGEPw67/Ub6gNMusk062tueurxN+HyjDCvYl4QVBKiSO+fqluXmRX/wSqST/4RtKth4mz8lDZiaZIpXr/uPROg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/iconv-lite": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@types/iconv-lite/-/iconv-lite-0.0.1.tgz", + "integrity": "sha1-qjuL2ivlErGuCgV7lC6GnDcKVWk=", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/istanbul": { + "version": "0.4.30", + "resolved": "https://registry.npmjs.org/@types/istanbul/-/istanbul-0.4.30.tgz", + "integrity": "sha512-+hQU4fh2G96ze78uI5/V6+SRDZD1UnVrFn23i2eDetwfbBq3s0/zYP92xj/3qyvVMM3WnvS88N56zjz+HmL04A==", + "dev": true + }, + "@types/lodash": { + "version": "4.14.109", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.109.tgz", + "integrity": "sha512-hop8SdPUEzbcJm6aTsmuwjIYQo1tqLseKCM+s2bBqTU2gErwI4fE+aqUVOlscPSQbKHKgtMMPoC+h4AIGOJYvw==", + "dev": true + }, + "@types/md5": { + "version": "2.1.32", + "resolved": "https://registry.npmjs.org/@types/md5/-/md5-2.1.32.tgz", + "integrity": "sha1-k+I0N/zRenucqY0CqmAC6DWEL+g=", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, + "@types/mocha": { + "version": "2.2.48", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.48.tgz", + "integrity": "sha512-nlK/iyETgafGli8Zh9zJVCTicvU3iajSkRwOh3Hhiva598CMqNJ4NcVCGMTGKpGpTYj/9R8RLzS9NAykSSCqGw==", + "dev": true + }, + "@types/node": { + "version": "9.4.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-9.4.7.tgz", + "integrity": "sha512-4Ba90mWNx8ddbafuyGGwjkZMigi+AWfYLSDCpovwsE63ia8w93r3oJ8PIAQc3y8U+XHcnMOHPIzNe3o438Ywcw==", + "dev": true + }, + "@types/request": { + "version": "2.47.0", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.47.0.tgz", + "integrity": "sha512-/KXM5oev+nNCLIgBjkwbk8VqxmzI56woD4VUxn95O+YeQ8hJzcSmIZ1IN3WexiqBb6srzDo2bdMbsXxgXNkz5Q==", + "dev": true, + "requires": { + "@types/caseless": "*", + "@types/form-data": "*", + "@types/node": "*", + "@types/tough-cookie": "*" + } + }, + "@types/semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==", + "dev": true + }, + "@types/shortid": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/shortid/-/shortid-0.0.29.tgz", + "integrity": "sha1-gJPuBBam4r8qpjOBCRFLP7/6Dps=", + "dev": true + }, + "@types/sinon": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-4.3.3.tgz", + "integrity": "sha512-Tt7w/ylBS/OEAlSCwzB0Db1KbxnkycP/1UkQpbvKFYoUuRn4uYsC3xh5TRPrOjTy0i8TIkSz1JdNL4GPVdf3KQ==", + "dev": true + }, + "@types/tough-cookie": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.3.tgz", + "integrity": "sha512-MDQLxNFRLasqS4UlkWMSACMKeSm1x4Q3TxzUC7KQUsh6RK1ZrQ0VEyE3yzXcBu+K8ejVj4wuX32eUG02yNp+YQ==", + "dev": true + }, + "@types/untildify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/untildify/-/untildify-3.0.0.tgz", + "integrity": "sha512-FTktI3Y1h+gP9GTjTvXBP5v8xpH4RU6uS9POoBcGy4XkS2Np6LNtnP1eiNNth4S7P+qw2c/rugkwBasSHFzJEg==", + "dev": true + }, + "@types/uuid": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.3.tgz", + "integrity": "sha512-5fRLCYhLtDb3hMWqQyH10qtF+Ud2JnNCXTCZ+9ktNdCcgslcuXkDTkFcJNk++MT29yDntDnlF1+jD+uVGumsbw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/winreg": { + "version": "1.2.30", + "resolved": "https://registry.npmjs.org/@types/winreg/-/winreg-1.2.30.tgz", + "integrity": "sha1-kdZxDlNtNFucmwF8V0z2qNpkxRg=", + "dev": true + }, + "@types/xml2js": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.2.tgz", + "integrity": "sha512-8aKUBSj3oGcnuiBmDLm3BIk09RYg01mz9HlQ2u4aS17oJ25DxjQrEUVGFSBVNOfM45pQW4OjcBPplq6r/exJdA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "JSONStream": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.3.tgz", + "integrity": "sha512-3Sp6WZZ/lXl+nTDoGpGWHEpTnnC6X5fnkolYZR6nwIfzbxxvA8utPWe1gCt7i0m9uVGsSz2IS8K8mJ7HmlduMg==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "acorn": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.5.3.tgz", + "integrity": "sha512-jd5MkIUlbbmb07nXH0DT3y7rDVtkzDi4XZOUVWAer8ajmF/DTSSbl5oNFyDOl/OXA33Bl79+ypHhl2pN20VeOQ==", + "dev": true + }, + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dev": true, + "requires": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, + "ansi-colors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, + "requires": { + "ansi-wrap": "^0.1.0" + } + }, + "ansi-cyan": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", + "integrity": "sha1-U4rlKK+JgvKK4w2G8vF0VtJgmHM=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha1-KWLPVOyXksSFEKPetSRDaGHvclE=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-red": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", + "integrity": "sha1-jGOPnRCAgAo1PJwoyKgcpHBdlGw=", + "dev": true, + "requires": { + "ansi-wrap": "0.1.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", + "dev": true + }, + "anymatch": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "dev": true, + "requires": { + "micromatch": "^2.1.5", + "normalize-path": "^2.0.0" + }, + "dependencies": { + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1" + } + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "requires": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + } + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "requires": { + "is-posix-bracket": "^0.1.0" + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "requires": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + } + } + } + }, + "append-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", + "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", + "dev": true, + "requires": { + "buffer-equal": "^1.0.0" + } + }, + "applicationinsights": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/applicationinsights/-/applicationinsights-1.0.1.tgz", + "integrity": "sha1-U0Rrgw/o1dYZ7uKieLMdPSUDCSc=", + "requires": { + "diagnostic-channel": "0.2.0", + "diagnostic-channel-publishers": "0.2.1", + "zone.js": "0.7.6" + } + }, + "arch": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.1.0.tgz", + "integrity": "sha1-NhOqRhSQZLPB8GB5Gb8dR4boKIk=" + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "argv": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/argv/-/argv-0.0.2.tgz", + "integrity": "sha1-7L0W+JSbFXGDcRsb2jNPN4QBhas=", + "dev": true + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-differ": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", + "dev": true + }, + "array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", + "dev": true + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, + "array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "dev": true + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "atob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.1.tgz", + "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", + "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" + }, + "azure-storage": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/azure-storage/-/azure-storage-2.8.3.tgz", + "integrity": "sha512-gcbdUY0tLivJvjUZD9BAxWrRDcige4OLFHhN3kY0p9oZYAFNNNqwgO7rBXvV+zdoX9HajeMOEog9/S/wxabeGg==", + "dev": true, + "requires": { + "browserify-mime": "~1.2.9", + "extend": "~1.2.1", + "json-edm-parser": "0.1.2", + "md5.js": "1.3.4", + "readable-stream": "~2.0.0", + "request": "^2.86.0", + "underscore": "~1.8.3", + "uuid": "^3.0.0", + "validator": "~9.4.1", + "xml2js": "0.2.8", + "xmlbuilder": "0.4.3" + }, + "dependencies": { + "extend": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-1.2.1.tgz", + "integrity": "sha1-oPX9bPyDpf5J72mNYOyKYk3UV2w=", + "dev": true + }, + "request": { + "version": "2.87.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", + "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.6.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.1", + "forever-agent": "~0.6.1", + "form-data": "~2.3.1", + "har-validator": "~5.0.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.17", + "oauth-sign": "~0.8.2", + "performance-now": "^2.1.0", + "qs": "~6.5.1", + "safe-buffer": "^5.1.1", + "tough-cookie": "~2.3.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.1.0" + }, + "dependencies": { + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", + "dev": true + } + } + }, + "sax": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz", + "integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE=", + "dev": true + }, + "xml2js": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.2.8.tgz", + "integrity": "sha1-m4FpCTFjH/CdGVdUn69U9PmAs8I=", + "dev": true, + "requires": { + "sax": "0.5.x" + } + }, + "xmlbuilder": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-0.4.3.tgz", + "integrity": "sha1-xGFLp04K0ZbmCcknLNnh3bKKilg=", + "dev": true + } + } + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha1-EQHpVE9KdrG8OybUUsqW16NeeXg=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", + "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "optional": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "beeper": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", + "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", + "dev": true + }, + "binary-extensions": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", + "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", + "dev": true + }, + "bl": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", + "dev": true, + "requires": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + }, + "dependencies": { + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "dev": true, + "requires": { + "inherits": "~2.0.0" + } + }, + "bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", + "dev": true + }, + "boom": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", + "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", + "requires": { + "hoek": "4.x.x" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "browserify-mime": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/browserify-mime/-/browserify-mime-1.2.9.tgz", + "integrity": "sha1-rrGvKN5sDXpqLOQK22j/GEIq8x8=", + "dev": true + }, + "buffer": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-3.6.0.tgz", + "integrity": "sha1-pyyTb3e5a/UvX357RnGAYoVR3vs=", + "dev": true, + "requires": { + "base64-js": "0.0.8", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dev": true, + "requires": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "dev": true + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + }, + "buffer-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", + "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=", + "dev": true + }, + "buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", + "dev": true + }, + "buffer-from": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz", + "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "cacheable-request": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz", + "integrity": "sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0=", + "dev": true, + "requires": { + "clone-response": "1.0.2", + "get-stream": "3.0.0", + "http-cache-semantics": "3.8.1", + "keyv": "3.0.0", + "lowercase-keys": "1.0.0", + "normalize-url": "2.0.1", + "responselike": "1.0.2" + }, + "dependencies": { + "lowercase-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", + "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=", + "dev": true + } + } + }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", + "dev": true + }, + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "caw": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz", + "integrity": "sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==", + "dev": true, + "requires": { + "get-proxy": "^2.0.0", + "isurl": "^1.0.0-alpha5", + "tunnel-agent": "^0.6.0", + "url-to-options": "^1.0.1" + } + }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dev": true, + "optional": true, + "requires": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + } + }, + "chai": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", + "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", + "dev": true, + "requires": { + "assertion-error": "^1.0.1", + "check-error": "^1.0.1", + "deep-eql": "^3.0.0", + "get-func-name": "^2.0.0", + "pathval": "^1.0.0", + "type-detect": "^4.0.0" + } + }, + "chai-arrays": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chai-arrays/-/chai-arrays-2.0.0.tgz", + "integrity": "sha512-jWAvZu1BV8tL3pj0iosBECzzHEg+XB1zSnMjJGX83bGi/1GlGdDO7J/A0sbBBS6KJT0FVqZIzZW9C6WLiMkHpQ==", + "dev": true + }, + "chai-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", + "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "dev": true, + "requires": { + "check-error": "^1.0.2" + } + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "chokidar": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "dev": true, + "requires": { + "anymatch": "^1.3.0", + "async-each": "^1.0.0", + "fsevents": "^1.0.0", + "glob-parent": "^2.0.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^2.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0" + }, + "dependencies": { + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "requires": { + "is-glob": "^2.0.0" + } + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + } + } + }, + "ci-info": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.1.3.tgz", + "integrity": "sha512-SK/846h/Rcy8q9Z9CAwGBLfCJ6EkjJWdpelWDufQpqVDYq2Wnnv8zlSO6AMQap02jvhVruKKpEtQOufo3pFhLg==", + "dev": true + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, + "optional": true, + "requires": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true, + "optional": true + } + } + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + }, + "clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "dev": true + }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", + "dev": true + }, + "cloneable-readable": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.2.tgz", + "integrity": "sha512-Bq6+4t+lbM8vhTs/Bef5c5AdEMtapp/iFb6+s4/Hh9MVTt8OLKH7ZOOZSCT+Ys7hsHvqv0GuMPJ1lnQJVHvxpg==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + }, + "dependencies": { + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "codecov": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/codecov/-/codecov-3.0.2.tgz", + "integrity": "sha512-9ljtIROIjPIUmMRqO+XuDITDoV8xRrZmA0jcEq6p2hg2+wY9wGmLfreAZGIL72IzUfdEDZaU8+Vjidg1fBQ8GQ==", + "dev": true, + "requires": { + "argv": "0.0.2", + "request": "^2.81.0", + "urlgrey": "0.4.4" + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", + "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "dev": true, + "requires": { + "color-name": "^1.1.1" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true + }, + "colors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.0.tgz", + "integrity": "sha512-EDpX3a7wHMWFA7PUHWPHNWqOxIIRSJetuwl0AS5Oi/5FMV8kWm69RTlgm00GKjBO1xFHMtBbL49yRtMMdticBw==", + "dev": true + }, + "combined-stream": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "commandpost": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/commandpost/-/commandpost-1.3.0.tgz", + "integrity": "sha512-T62tyrmYTkaRDbV2z1k2yXTyxk0cFptXYwo1cUbnfHtp7ThLgQ9/90jG1Ym5WLZgFhvOTaHA5VSARWJ9URpLDw==", + "dev": true + }, + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "config-chain": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.11.tgz", + "integrity": "sha1-q6CXR9++TD5w52am5BWG4YWfxvI=", + "dev": true, + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", + "dev": true + }, + "convert-source-map": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", + "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", + "dev": true + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" + }, + "cryptiles": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", + "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", + "requires": { + "boom": "5.x.x" + }, + "dependencies": { + "boom": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", + "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", + "requires": { + "hoek": "4.x.x" + } + } + } + }, + "css": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/css/-/css-2.2.3.tgz", + "integrity": "sha512-0W171WccAjQGGTKLhw4m2nnl0zPHUlTO/I8td4XzJgIB8Hg3ZZx71qT4G4eX8OVsSiaAKiUMy73E3nsbPlg2DQ==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "source-map": "^0.1.38", + "source-map-resolve": "^0.5.1", + "urix": "^0.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "^1.0.1" + } + }, + "d": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "dev": true, + "requires": { + "es5-ext": "^0.10.9" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "dateformat": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", + "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=", + "dev": true + }, + "debounce": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.1.0.tgz", + "integrity": "sha512-ZQVKfRVlwRfD150ndzEK8M90ABT+Y/JQKs4Y7U4MXdpuoUkkrr4DwKbVux3YjylA5bUMUj0Nc3pMxPJX6N2QQQ==", + "dev": true + }, + "debounce-hashed": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/debounce-hashed/-/debounce-hashed-0.1.2.tgz", + "integrity": "sha1-oN/jB8Gn2zD2kRyM+8DvhB831K8=", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "debug-fabulous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.1.0.tgz", + "integrity": "sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==", + "dev": true, + "requires": { + "debug": "3.X", + "memoizee": "0.4.X", + "object-assign": "4.X" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "decache": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/decache/-/decache-4.4.0.tgz", + "integrity": "sha1-b232uF1+fEQQqTL/wmSJt46azRM=", + "dev": true, + "requires": { + "callsite": "^1.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "decompress": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.0.tgz", + "integrity": "sha1-eu3YVCflqS2s/lVnSnxQXpbQH50=", + "dev": true, + "requires": { + "decompress-tar": "^4.0.0", + "decompress-tarbz2": "^4.0.0", + "decompress-targz": "^4.0.0", + "decompress-unzip": "^4.0.1", + "graceful-fs": "^4.1.10", + "make-dir": "^1.0.0", + "pify": "^2.3.0", + "strip-dirs": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "decompress-tar": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", + "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", + "dev": true, + "requires": { + "file-type": "^5.2.0", + "is-stream": "^1.1.0", + "tar-stream": "^1.5.2" + }, + "dependencies": { + "file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=", + "dev": true + } + } + }, + "decompress-tarbz2": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", + "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", + "dev": true, + "requires": { + "decompress-tar": "^4.1.0", + "file-type": "^6.1.0", + "is-stream": "^1.1.0", + "seek-bzip": "^1.0.5", + "unbzip2-stream": "^1.0.9" + }, + "dependencies": { + "file-type": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz", + "integrity": "sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==", + "dev": true + } + } + }, + "decompress-targz": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", + "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", + "dev": true, + "requires": { + "decompress-tar": "^4.1.1", + "file-type": "^5.2.0", + "is-stream": "^1.1.0" + }, + "dependencies": { + "file-type": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", + "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=", + "dev": true + } + } + }, + "decompress-unzip": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", + "integrity": "sha1-3qrM39FK6vhVePczroIQ+bSEj2k=", + "dev": true, + "requires": { + "file-type": "^3.8.0", + "get-stream": "^2.2.0", + "pify": "^2.3.0", + "yauzl": "^2.4.2" + }, + "dependencies": { + "file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha1-JXoHg4TR24CHvESdEH1SpSZyuek=", + "dev": true + }, + "get-stream": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", + "integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=", + "dev": true, + "requires": { + "object-assign": "^4.0.1", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "deep-assign": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/deep-assign/-/deep-assign-1.0.0.tgz", + "integrity": "sha1-sJJ0O+hCfcYh6gBnzex+cN0Z83s=", + "dev": true, + "requires": { + "is-obj": "^1.0.0" + } + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "deepmerge": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.1.1.tgz", + "integrity": "sha512-urQxA1smbLZ2cBbXbaYObM1dJ82aJ2H57A1C/Kklfh/ZN1bgH4G/n5KWhdNfOK11W98gqZfyYj7W4frJJRwA2w==", + "dev": true + }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dev": true, + "requires": { + "clone": "^1.0.2" + } + }, + "define-properties": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", + "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", + "dev": true, + "requires": { + "foreach": "^2.0.5", + "object-keys": "^1.0.8" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "del": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", + "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", + "dev": true, + "requires": { + "globby": "^6.1.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "p-map": "^1.1.1", + "pify": "^3.0.0", + "rimraf": "^2.2.8" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "deprecated": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/deprecated/-/deprecated-0.0.1.tgz", + "integrity": "sha1-+cmvVGSvoeepcUWKi97yqpTVuxk=", + "dev": true + }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true + }, + "detect-indent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", + "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=", + "dev": true + }, + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "dev": true + }, + "diagnostic-channel": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz", + "integrity": "sha1-zJmvlhLCP7H/8TYSxy8sv6qNWhc=", + "requires": { + "semver": "^5.3.0" + } + }, + "diagnostic-channel-publishers": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz", + "integrity": "sha1-ji1geottef6IC1SLxYzGvrKIxPM=" + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "diff-match-patch": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.0.tgz", + "integrity": "sha1-HMPIOkkNZ/ldkeOfatHy4Ia2MEg=" + }, + "doctrine": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-0.7.2.tgz", + "integrity": "sha1-fLhgNZujvpDgQLJrcpzkv6ZUxSM=", + "dev": true, + "requires": { + "esutils": "^1.1.6", + "isarray": "0.0.1" + }, + "dependencies": { + "esutils": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.1.6.tgz", + "integrity": "sha1-wBzKqa5LiXxtDD4hCuUvPHqEQ3U=", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } + } + }, + "dotenv": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-5.0.1.tgz", + "integrity": "sha512-4As8uPrjfwb7VXC+WnLCbXK7y+Ueb2B3zgNCePYfhxS1PYeaO1YTeplffTEcbfLhvFNGLAz90VvJs9yomG7bow==" + }, + "download": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/download/-/download-7.0.0.tgz", + "integrity": "sha512-0Fe/CAjKycx12IG9We9gYlLP03BEcWTpttg7P5mwfOiQTg584kpuHqP7F61RkUJM+mfEdEU9TJonm0PJp5rQLw==", + "dev": true, + "requires": { + "caw": "^2.0.1", + "content-disposition": "^0.5.2", + "decompress": "^4.2.0", + "ext-name": "^5.0.0", + "file-type": "^7.7.1", + "filenamify": "^2.0.0", + "get-stream": "^3.0.0", + "got": "^8.3.1", + "make-dir": "^1.2.0", + "p-event": "^1.3.0", + "pify": "^3.0.0" + } + }, + "duplexer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", + "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", + "dev": true + }, + "duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", + "dev": true, + "requires": { + "readable-stream": "~1.1.9" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + } + } + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, + "duplexify": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.0.tgz", + "integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==", + "dev": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + } + } + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "optional": true, + "requires": { + "jsbn": "~0.1.0" + } + }, + "editorconfig": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.13.3.tgz", + "integrity": "sha512-WkjsUNVCu+ITKDj73QDvi0trvpdDWdkDyHybDGSXPfekLCqwmpD7CP7iPbvBgosNuLcI96XTDwNa75JyFl7tEQ==", + "dev": true, + "requires": { + "bluebird": "^3.0.5", + "commander": "^2.9.0", + "lru-cache": "^3.2.0", + "semver": "^5.1.0", + "sigmund": "^1.0.1" + }, + "dependencies": { + "lru-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-3.2.0.tgz", + "integrity": "sha1-cXibO39Tmb7IVl3aOKow0qCX7+4=", + "dev": true, + "requires": { + "pseudomap": "^1.0.1" + } + } + } + }, + "end-of-stream": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-0.1.5.tgz", + "integrity": "sha1-jhdyBsPICDfYVjLouTWd/osvbq8=", + "dev": true, + "requires": { + "once": "~1.3.0" + }, + "dependencies": { + "once": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", + "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", + "dev": true, + "requires": { + "wrappy": "1" + } + } + } + }, + "error-ex": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", + "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es5-ext": { + "version": "0.10.43", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.43.tgz", + "integrity": "sha512-cZd1vezWuTM5qMlasKWqQFioFKwO352nVBzhOTMUf/pKQl5Gcq5EdJzqtSNXKnFQSCJDiQZjCYlYbnzFB657OA==", + "dev": true, + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.1", + "next-tick": "1" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "es6-weak-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", + "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.14", + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escodegen": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "dev": true, + "requires": { + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.2.0" + }, + "dependencies": { + "source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "dev": true, + "optional": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", + "dev": true + }, + "estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "event-stream": { + "version": "3.3.4", + "resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "dev": true, + "requires": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "dev": true, + "requires": { + "fill-range": "^2.1.0" + }, + "dependencies": { + "fill-range": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "dev": true, + "requires": { + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" + } + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "ext-list": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", + "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", + "dev": true, + "requires": { + "mime-db": "^1.28.0" + } + }, + "ext-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", + "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", + "dev": true, + "requires": { + "ext-list": "^2.0.0", + "sort-keys-length": "^1.0.0" + } + }, + "extend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fancy-log": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.2.tgz", + "integrity": "sha1-9BEl49hPLn2JpD0G2VjI94vha+E=", + "dev": true, + "requires": { + "ansi-gray": "^0.1.1", + "color-support": "^1.1.3", + "time-stamp": "^1.0.0" + } + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fd-slicer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", + "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "dev": true, + "requires": { + "pend": "~1.2.0" + } + }, + "file-type": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-7.7.1.tgz", + "integrity": "sha512-bTrKkzzZI6wH+NXhyD3SOXtb2zXTw2SbwI2RxUlRcXVsnN7jNL5hJzVQLYv7FOQhxFkK4XWdAflEaWFpaLLWpQ==", + "dev": true + }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", + "dev": true + }, + "filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=", + "dev": true + }, + "filenamify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-2.0.0.tgz", + "integrity": "sha1-vRYiYsC26Uv7zc8Zo7uzdk94VpU=", + "dev": true, + "requires": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.0", + "trim-repeated": "^1.0.0" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-index": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/find-index/-/find-index-0.1.1.tgz", + "integrity": "sha1-Z101iyyjiS15Whq0cjL4tuLg3eQ=", + "dev": true + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "findup-sync": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "dev": true, + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^3.1.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + } + }, + "fined": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.1.0.tgz", + "integrity": "sha1-s33IRLdqL15wgeiE98CuNE8VNHY=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^2.0.3", + "object.defaults": "^1.1.0", + "object.pick": "^1.2.0", + "parse-filepath": "^1.0.1" + } + }, + "first-chunk-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz", + "integrity": "sha1-Wb+1DNkF9g18OUzT2ayqtOatk04=", + "dev": true + }, + "flagged-respawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.0.tgz", + "integrity": "sha1-Tnmumy6zi/hrO7Vr8+ClaqX8q9c=", + "dev": true + }, + "flat": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.0.0.tgz", + "integrity": "sha512-ji/WMv2jdsE+LaznpkIF9Haax0sdpTBozrz/Dtg4qSRMfbs8oVg4ypJunIRYPiMLvH/ed6OflXbnbTIKJhtgeg==", + "dev": true, + "requires": { + "is-buffer": "~1.1.5" + } + }, + "flush-write-stream": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", + "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.4" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + }, + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "1.0.6", + "mime-types": "^2.1.12" + } + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", + "dev": true + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true + }, + "fs-extra": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs-mkdirp-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", + "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "through2": "^2.0.3" + } + }, + "fs-walk": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/fs-walk/-/fs-walk-0.0.1.tgz", + "integrity": "sha1-9/yRw64e6tB8mYvF0N1B8tvr0zU=", + "dev": true, + "requires": { + "async": "*" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", + "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.21", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": "^2.1.0" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "minipass": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.1.10", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.7", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.0.5" + } + }, + "safe-buffer": { + "version": "5.1.1", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.5.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.1", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true, + "dev": true + } + } + }, + "fstream": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", + "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "fuzzy": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/fuzzy/-/fuzzy-0.1.3.tgz", + "integrity": "sha1-THbsL/CsGjap3M+aAN+GIweNTtg=" + }, + "gaze": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.5.2.tgz", + "integrity": "sha1-QLcJU30k0dRXZ9takIaJ3+aaxE8=", + "dev": true, + "requires": { + "globule": "~0.1.0" + } + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "get-port": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", + "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=" + }, + "get-proxy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-2.1.0.tgz", + "integrity": "sha512-zmZIaQTWnNQb4R4fJUEp/FC51eZsc6EkErspy3xtIYStaq8EB/hDIWipxsal+E8rz0qD7f2sL/NA9Xee4RInJw==", + "dev": true, + "requires": { + "npm-conf": "^1.1.0" + } + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "dev": true, + "requires": { + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" + }, + "dependencies": { + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "requires": { + "is-glob": "^2.0.0" + } + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + } + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "glob-stream": { + "version": "3.1.18", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-3.1.18.tgz", + "integrity": "sha1-kXCl8St5Awb9/lmPMT+PeVT9FDs=", + "dev": true, + "requires": { + "glob": "^4.3.1", + "glob2base": "^0.0.12", + "minimatch": "^2.0.1", + "ordered-read-streams": "^0.1.0", + "through2": "^0.6.1", + "unique-stream": "^1.0.0" + }, + "dependencies": { + "glob": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz", + "integrity": "sha1-xstz0yJsHv7wTePFbQEvAzd+4V8=", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^2.0.1", + "once": "^1.3.0" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "minimatch": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", + "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", + "dev": true, + "requires": { + "brace-expansion": "^1.0.0" + } + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "dev": true, + "requires": { + "readable-stream": ">=1.0.33-1 <1.1.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + } + } + }, + "glob-watcher": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-0.0.6.tgz", + "integrity": "sha1-uVtKjfdLOcgymLDAXJeLTZo7cQs=", + "dev": true, + "requires": { + "gaze": "^0.5.1" + } + }, + "glob2base": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/glob2base/-/glob2base-0.0.12.tgz", + "integrity": "sha1-nUGbPijxLoOjYhZKJ3BVkiycDVY=", + "dev": true, + "requires": { + "find-index": "^0.1.1" + } + }, + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "requires": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + } + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "globule": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/globule/-/globule-0.1.0.tgz", + "integrity": "sha1-2cjt3h2nnRJaFRt5UzuXhnY0auU=", + "dev": true, + "requires": { + "glob": "~3.1.21", + "lodash": "~1.0.1", + "minimatch": "~0.2.11" + }, + "dependencies": { + "glob": { + "version": "3.1.21", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", + "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", + "dev": true, + "requires": { + "graceful-fs": "~1.2.0", + "inherits": "1", + "minimatch": "~0.2.11" + } + }, + "graceful-fs": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", + "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=", + "dev": true + }, + "inherits": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", + "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", + "dev": true + }, + "lodash": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", + "integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE=", + "dev": true + }, + "minimatch": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "dev": true, + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } + } + } + }, + "glogg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.1.tgz", + "integrity": "sha512-ynYqXLoluBKf9XGR1gA59yEJisIL7YHEH4xr3ZziHB5/yl4qWfaK8Js9jGe6gBGCSCKVqiyO30WnRZADvemUNw==", + "dev": true, + "requires": { + "sparkles": "^1.0.0" + } + }, + "got": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/got/-/got-8.3.1.tgz", + "integrity": "sha512-tiLX+bnYm5A56T5N/n9Xo89vMaO1mrS9qoDqj3u/anVooqGozvY/HbXzEpDfbNeKsHCBpK40gSbz8wGYSp3i1w==", + "dev": true, + "requires": { + "@sindresorhus/is": "^0.7.0", + "cacheable-request": "^2.1.1", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "into-stream": "^3.1.0", + "is-retry-allowed": "^1.1.0", + "isurl": "^1.0.0-alpha5", + "lowercase-keys": "^1.0.0", + "mimic-response": "^1.0.0", + "p-cancelable": "^0.4.0", + "p-timeout": "^2.0.1", + "pify": "^3.0.0", + "safe-buffer": "^5.1.1", + "timed-out": "^4.0.1", + "url-parse-lax": "^3.0.0", + "url-to-options": "^1.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", + "dev": true + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "gulp": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-3.9.1.tgz", + "integrity": "sha1-VxzkWSjdQK9lFPxAEYZgFsE4RbQ=", + "dev": true, + "requires": { + "archy": "^1.0.0", + "chalk": "^1.0.0", + "deprecated": "^0.0.1", + "gulp-util": "^3.0.0", + "interpret": "^1.0.0", + "liftoff": "^2.1.0", + "minimist": "^1.1.0", + "orchestrator": "^0.3.0", + "pretty-hrtime": "^1.0.0", + "semver": "^4.1.0", + "tildify": "^1.0.0", + "v8flags": "^2.0.2", + "vinyl-fs": "^0.3.0" + }, + "dependencies": { + "semver": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", + "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", + "dev": true + } + } + }, + "gulp-chmod": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/gulp-chmod/-/gulp-chmod-2.0.0.tgz", + "integrity": "sha1-AMOQuSigeZslGsz2MaoJ4BzGKZw=", + "dev": true, + "requires": { + "deep-assign": "^1.0.0", + "stat-mode": "^0.2.0", + "through2": "^2.0.0" + } + }, + "gulp-debounced-watch": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/gulp-debounced-watch/-/gulp-debounced-watch-1.0.4.tgz", + "integrity": "sha1-WkfU4kzkY2XOguysMqKjA+QysSo=", + "dev": true, + "requires": { + "debounce-hashed": "^0.1.1", + "gulp-watch": "^4.3.4", + "object-assign": "^3.0.0" + }, + "dependencies": { + "gulp-watch": { + "version": "4.3.11", + "resolved": "https://registry.npmjs.org/gulp-watch/-/gulp-watch-4.3.11.tgz", + "integrity": "sha1-Fi/FY96fx3DpH5p845VVE6mhGMA=", + "dev": true, + "requires": { + "anymatch": "^1.3.0", + "chokidar": "^1.6.1", + "glob-parent": "^3.0.1", + "gulp-util": "^3.0.7", + "object-assign": "^4.1.0", + "path-is-absolute": "^1.0.1", + "readable-stream": "^2.2.2", + "slash": "^1.0.0", + "vinyl": "^1.2.0", + "vinyl-file": "^2.0.0" + }, + "dependencies": { + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + } + } + }, + "object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "vinyl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", + "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", + "dev": true, + "requires": { + "clone": "^1.0.0", + "clone-stats": "^0.0.1", + "replace-ext": "0.0.1" + } + } + } + }, + "gulp-filter": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/gulp-filter/-/gulp-filter-5.1.0.tgz", + "integrity": "sha1-oF4Rr/sHz33PQafeHLe2OsN4PnM=", + "dev": true, + "requires": { + "multimatch": "^2.0.0", + "plugin-error": "^0.1.2", + "streamfilter": "^1.0.5" + } + }, + "gulp-gitmodified": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/gulp-gitmodified/-/gulp-gitmodified-1.1.1.tgz", + "integrity": "sha1-hfNnWRXB1RtmgH8o3g67WR5+nfQ=", + "dev": true, + "requires": { + "gulp-util": "~2.2.12", + "lodash.find": "^3.2.1", + "through2": "^2.0.0", + "vinyl": "^0.4.3", + "which": "~1.0.5" + }, + "dependencies": { + "ansi-regex": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", + "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=", + "dev": true + }, + "ansi-styles": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz", + "integrity": "sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=", + "dev": true + }, + "chalk": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", + "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", + "dev": true, + "requires": { + "ansi-styles": "^1.1.0", + "escape-string-regexp": "^1.0.0", + "has-ansi": "^0.1.0", + "strip-ansi": "^0.3.0", + "supports-color": "^0.2.0" + } + }, + "clone": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", + "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", + "dev": true + }, + "dateformat": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", + "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1", + "meow": "^3.3.0" + } + }, + "gulp-util": { + "version": "2.2.20", + "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-2.2.20.tgz", + "integrity": "sha1-1xRuVyiRC9jwR6awseVJvCLb1kw=", + "dev": true, + "requires": { + "chalk": "^0.5.0", + "dateformat": "^1.0.7-1.2.3", + "lodash._reinterpolate": "^2.4.1", + "lodash.template": "^2.4.1", + "minimist": "^0.2.0", + "multipipe": "^0.1.0", + "through2": "^0.5.0", + "vinyl": "^0.2.1" + }, + "dependencies": { + "through2": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.5.1.tgz", + "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", + "dev": true, + "requires": { + "readable-stream": "~1.0.17", + "xtend": "~3.0.0" + } + }, + "vinyl": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.2.3.tgz", + "integrity": "sha1-vKk4IJWC7FpJrVOKAPofEl5RMlI=", + "dev": true, + "requires": { + "clone-stats": "~0.0.1" + } + } + } + }, + "has-ansi": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", + "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=", + "dev": true, + "requires": { + "ansi-regex": "^0.2.0" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "lodash._reinterpolate": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-2.4.1.tgz", + "integrity": "sha1-TxInqlqHEfxjL1sHofRgequLMiI=", + "dev": true + }, + "lodash.escape": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-2.4.1.tgz", + "integrity": "sha1-LOEsXghNsKV92l5dHu659dF1o7Q=", + "dev": true, + "requires": { + "lodash._escapehtmlchar": "~2.4.1", + "lodash._reunescapedhtml": "~2.4.1", + "lodash.keys": "~2.4.1" + } + }, + "lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "dev": true, + "requires": { + "lodash._isnative": "~2.4.1", + "lodash._shimkeys": "~2.4.1", + "lodash.isobject": "~2.4.1" + } + }, + "lodash.template": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-2.4.1.tgz", + "integrity": "sha1-nmEQB+32KRKal0qzxIuBez4c8g0=", + "dev": true, + "requires": { + "lodash._escapestringchar": "~2.4.1", + "lodash._reinterpolate": "~2.4.1", + "lodash.defaults": "~2.4.1", + "lodash.escape": "~2.4.1", + "lodash.keys": "~2.4.1", + "lodash.templatesettings": "~2.4.1", + "lodash.values": "~2.4.1" + } + }, + "lodash.templatesettings": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-2.4.1.tgz", + "integrity": "sha1-6nbHXRHrhtTb6JqDiTu4YZKaxpk=", + "dev": true, + "requires": { + "lodash._reinterpolate": "~2.4.1", + "lodash.escape": "~2.4.1" + } + }, + "minimist": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.2.0.tgz", + "integrity": "sha1-Tf/lJdriuGTGbC4jxicdev3s784=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "strip-ansi": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", + "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", + "dev": true, + "requires": { + "ansi-regex": "^0.2.1" + } + }, + "supports-color": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", + "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=", + "dev": true + }, + "vinyl": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", + "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", + "dev": true, + "requires": { + "clone": "^0.2.0", + "clone-stats": "^0.0.1" + } + }, + "which": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/which/-/which-1.0.9.tgz", + "integrity": "sha1-RgwdoPgQED0DIam2M6+eV15kSG8=", + "dev": true + }, + "xtend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz", + "integrity": "sha1-XM50B7r2Qsunvs2laBEcST9ZZlo=", + "dev": true + } + } + }, + "gulp-gunzip": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulp-gunzip/-/gulp-gunzip-1.0.0.tgz", + "integrity": "sha1-FbdBFF6Dqcb1CIYkG1fMWHHxUak=", + "dev": true, + "requires": { + "through2": "~0.6.5", + "vinyl": "~0.4.6" + }, + "dependencies": { + "clone": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", + "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "dev": true, + "requires": { + "readable-stream": ">=1.0.33-1 <1.1.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + }, + "vinyl": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", + "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", + "dev": true, + "requires": { + "clone": "^0.2.0", + "clone-stats": "^0.0.1" + } + } + } + }, + "gulp-json-editor": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/gulp-json-editor/-/gulp-json-editor-2.4.1.tgz", + "integrity": "sha512-20nYwO5Bec5X6DfXmBmHEtDAyluTkMguhuvCzqwrHDv/NzwOn3qS4ofAMw9L2gnWAmzxKzHAkFO19LNDWyTwlg==", + "dev": true, + "requires": { + "deepmerge": "^2.1.0", + "detect-indent": "^5.0.0", + "js-beautify": "^1.7.5", + "plugin-error": "^1.0.1", + "through2": "^2.0.3" + }, + "dependencies": { + "plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + } + } + } + }, + "gulp-remote-src-vscode": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/gulp-remote-src-vscode/-/gulp-remote-src-vscode-0.5.0.tgz", + "integrity": "sha512-/9vtSk9eI9DEWCqzGieglPqmx0WUQ9pwPHyHFpKmfxqdgqGJC2l0vFMdYs54hLdDsMDEZFLDL2J4ikjc4hQ5HQ==", + "dev": true, + "requires": { + "event-stream": "^3.3.4", + "node.extend": "^1.1.2", + "request": "^2.79.0", + "through2": "^2.0.3", + "vinyl": "^2.0.1" + }, + "dependencies": { + "clone": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", + "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true + }, + "vinyl": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.1.0.tgz", + "integrity": "sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=", + "dev": true, + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + } + } + }, + "gulp-sourcemaps": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-2.6.4.tgz", + "integrity": "sha1-y7IAhFCxvM5s0jv5gze+dRv24wo=", + "dev": true, + "requires": { + "@gulp-sourcemaps/identity-map": "1.X", + "@gulp-sourcemaps/map-sources": "1.X", + "acorn": "5.X", + "convert-source-map": "1.X", + "css": "2.X", + "debug-fabulous": "1.X", + "detect-newline": "2.X", + "graceful-fs": "4.X", + "source-map": "~0.6.0", + "strip-bom-string": "1.X", + "through2": "2.X" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "gulp-symdest": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/gulp-symdest/-/gulp-symdest-1.1.0.tgz", + "integrity": "sha1-wWUyBzLRks5W/ZQnH/oSMjS/KuA=", + "dev": true, + "requires": { + "event-stream": "^3.3.1", + "mkdirp": "^0.5.1", + "queue": "^3.1.0", + "vinyl-fs": "^2.4.3" + }, + "dependencies": { + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1" + } + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "requires": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + } + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "requires": { + "is-posix-bracket": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + }, + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-stream": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-5.3.5.tgz", + "integrity": "sha1-pVZlqajM3EGRWofHAeMtTgFvrSI=", + "dev": true, + "requires": { + "extend": "^3.0.0", + "glob": "^5.0.3", + "glob-parent": "^3.0.0", + "micromatch": "^2.3.7", + "ordered-read-streams": "^0.3.0", + "through2": "^0.6.0", + "to-absolute-glob": "^0.1.1", + "unique-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "dev": true, + "requires": { + "readable-stream": ">=1.0.33-1 <1.1.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + } + } + }, + "gulp-sourcemaps": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-1.6.0.tgz", + "integrity": "sha1-uG/zSdgBzrVuHZ59x7vLS33uYAw=", + "dev": true, + "requires": { + "convert-source-map": "^1.1.1", + "graceful-fs": "^4.1.2", + "strip-bom": "^2.0.0", + "through2": "^2.0.0", + "vinyl": "^1.0.0" + } + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + }, + "is-valid-glob": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-0.3.0.tgz", + "integrity": "sha1-1LVcafUYhvm2XHDWwmItN+KfSP4=", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "requires": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + } + }, + "ordered-read-streams": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz", + "integrity": "sha1-cTfmmzKYuzQiR6G77jiByA4v14s=", + "dev": true, + "requires": { + "is-stream": "^1.0.1", + "readable-stream": "^2.0.1" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-bom-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz", + "integrity": "sha1-5xRDmFd9Uaa+0PoZlPoF9D/ZiO4=", + "dev": true, + "requires": { + "first-chunk-stream": "^1.0.0", + "strip-bom": "^2.0.0" + } + }, + "to-absolute-glob": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-0.1.1.tgz", + "integrity": "sha1-HN+kcqnvUMI57maZm2YsoOs5k38=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1" + } + }, + "unique-stream": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.2.1.tgz", + "integrity": "sha1-WqADz76Uxf+GbE59ZouxxNuts2k=", + "dev": true, + "requires": { + "json-stable-stringify": "^1.0.0", + "through2-filter": "^2.0.0" + } + }, + "vinyl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", + "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", + "dev": true, + "requires": { + "clone": "^1.0.0", + "clone-stats": "^0.0.1", + "replace-ext": "0.0.1" + } + }, + "vinyl-fs": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-2.4.4.tgz", + "integrity": "sha1-vm/zJwy1Xf19MGNkDegfJddTIjk=", + "dev": true, + "requires": { + "duplexify": "^3.2.0", + "glob-stream": "^5.3.2", + "graceful-fs": "^4.0.0", + "gulp-sourcemaps": "1.6.0", + "is-valid-glob": "^0.3.0", + "lazystream": "^1.0.0", + "lodash.isequal": "^4.0.0", + "merge-stream": "^1.0.0", + "mkdirp": "^0.5.0", + "object-assign": "^4.0.0", + "readable-stream": "^2.0.4", + "strip-bom": "^2.0.0", + "strip-bom-stream": "^1.0.0", + "through2": "^2.0.0", + "through2-filter": "^2.0.0", + "vali-date": "^1.0.0", + "vinyl": "^1.0.0" + } + } + } + }, + "gulp-typescript": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/gulp-typescript/-/gulp-typescript-4.0.2.tgz", + "integrity": "sha512-Hhbn5Aa2l3T+tnn0KqsG6RRJmcYEsr3byTL2nBpNBeAK8pqug9Od4AwddU4JEI+hRw7mzZyjRbB8DDWR6paGVA==", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1", + "plugin-error": "^0.1.2", + "source-map": "^0.6.1", + "through2": "^2.0.3", + "vinyl": "^2.1.0", + "vinyl-fs": "^3.0.0" + }, + "dependencies": { + "clone": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", + "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "glob-stream": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", + "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", + "dev": true, + "requires": { + "extend": "^3.0.0", + "glob": "^7.1.1", + "glob-parent": "^3.1.0", + "is-negated-glob": "^1.0.0", + "ordered-read-streams": "^1.0.0", + "pumpify": "^1.3.5", + "readable-stream": "^2.1.5", + "remove-trailing-separator": "^1.0.1", + "to-absolute-glob": "^2.0.0", + "unique-stream": "^2.0.2" + } + }, + "ordered-read-streams": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", + "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", + "dev": true, + "requires": { + "readable-stream": "^2.0.1" + } + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "unique-stream": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.2.1.tgz", + "integrity": "sha1-WqADz76Uxf+GbE59ZouxxNuts2k=", + "dev": true, + "requires": { + "json-stable-stringify": "^1.0.0", + "through2-filter": "^2.0.0" + } + }, + "vinyl": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.1.0.tgz", + "integrity": "sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=", + "dev": true, + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + }, + "vinyl-fs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-3.0.3.tgz", + "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", + "dev": true, + "requires": { + "fs-mkdirp-stream": "^1.0.0", + "glob-stream": "^6.1.0", + "graceful-fs": "^4.0.0", + "is-valid-glob": "^1.0.0", + "lazystream": "^1.0.0", + "lead": "^1.0.0", + "object.assign": "^4.0.4", + "pumpify": "^1.3.5", + "readable-stream": "^2.3.3", + "remove-bom-buffer": "^3.0.0", + "remove-bom-stream": "^1.2.0", + "resolve-options": "^1.1.0", + "through2": "^2.0.0", + "to-through": "^2.0.0", + "value-or-function": "^3.0.0", + "vinyl": "^2.0.0", + "vinyl-sourcemap": "^1.1.0" + } + } + } + }, + "gulp-untar": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/gulp-untar/-/gulp-untar-0.0.7.tgz", + "integrity": "sha512-0QfbCH2a1k2qkTLWPqTX+QO4qNsHn3kC546YhAP3/n0h+nvtyGITDuDrYBMDZeW4WnFijmkOvBWa5HshTic1tw==", + "dev": true, + "requires": { + "event-stream": "~3.3.4", + "streamifier": "~0.1.1", + "tar": "^2.2.1", + "through2": "~2.0.3", + "vinyl": "^1.2.0" + }, + "dependencies": { + "vinyl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", + "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", + "dev": true, + "requires": { + "clone": "^1.0.0", + "clone-stats": "^0.0.1", + "replace-ext": "0.0.1" + } + } + } + }, + "gulp-util": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", + "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", + "dev": true, + "requires": { + "array-differ": "^1.0.0", + "array-uniq": "^1.0.2", + "beeper": "^1.0.0", + "chalk": "^1.0.0", + "dateformat": "^2.0.0", + "fancy-log": "^1.1.0", + "gulplog": "^1.0.0", + "has-gulplog": "^0.1.0", + "lodash._reescape": "^3.0.0", + "lodash._reevaluate": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.template": "^3.0.0", + "minimist": "^1.1.0", + "multipipe": "^0.1.2", + "object-assign": "^3.0.0", + "replace-ext": "0.0.1", + "through2": "^2.0.0", + "vinyl": "^0.5.0" + }, + "dependencies": { + "object-assign": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", + "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", + "dev": true + } + } + }, + "gulp-vinyl-zip": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/gulp-vinyl-zip/-/gulp-vinyl-zip-2.1.0.tgz", + "integrity": "sha1-JOQGhdwFtxSZlSRQmeBZAmO+ja0=", + "dev": true, + "requires": { + "event-stream": "^3.3.1", + "queue": "^4.2.1", + "through2": "^2.0.3", + "vinyl": "^2.0.2", + "vinyl-fs": "^2.0.0", + "yauzl": "^2.2.1", + "yazl": "^2.2.1" + }, + "dependencies": { + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1" + } + }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "requires": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + } + }, + "clone": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", + "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "requires": { + "is-posix-bracket": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + }, + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-stream": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-5.3.5.tgz", + "integrity": "sha1-pVZlqajM3EGRWofHAeMtTgFvrSI=", + "dev": true, + "requires": { + "extend": "^3.0.0", + "glob": "^5.0.3", + "glob-parent": "^3.0.0", + "micromatch": "^2.3.7", + "ordered-read-streams": "^0.3.0", + "through2": "^0.6.0", + "to-absolute-glob": "^0.1.1", + "unique-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "dev": true, + "requires": { + "readable-stream": ">=1.0.33-1 <1.1.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + } + } + }, + "gulp-sourcemaps": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/gulp-sourcemaps/-/gulp-sourcemaps-1.6.0.tgz", + "integrity": "sha1-uG/zSdgBzrVuHZ59x7vLS33uYAw=", + "dev": true, + "requires": { + "convert-source-map": "^1.1.1", + "graceful-fs": "^4.1.2", + "strip-bom": "^2.0.0", + "through2": "^2.0.0", + "vinyl": "^1.0.0" + }, + "dependencies": { + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + }, + "clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", + "dev": true + }, + "replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", + "dev": true + }, + "vinyl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", + "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", + "dev": true, + "requires": { + "clone": "^1.0.0", + "clone-stats": "^0.0.1", + "replace-ext": "0.0.1" + } + } + } + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + }, + "is-valid-glob": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-0.3.0.tgz", + "integrity": "sha1-1LVcafUYhvm2XHDWwmItN+KfSP4=", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "requires": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + } + }, + "ordered-read-streams": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz", + "integrity": "sha1-cTfmmzKYuzQiR6G77jiByA4v14s=", + "dev": true, + "requires": { + "is-stream": "^1.0.1", + "readable-stream": "^2.0.1" + } + }, + "queue": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-4.4.2.tgz", + "integrity": "sha512-fSMRXbwhMwipcDZ08enW2vl+YDmAmhcNcr43sCJL8DIg+CFOsoRLG23ctxA+fwNk1w55SePSiS7oqQQSgQoVJQ==", + "dev": true, + "requires": { + "inherits": "~2.0.0" + } + }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-bom-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz", + "integrity": "sha1-5xRDmFd9Uaa+0PoZlPoF9D/ZiO4=", + "dev": true, + "requires": { + "first-chunk-stream": "^1.0.0", + "strip-bom": "^2.0.0" + } + }, + "to-absolute-glob": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-0.1.1.tgz", + "integrity": "sha1-HN+kcqnvUMI57maZm2YsoOs5k38=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1" + } + }, + "unique-stream": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.2.1.tgz", + "integrity": "sha1-WqADz76Uxf+GbE59ZouxxNuts2k=", + "dev": true, + "requires": { + "json-stable-stringify": "^1.0.0", + "through2-filter": "^2.0.0" + } + }, + "vinyl": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.1.0.tgz", + "integrity": "sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=", + "dev": true, + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + }, + "vinyl-fs": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-2.4.4.tgz", + "integrity": "sha1-vm/zJwy1Xf19MGNkDegfJddTIjk=", + "dev": true, + "requires": { + "duplexify": "^3.2.0", + "glob-stream": "^5.3.2", + "graceful-fs": "^4.0.0", + "gulp-sourcemaps": "1.6.0", + "is-valid-glob": "^0.3.0", + "lazystream": "^1.0.0", + "lodash.isequal": "^4.0.0", + "merge-stream": "^1.0.0", + "mkdirp": "^0.5.0", + "object-assign": "^4.0.0", + "readable-stream": "^2.0.4", + "strip-bom": "^2.0.0", + "strip-bom-stream": "^1.0.0", + "through2": "^2.0.0", + "through2-filter": "^2.0.0", + "vali-date": "^1.0.0", + "vinyl": "^1.0.0" + }, + "dependencies": { + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + }, + "clone-stats": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", + "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", + "dev": true + }, + "replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", + "dev": true + }, + "vinyl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", + "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", + "dev": true, + "requires": { + "clone": "^1.0.0", + "clone-stats": "^0.0.1", + "replace-ext": "0.0.1" + } + } + } + } + } + }, + "gulp-watch": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/gulp-watch/-/gulp-watch-5.0.0.tgz", + "integrity": "sha512-q+HLppxXd11z9ndqql4Z0sd5xOAesJjycl0PRaq6ImK7b1BqBRL37YvxEE8ngUdIfpfHa0O9OCoovoggcFpCaQ==", + "dev": true, + "requires": { + "anymatch": "^1.3.0", + "chokidar": "^2.0.0", + "glob-parent": "^3.0.1", + "gulp-util": "^3.0.7", + "object-assign": "^4.1.0", + "path-is-absolute": "^1.0.1", + "readable-stream": "^2.2.2", + "slash": "^1.0.0", + "vinyl": "^2.1.0", + "vinyl-file": "^2.0.0" + }, + "dependencies": { + "chokidar": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.3.tgz", + "integrity": "sha512-zW8iXYZtXMx4kux/nuZVXjkLP+CyIK5Al5FHnj1OgTKGZfp4Oy6/ymtMSKFv3GD8DviEmUPmJg9eFdJ/JzudMg==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.0", + "braces": "^2.3.0", + "fsevents": "^1.1.2", + "glob-parent": "^3.1.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^2.1.1", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0", + "upath": "^1.0.0" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + } + } + }, + "clone": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", + "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "is-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "vinyl": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.1.0.tgz", + "integrity": "sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=", + "dev": true, + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + } + } + }, + "gulplog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", + "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", + "dev": true, + "requires": { + "glogg": "^1.0.0" + } + }, + "handlebars": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz", + "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", + "dev": true, + "requires": { + "async": "^1.4.0", + "optimist": "^0.6.1", + "source-map": "^0.4.4", + "uglify-js": "^2.6" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "requires": { + "ajv": "^5.1.0", + "har-schema": "^2.0.0" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "has-gulplog": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", + "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", + "dev": true, + "requires": { + "sparkles": "^1.0.0" + } + }, + "has-symbol-support-x": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", + "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==", + "dev": true + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "has-to-string-tag-x": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", + "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", + "dev": true, + "requires": { + "has-symbol-support-x": "^1.4.1" + } + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", + "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "hawk": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", + "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", + "requires": { + "boom": "4.x.x", + "cryptiles": "3.x.x", + "hoek": "4.x.x", + "sntp": "2.x.x" + } + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "hoek": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", + "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" + }, + "homedir-polyfill": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", + "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", + "dev": true, + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "hosted-git-info": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", + "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==", + "dev": true + }, + "http-cache-semantics": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", + "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==", + "dev": true + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "husky": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/husky/-/husky-0.14.3.tgz", + "integrity": "sha512-e21wivqHpstpoiWA/Yi8eFti8E+sQDSS53cpJsPptPs295QTOQR0ZwnHo2TXy1XOpZFD9rPOd3NpmqTK6uMLJA==", + "dev": true, + "requires": { + "is-ci": "^1.0.10", + "normalize-path": "^1.0.0", + "strip-indent": "^2.0.0" + }, + "dependencies": { + "normalize-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-1.0.0.tgz", + "integrity": "sha1-MtDkcvkf80VwHBWoMRAY07CpA3k=", + "dev": true + }, + "strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", + "dev": true + } + } + }, + "iconv-lite": { + "version": "0.4.21", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.21.tgz", + "integrity": "sha512-En5V9za5mBt2oUA03WGD3TwDv0MKAruqsuxstbMUZaj9W9k/m1CV/9py3l0L5kw9Bln8fdHQmzHSYtvpvTLpKw==", + "requires": { + "safer-buffer": "^2.1.0" + } + }, + "ieee754": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.11.tgz", + "integrity": "sha512-VhDzCKN7K8ufStx/CLj5/PDTMgph+qwN5Pkd5i0sGnVwk56zJ0lkT8Qzi1xqWLS0Wp29DgDtNeS7v8/wMoZeHg==", + "dev": true + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "dev": true + }, + "interpret": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", + "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", + "dev": true + }, + "into-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz", + "integrity": "sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY=", + "dev": true, + "requires": { + "from2": "^2.1.1", + "p-is-promise": "^1.1.0" + } + }, + "inversify": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/inversify/-/inversify-4.11.1.tgz", + "integrity": "sha512-9bs/36crPdTSOCcoomHMb96s+B8W0+2c9dHFP/Srv9ZQaPnUvsMgzmMHfgVECqfHVUIW+M5S7SYOjoig8khWuQ==" + }, + "is": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is/-/is-3.2.1.tgz", + "integrity": "sha1-0Kwq1V63sL7JJqUmb2xmKqqD3KU=", + "dev": true + }, + "is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "requires": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dev": true, + "requires": { + "builtin-modules": "^1.0.0" + } + }, + "is-ci": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.1.0.tgz", + "integrity": "sha512-c7TnwxLePuqIlxHgr7xtxzycJPegNHFuIrBkwbf8hc58//+Op1CqFkyS+xnIMkwn9UsJIwc174BIjkyBmSpjKg==", + "dev": true, + "requires": { + "ci-info": "^1.0.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", + "dev": true + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "dev": true, + "requires": { + "is-primitive": "^2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", + "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + }, + "is-natural-number": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz", + "integrity": "sha1-q5124dtM7VHjXeDHLr7PCfc0zeg=", + "dev": true + }, + "is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true + }, + "is-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", + "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=", + "dev": true + }, + "is-odd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", + "integrity": "sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==", + "dev": true, + "requires": { + "is-number": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + } + } + }, + "is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "requires": { + "is-path-inside": "^1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", + "dev": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", + "dev": true + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", + "dev": true + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, + "is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "requires": { + "is-unc-path": "^1.0.0" + } + }, + "is-retry-allowed": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", + "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=", + "dev": true + }, + "is-running": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-running/-/is-running-2.1.0.tgz", + "integrity": "sha1-MKc/9cw4VOT8JUkICen1q/jeCeA=", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "requires": { + "unc-path-regex": "^0.1.2" + } + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "istanbul": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz", + "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", + "dev": true, + "requires": { + "abbrev": "1.0.x", + "async": "1.x", + "escodegen": "1.8.x", + "esprima": "2.7.x", + "glob": "^5.0.15", + "handlebars": "^4.0.1", + "js-yaml": "3.x", + "mkdirp": "0.5.x", + "nopt": "3.x", + "once": "1.x", + "resolve": "1.1.x", + "supports-color": "^3.1.0", + "which": "^1.1.1", + "wordwrap": "^1.0.0" + }, + "dependencies": { + "abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", + "dev": true + }, + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "dev": true, + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "isurl": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", + "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", + "dev": true, + "requires": { + "has-to-string-tag-x": "^1.2.0", + "is-object": "^1.0.1" + } + }, + "js-beautify": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.7.5.tgz", + "integrity": "sha512-9OhfAqGOrD7hoQBLJMTA+BKuKmoEtTJXzZ7WDF/9gvjtey1koVLuZqIY6c51aPDjbNdNtIXAkiWKVhziawE9Og==", + "dev": true, + "requires": { + "config-chain": "~1.1.5", + "editorconfig": "^0.13.2", + "mkdirp": "~0.5.0", + "nopt": "~3.0.1" + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz", + "integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "dependencies": { + "esprima": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", + "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "dev": true + } + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "optional": true + }, + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "dev": true + }, + "json-edm-parser": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/json-edm-parser/-/json-edm-parser-0.1.2.tgz", + "integrity": "sha1-HmCw/vG8CvZ7wNFG393lSGzWFbQ=", + "dev": true, + "requires": { + "jsonparse": "~1.2.0" + } + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "requires": { + "jsonify": "~0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsonparse": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.2.0.tgz", + "integrity": "sha1-XAxWhRBxYOcv50ib3eoLRMK8Z70=", + "dev": true + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "just-extend": { + "version": "1.1.27", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-1.1.27.tgz", + "integrity": "sha512-mJVp13Ix6gFo3SBAy9U/kL+oeZqzlYYYLQBwXVBlVzIsZwBqGREnOro24oC/8s8aox+rJhtZ2DiQof++IrkA+g==", + "dev": true + }, + "keyv": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", + "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==", + "dev": true, + "requires": { + "json-buffer": "3.0.0" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true, + "optional": true + }, + "lazystream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", + "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "dev": true, + "requires": { + "readable-stream": "^2.0.5" + } + }, + "lead": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-1.0.0.tgz", + "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", + "dev": true, + "requires": { + "flush-write-stream": "^1.0.2" + } + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "liftoff": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-2.5.0.tgz", + "integrity": "sha1-IAkpG7Mc6oYbvxCnwVooyvdcMew=", + "dev": true, + "requires": { + "extend": "^3.0.0", + "findup-sync": "^2.0.0", + "fined": "^1.0.1", + "flagged-respawn": "^1.0.0", + "is-plain-object": "^2.0.4", + "object.map": "^1.0.0", + "rechoir": "^0.6.2", + "resolve": "^1.1.7" + } + }, + "line-by-line": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/line-by-line/-/line-by-line-0.1.6.tgz", + "integrity": "sha512-MmwVPfOyp0lWnEZ3fBA8Ah4pMFvxO6WgWovqZNu7Y4J0TNnGcsV4S1LzECHbdgqk1hoHc2mFP1Axc37YUqwafg==" + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + } + } + }, + "lodash": { + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", + "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" + }, + "lodash._basecallback": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/lodash._basecallback/-/lodash._basecallback-3.3.1.tgz", + "integrity": "sha1-t7K7Q9whYEJKIczybFfkQ3cqjic=", + "dev": true, + "requires": { + "lodash._baseisequal": "^3.0.0", + "lodash._bindcallback": "^3.0.0", + "lodash.isarray": "^3.0.0", + "lodash.pairs": "^3.0.0" + } + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", + "dev": true + }, + "lodash._baseeach": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash._baseeach/-/lodash._baseeach-3.0.4.tgz", + "integrity": "sha1-z4cGVyyhROjZ11InyZDamC+TKvM=", + "dev": true, + "requires": { + "lodash.keys": "^3.0.0" + } + }, + "lodash._basefind": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._basefind/-/lodash._basefind-3.0.0.tgz", + "integrity": "sha1-srugXMZF+XLeLPkl+iv2Og9gyK4=", + "dev": true + }, + "lodash._basefindindex": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/lodash._basefindindex/-/lodash._basefindindex-3.6.0.tgz", + "integrity": "sha1-8IM2ChsCJBjtgbyJm+sxLiHnSk8=", + "dev": true + }, + "lodash._baseisequal": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/lodash._baseisequal/-/lodash._baseisequal-3.0.7.tgz", + "integrity": "sha1-2AJfdjOdKTQnZ9zIh85cuVpbUfE=", + "dev": true, + "requires": { + "lodash.isarray": "^3.0.0", + "lodash.istypedarray": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "lodash._basetostring": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", + "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=", + "dev": true + }, + "lodash._basevalues": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", + "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", + "dev": true + }, + "lodash._bindcallback": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", + "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=", + "dev": true + }, + "lodash._escapehtmlchar": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._escapehtmlchar/-/lodash._escapehtmlchar-2.4.1.tgz", + "integrity": "sha1-32fDu2t+jh6DGrSL+geVuSr+iZ0=", + "dev": true, + "requires": { + "lodash._htmlescapes": "~2.4.1" + } + }, + "lodash._escapestringchar": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._escapestringchar/-/lodash._escapestringchar-2.4.1.tgz", + "integrity": "sha1-7P4iYYoq3lC/7qQ5N+Ud9m8O23I=", + "dev": true + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", + "dev": true + }, + "lodash._htmlescapes": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._htmlescapes/-/lodash._htmlescapes-2.4.1.tgz", + "integrity": "sha1-MtFL8IRLbeb4tioFG09nwii2JMs=", + "dev": true + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", + "dev": true + }, + "lodash._isnative": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._isnative/-/lodash._isnative-2.4.1.tgz", + "integrity": "sha1-PqZAS3hKe+g2x7V1gOHN95sUgyw=", + "dev": true + }, + "lodash._objecttypes": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz", + "integrity": "sha1-fAt/admKH3ZSn4kLDNsbTf7BHBE=", + "dev": true + }, + "lodash._reescape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", + "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=", + "dev": true + }, + "lodash._reevaluate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", + "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=", + "dev": true + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", + "dev": true + }, + "lodash._reunescapedhtml": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._reunescapedhtml/-/lodash._reunescapedhtml-2.4.1.tgz", + "integrity": "sha1-dHxPxAED6zu4oJduVx96JlnpO6c=", + "dev": true, + "requires": { + "lodash._htmlescapes": "~2.4.1", + "lodash.keys": "~2.4.1" + }, + "dependencies": { + "lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "dev": true, + "requires": { + "lodash._isnative": "~2.4.1", + "lodash._shimkeys": "~2.4.1", + "lodash.isobject": "~2.4.1" + } + } + } + }, + "lodash._root": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", + "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", + "dev": true + }, + "lodash._shimkeys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash._shimkeys/-/lodash._shimkeys-2.4.1.tgz", + "integrity": "sha1-bpzJZm/wgfC1psl4uD4kLmlJ0gM=", + "dev": true, + "requires": { + "lodash._objecttypes": "~2.4.1" + } + }, + "lodash.defaults": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-2.4.1.tgz", + "integrity": "sha1-p+iIXwXmiFEUS24SqPNngCa8TFQ=", + "dev": true, + "requires": { + "lodash._objecttypes": "~2.4.1", + "lodash.keys": "~2.4.1" + }, + "dependencies": { + "lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "dev": true, + "requires": { + "lodash._isnative": "~2.4.1", + "lodash._shimkeys": "~2.4.1", + "lodash.isobject": "~2.4.1" + } + } + } + }, + "lodash.escape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", + "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", + "dev": true, + "requires": { + "lodash._root": "^3.0.0" + } + }, + "lodash.find": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/lodash.find/-/lodash.find-3.2.1.tgz", + "integrity": "sha1-BG4xnzrOkSrGySRsf2g8XsB7Nq0=", + "dev": true, + "requires": { + "lodash._basecallback": "^3.0.0", + "lodash._baseeach": "^3.0.0", + "lodash._basefind": "^3.0.0", + "lodash._basefindindex": "^3.0.0", + "lodash.isarray": "^3.0.0", + "lodash.keys": "^3.0.0" + } + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", + "dev": true + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", + "dev": true + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", + "dev": true + }, + "lodash.isobject": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-2.4.1.tgz", + "integrity": "sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU=", + "dev": true, + "requires": { + "lodash._objecttypes": "~2.4.1" + } + }, + "lodash.istypedarray": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz", + "integrity": "sha1-yaR3SYYHUB2OhJTSg7h8OSgc72I=", + "dev": true + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "dev": true, + "requires": { + "lodash._getnative": "^3.0.0", + "lodash.isarguments": "^3.0.0", + "lodash.isarray": "^3.0.0" + } + }, + "lodash.pairs": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash.pairs/-/lodash.pairs-3.0.1.tgz", + "integrity": "sha1-u+CNV4bu6qCaFckevw3LfSvjJqk=", + "dev": true, + "requires": { + "lodash.keys": "^3.0.0" + } + }, + "lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", + "dev": true + }, + "lodash.template": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", + "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", + "dev": true, + "requires": { + "lodash._basecopy": "^3.0.0", + "lodash._basetostring": "^3.0.0", + "lodash._basevalues": "^3.0.0", + "lodash._isiterateecall": "^3.0.0", + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0", + "lodash.keys": "^3.0.0", + "lodash.restparam": "^3.0.0", + "lodash.templatesettings": "^3.0.0" + } + }, + "lodash.templatesettings": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", + "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", + "dev": true, + "requires": { + "lodash._reinterpolate": "^3.0.0", + "lodash.escape": "^3.0.0" + } + }, + "lodash.values": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.values/-/lodash.values-2.4.1.tgz", + "integrity": "sha1-q/UUQ2s8twUAFieXjLzzCxKA7qQ=", + "dev": true, + "requires": { + "lodash.keys": "~2.4.1" + }, + "dependencies": { + "lodash.keys": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-2.4.1.tgz", + "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", + "dev": true, + "requires": { + "lodash._isnative": "~2.4.1", + "lodash._shimkeys": "~2.4.1", + "lodash.isobject": "~2.4.1" + } + } + } + }, + "lolex": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.7.0.tgz", + "integrity": "sha512-uJkH2e0BVfU5KOJUevbTOtpDduooSarH5PopO+LfM/vZf8Z9sJzODqKev804JYM2i++ktJfUmC1le4LwFQ1VMg==", + "dev": true + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true + }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, + "lru-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", + "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", + "dev": true, + "requires": { + "es5-ext": "~0.10.2" + } + }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "make-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "math-random": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz", + "integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w=", + "dev": true + }, + "md5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", + "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=", + "requires": { + "charenc": "~0.0.1", + "crypt": "~0.0.1", + "is-buffer": "~1.1.1" + } + }, + "md5.js": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", + "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", + "dev": true, + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "memoizee": { + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.12.tgz", + "integrity": "sha512-sprBu6nwxBWBvBOh5v2jcsGqiGLlL2xr2dLub3vR8dnE8YB17omwtm/0NSHl8jjNbcsJd5GMWJAnTSVe/O0Wfg==", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.30", + "es6-weak-map": "^2.0.2", + "event-emitter": "^0.3.5", + "is-promise": "^2.1", + "lru-queue": "0.1", + "next-tick": "1", + "timers-ext": "^0.1.2" + } + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + } + }, + "merge-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", + "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", + "dev": true, + "requires": { + "readable-stream": "^2.0.1" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + }, + "mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "requires": { + "mime-db": "~1.33.0" + } + }, + "mimic-response": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.0.tgz", + "integrity": "sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "multimatch": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz", + "integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=", + "dev": true, + "requires": { + "array-differ": "^1.0.0", + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "minimatch": "^3.0.0" + } + }, + "multipipe": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", + "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", + "dev": true, + "requires": { + "duplexer2": "0.0.2" + } + }, + "named-js-regexp": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/named-js-regexp/-/named-js-regexp-1.3.3.tgz", + "integrity": "sha512-zIUAXzGQOp16VR0Ct89SDstU62hzAPBluNUrUrsdD7MNSRbm/vyqGhEnp+4hnsMjmX3C2wh1cbIEP0joKMFLxw==" + }, + "nan": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", + "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", + "integrity": "sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-odd": "^2.0.0", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "natives": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.4.tgz", + "integrity": "sha512-Q29yeg9aFKwhLVdkTAejM/HvYG0Y1Am1+HUkFQGn5k2j8GS+v60TVmZh6nujpEAj/qql+wGUrlryO8bF+b1jEg==", + "dev": true + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "nise": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.3.3.tgz", + "integrity": "sha512-v1J/FLUB9PfGqZLGDBhQqODkbLotP0WtLo9R4EJY2PPu5f5Xg4o0rA8FDlmrjFSv9vBBKcfnOSpfYYuu5RTHqg==", + "dev": true, + "requires": { + "@sinonjs/formatio": "^2.0.0", + "just-extend": "^1.1.27", + "lolex": "^2.3.2", + "path-to-regexp": "^1.7.0", + "text-encoding": "^0.6.4" + } + }, + "node-has-native-dependencies": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/node-has-native-dependencies/-/node-has-native-dependencies-1.0.2.tgz", + "integrity": "sha1-MVLsl1O2ZB5NMi0YXdSTBkmto9o=", + "dev": true, + "requires": { + "fs-walk": "0.0.1" + } + }, + "node-stream-zip": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.6.0.tgz", + "integrity": "sha512-py/b/mLnyp/VvHCAl/Pqn6y+oLJrWpLYpLxJmGEAs1vxYDoAxgdbOzYgjpjEju/jrHzxUPurF+kT6KTfb+a4tA==" + }, + "node.extend": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/node.extend/-/node.extend-1.1.6.tgz", + "integrity": "sha1-p7iCyC1sk6SGOlUEvV3o7IYli5Y=", + "dev": true, + "requires": { + "is": "^3.1.0" + } + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "normalize-url": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", + "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", + "dev": true, + "requires": { + "prepend-http": "^2.0.0", + "query-string": "^5.0.1", + "sort-keys": "^2.0.0" + }, + "dependencies": { + "sort-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", + "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=", + "dev": true, + "requires": { + "is-plain-obj": "^1.0.0" + } + } + } + }, + "now-and-later": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.0.tgz", + "integrity": "sha1-vGHLtFbXnLMiB85HygUTb/Ln1u4=", + "dev": true, + "requires": { + "once": "^1.3.2" + } + }, + "npm-conf": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", + "integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==", + "dev": true, + "requires": { + "config-chain": "^1.1.11", + "pify": "^3.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-keys": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", + "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", + "dev": true, + "requires": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", + "dev": true, + "requires": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "dev": true, + "requires": { + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" + }, + "dependencies": { + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true, + "requires": { + "for-in": "^1.0.1" + } + } + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "opn": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", + "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", + "requires": { + "is-wsl": "^1.1.0" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + } + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + } + }, + "orchestrator": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/orchestrator/-/orchestrator-0.3.8.tgz", + "integrity": "sha1-FOfp4nZPcxX7rBhOUGx6pt+UrX4=", + "dev": true, + "requires": { + "end-of-stream": "~0.1.5", + "sequencify": "~0.0.7", + "stream-consume": "~0.1.0" + } + }, + "ordered-read-streams": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz", + "integrity": "sha1-/VZamvjrRHO6abbtijQ1LLVS8SY=", + "dev": true + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "p-cancelable": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", + "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==", + "dev": true + }, + "p-event": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-1.3.0.tgz", + "integrity": "sha1-jmtPT2XHK8W2/ii3XtqHT5akoIU=", + "dev": true, + "requires": { + "p-timeout": "^1.1.1" + }, + "dependencies": { + "p-timeout": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz", + "integrity": "sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=", + "dev": true, + "requires": { + "p-finally": "^1.0.0" + } + } + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=", + "dev": true + }, + "p-map": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", + "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", + "dev": true + }, + "p-timeout": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", + "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", + "dev": true, + "requires": { + "p-finally": "^1.0.0" + } + }, + "parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", + "dev": true, + "requires": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + } + }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "dev": true, + "requires": { + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" + }, + "dependencies": { + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "^1.0.0" + } + } + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-parse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "dev": true + }, + "path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", + "dev": true, + "requires": { + "path-root-regex": "^0.1.0" + } + }, + "path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", + "dev": true + }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "dev": true, + "requires": { + "isarray": "0.0.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } + } + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, + "pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "dev": true, + "requires": { + "through": "~2.3" + } + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "pidusage": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-1.2.0.tgz", + "integrity": "sha512-OGo+iSOk44HRJ8q15AyG570UYxcm5u+R99DI8Khu8P3tKGkVu5EZX4ywHglWSTMNNXQ274oeGpYrvFEhDIFGPg==" + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "plugin-error": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-0.1.2.tgz", + "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", + "dev": true, + "requires": { + "ansi-cyan": "^0.1.1", + "ansi-red": "^0.1.1", + "arr-diff": "^1.0.1", + "arr-union": "^2.0.1", + "extend-shallow": "^1.1.2" + }, + "dependencies": { + "arr-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", + "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", + "dev": true, + "requires": { + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" + } + }, + "arr-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", + "integrity": "sha1-IPnqtexw9cfSFbEHexw5Fh0pLH0=", + "dev": true + }, + "array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=", + "dev": true + }, + "extend-shallow": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-1.1.4.tgz", + "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", + "dev": true, + "requires": { + "kind-of": "^1.1.0" + } + }, + "kind-of": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz", + "integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=", + "dev": true + } + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "postinstall-build": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postinstall-build/-/postinstall-build-5.0.1.tgz", + "integrity": "sha1-uRepB5smF42aJK9aXNjLSpkdEbk=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", + "dev": true + }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", + "dev": true + }, + "pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", + "dev": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + }, + "dependencies": { + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + } + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "query-string": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", + "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", + "dev": true, + "requires": { + "decode-uri-component": "^0.2.0", + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + } + }, + "querystringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.0.0.tgz", + "integrity": "sha512-eTPo5t/4bgaMNZxyjWx6N2a6AuE0mq51KWvpc7nU/MAqixcI6v6KrGUKES0HaomdnolQBBXU/++X6/QQ9KL4tw==", + "dev": true + }, + "queue": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/queue/-/queue-3.1.0.tgz", + "integrity": "sha1-bEnQHwCeIlZ4h4nyv/rGuLmZBYU=", + "dev": true, + "requires": { + "inherits": "~2.0.0" + } + }, + "randomatic": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.0.0.tgz", + "integrity": "sha512-VdxFOIEY3mNO5PtSRkkle/hPJDHvQhK21oa73K4yAc9qmp6N429gAyF1gZMOTMeS0/AYzaV/2Trcef+NaIonSA==", + "dev": true, + "requires": { + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true + } + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "string_decoder": "~0.10.x", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", + "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "minimatch": "^3.0.2", + "readable-stream": "^2.0.2", + "set-immediate-shim": "^1.0.1" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, + "reflect-metadata": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.12.tgz", + "integrity": "sha512-n+IyV+nGz3+0q3/Yf1ra12KpCyi001bi4XFxSjbiWWjfqb52iTTtpGXmCCAOWWIAn9KEuFZKGqBERHmrtScZ3A==" + }, + "regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "dev": true, + "requires": { + "is-equal-shallow": "^0.1.3" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "relative": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/relative/-/relative-3.0.2.tgz", + "integrity": "sha1-Dc2OxUpdNaPBXhBFA9ZTdbWlNn8=", + "dev": true, + "requires": { + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "remap-istanbul": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/remap-istanbul/-/remap-istanbul-0.10.1.tgz", + "integrity": "sha512-gsNQXs5kJLhErICSyYhzVZ++C8LBW8dgwr874Y2QvzAUS75zBlD/juZgXs39nbYJ09fZDlX2AVLVJAY2jbFJoQ==", + "dev": true, + "requires": { + "amdefine": "^1.0.0", + "istanbul": "0.4.5", + "minimatch": "^3.0.3", + "plugin-error": "^0.1.2", + "source-map": "^0.6.1", + "through2": "2.0.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "through2": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.1.tgz", + "integrity": "sha1-OE51MU1J8y3hLuu4E2uOtrXVnak=", + "dev": true, + "requires": { + "readable-stream": "~2.0.0", + "xtend": "~4.0.0" + } + } + } + }, + "remove-bom-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", + "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5", + "is-utf8": "^0.2.1" + } + }, + "remove-bom-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz", + "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", + "dev": true, + "requires": { + "remove-bom-buffer": "^3.0.0", + "safe-buffer": "^5.1.0", + "through2": "^2.0.3" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", + "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "replace-ext": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", + "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", + "dev": true + }, + "request": { + "version": "2.85.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.85.0.tgz", + "integrity": "sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.6.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.1", + "forever-agent": "~0.6.1", + "form-data": "~2.3.1", + "har-validator": "~5.0.3", + "hawk": "~6.0.2", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.17", + "oauth-sign": "~0.8.2", + "performance-now": "^2.1.0", + "qs": "~6.5.1", + "safe-buffer": "^5.1.1", + "stringstream": "~0.0.5", + "tough-cookie": "~2.3.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.1.0" + } + }, + "request-progress": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", + "integrity": "sha1-TKdUCBx/7GP1BeT6qCWqBs1mnb4=", + "requires": { + "throttleit": "^1.0.0" + } + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "resolve": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", + "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==", + "dev": true, + "requires": { + "path-parse": "^1.0.5" + } + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + } + }, + "resolve-options": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-1.1.0.tgz", + "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", + "dev": true, + "requires": { + "value-or-function": "^3.0.0" + } + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "dev": true, + "requires": { + "lowercase-keys": "^1.0.0" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "retyped-diff-match-patch-tsd-ambient": { + "version": "1.0.0-1", + "resolved": "https://registry.npmjs.org/retyped-diff-match-patch-tsd-ambient/-/retyped-diff-match-patch-tsd-ambient-1.0.0-1.tgz", + "integrity": "sha1-Jkgr9JFcftn4MAu1y+xI/U/1vGI=", + "dev": true + }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dev": true, + "optional": true, + "requires": { + "align-text": "^0.1.1" + } + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "dev": true, + "requires": { + "glob": "^7.0.5" + } + }, + "rxjs": { + "version": "5.5.9", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.9.tgz", + "integrity": "sha512-DHG9AHmCmgaFWgjBcXp6NxFDmh3MvIA62GqTWmLnTzr/3oZ6h5hLD8NA+9j+GF0jEwklNIpI4KuuyLG8UWMEvQ==", + "requires": { + "symbol-observable": "1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "samsam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", + "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", + "dev": true + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "seek-bzip": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.5.tgz", + "integrity": "sha1-z+kXyz0nS8/6x5J1ivUxc+sfq9w=", + "dev": true, + "requires": { + "commander": "~2.8.1" + }, + "dependencies": { + "commander": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", + "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", + "dev": true, + "requires": { + "graceful-readlink": ">= 1.0.0" + } + } + } + }, + "semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" + }, + "sequencify": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/sequencify/-/sequencify-0.0.7.tgz", + "integrity": "sha1-kM/xnQLgcCf9dn9erT57ldHnOAw=", + "dev": true + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "shortid": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.8.tgz", + "integrity": "sha1-AzsRfWoul1gE9vCWnb59PQs1UTE=", + "dev": true + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "sinon": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-4.5.0.tgz", + "integrity": "sha512-trdx+mB0VBBgoYucy6a9L7/jfQOmvGeaKZT4OOJ+lPAtI8623xyGr8wLiE4eojzBS8G9yXbhx42GHUOVLr4X2w==", + "dev": true, + "requires": { + "@sinonjs/formatio": "^2.0.0", + "diff": "^3.1.0", + "lodash.get": "^4.4.2", + "lolex": "^2.2.0", + "nise": "^1.2.0", + "supports-color": "^5.1.0", + "type-detect": "^4.0.5" + }, + "dependencies": { + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "sntp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", + "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", + "requires": { + "hoek": "4.x.x" + } + }, + "sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", + "dev": true, + "requires": { + "is-plain-obj": "^1.0.0" + } + }, + "sort-keys-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", + "integrity": "sha1-nLb09OnkgVWmqgZx7dM2/xR5oYg=", + "dev": true, + "requires": { + "sort-keys": "^1.0.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.6.tgz", + "integrity": "sha512-N4KXEz7jcKqPf2b2vZF11lQIz9W5ZMuUcIOGj243lduidkf2fjkVKJS9vNxVWn3u/uxX38AcE8U9nnH9FPcq+g==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "sparkles": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.1.tgz", + "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", + "dev": true + }, + "spdx-correct": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", + "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", + "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", + "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", + "dev": true + }, + "split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", + "dev": true, + "requires": { + "through": "2" + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", + "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "tweetnacl": "~0.14.0" + } + }, + "stat-mode": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-0.2.2.tgz", + "integrity": "sha1-5sgLYjEj19gM8TLOU480YokHJQI=", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", + "dev": true, + "requires": { + "duplexer": "~0.1.1" + } + }, + "stream-consume": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/stream-consume/-/stream-consume-0.1.1.tgz", + "integrity": "sha512-tNa3hzgkjEP7XbCkbRXe1jpg+ievoa0O4SCFlMOYEscGSS4JJsckGL8swUyAa/ApGU3Ae4t6Honor4HhL+tRyg==", + "dev": true + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "dev": true + }, + "streamfilter": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/streamfilter/-/streamfilter-1.0.7.tgz", + "integrity": "sha512-Gk6KZM+yNA1JpW0KzlZIhjo3EaBJDkYfXtYSbOwNIQ7Zd6006E6+sCFlW1NDvFG/vnXhKmw6TJJgiEQg/8lXfQ==", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + } + }, + "streamifier": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/streamifier/-/streamifier-0.1.1.tgz", + "integrity": "sha1-l+mNj6TRBdYqJpHR3AfoINuN/E8=", + "dev": true + }, + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", + "dev": true + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "stringstream": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", + "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==" + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-1.0.0.tgz", + "integrity": "sha1-hbiGLzhEtabV7IRnqTWYFzo295Q=", + "dev": true, + "requires": { + "first-chunk-stream": "^1.0.0", + "is-utf8": "^0.2.0" + } + }, + "strip-bom-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz", + "integrity": "sha1-+H217yYT9paKpUWr/h7HKLaoKco=", + "dev": true, + "requires": { + "first-chunk-stream": "^2.0.0", + "strip-bom": "^2.0.0" + }, + "dependencies": { + "first-chunk-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz", + "integrity": "sha1-G97NuOCDwGZLkZRVgVd6Q6nzHXA=", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + } + } + }, + "strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha1-5SEekiQ2n7uB1jOi8ABE3IztrZI=", + "dev": true + }, + "strip-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", + "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", + "dev": true, + "requires": { + "is-natural-number": "^4.0.1" + } + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "^4.0.1" + } + }, + "strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, + "sudo-prompt": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-8.2.0.tgz", + "integrity": "sha512-n5Nv2lIZaWfVBg10EWC8yaJCB6xV7sEsuaISAVFIS9F4fTRjy/O35A82lkweKuSqQItDlKOGQpTHK9/udQhRRw==" + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "symbol-observable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", + "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=" + }, + "tar": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", + "dev": true, + "requires": { + "block-stream": "*", + "fstream": "^1.0.2", + "inherits": "2" + } + }, + "tar-stream": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.1.tgz", + "integrity": "sha512-IFLM5wp3QrJODQFPm6/to3LJZrONdBY/otxcvDIQzu217zKye6yVR3hhi9lAjrC2Z+m/j5oDxMPb1qcd8cIvpA==", + "dev": true, + "requires": { + "bl": "^1.0.0", + "buffer-alloc": "^1.1.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "text-encoding": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", + "dev": true + }, + "throttleit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", + "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=" + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", + "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "dev": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + }, + "dependencies": { + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "through2-filter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-2.0.0.tgz", + "integrity": "sha1-YLxVoNrLdghdsfna6Zq0P4PWIuw=", + "dev": true, + "requires": { + "through2": "~2.0.0", + "xtend": "~4.0.0" + } + }, + "tildify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tildify/-/tildify-1.2.0.tgz", + "integrity": "sha1-3OwD9V3Km3qj5bBPIYF+tW5jWIo=", + "dev": true, + "requires": { + "os-homedir": "^1.0.0" + } + }, + "time-stamp": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", + "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", + "dev": true + }, + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", + "dev": true + }, + "timers-ext": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.5.tgz", + "integrity": "sha512-tsEStd7kmACHENhsUPaxb8Jf8/+GZZxyNFQbZD07HQOyooOa6At1rQqjffgvg7n+dxscQa9cjjMdWhJtsP2sxg==", + "dev": true, + "requires": { + "es5-ext": "~0.10.14", + "next-tick": "1" + } + }, + "tmp": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", + "integrity": "sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA=", + "requires": { + "os-tmpdir": "~1.0.1" + } + }, + "to-absolute-glob": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", + "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", + "dev": true, + "requires": { + "is-absolute": "^1.0.0", + "is-negated-glob": "^1.0.0" + } + }, + "to-buffer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", + "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "to-through": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-2.0.0.tgz", + "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", + "dev": true, + "requires": { + "through2": "^2.0.3" + } + }, + "tough-cookie": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "requires": { + "punycode": "^1.4.1" + } + }, + "tree-kill": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.0.tgz", + "integrity": "sha512-DlX6dR0lOIRDFxI0mjL9IYg6OTncLm/Zt+JiBhE5OlFcAR8yc9S7FFXU9so0oda47frdM/JFsk7UjNt9vscKcg==" + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, + "tslib": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.1.tgz", + "integrity": "sha512-avfPS28HmGLLc2o4elcc2EIq2FcH++Yo5YxpBZi9Yw93BCTGFthI4HPE4Rpep6vSYQaK8e69PelM44tPj+RaQg==", + "dev": true + }, + "tslint": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.10.0.tgz", + "integrity": "sha1-EeJrzLiK+gLdDZlWyuPUVAtfVMM=", + "dev": true, + "requires": { + "babel-code-frame": "^6.22.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^3.2.0", + "glob": "^7.1.1", + "js-yaml": "^3.7.0", + "minimatch": "^3.0.4", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.12.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "tslint-eslint-rules": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/tslint-eslint-rules/-/tslint-eslint-rules-5.3.1.tgz", + "integrity": "sha512-qq2H/AU/FlFbQJKXuxhtIk+ni/nQu9jHHhsFKa6hnA0/n3zl1/RWRc3TVFlL8HfWFMzkST350VeTrFpy1u4OUg==", + "dev": true, + "requires": { + "doctrine": "0.7.2", + "tslib": "1.9.0", + "tsutils": "2.8.0" + }, + "dependencies": { + "tslib": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.0.tgz", + "integrity": "sha512-f/qGG2tUkrISBlQZEjEqoZ3B2+npJjIf04H1wuAv9iA8i04Icp+61KRXxFdha22670NJopsZCIjhC3SnjPRKrQ==", + "dev": true + }, + "tsutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.8.0.tgz", + "integrity": "sha1-AWAXNymzvxOGKN0UoVN+AIUdgUo=", + "dev": true, + "requires": { + "tslib": "^1.7.1" + } + } + } + }, + "tslint-microsoft-contrib": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/tslint-microsoft-contrib/-/tslint-microsoft-contrib-5.0.3.tgz", + "integrity": "sha512-5AnfTGlfpUzpRHLmoojPBKFTTmbjnwgdaTHMdllausa4GBPya5u36i9ddrTX4PhetGZvd4JUYIpAmgHqVnsctg==", + "dev": true, + "requires": { + "tsutils": "^2.12.1" + } + }, + "tsutils": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.27.1.tgz", + "integrity": "sha512-AE/7uzp32MmaHvNNFES85hhUDHFdFZp6OAiZcd6y4ZKKIg6orJTm8keYWBhIhrJQH3a4LzNKat7ZPXZt5aTf6w==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "optional": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "typemoq": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/typemoq/-/typemoq-2.1.0.tgz", + "integrity": "sha512-DtRNLb7x8yCTv/KHlwes+NI+aGb4Vl1iPC63Hhtcvk1DpxSAZzKWQv0RQFY0jX2Uqj0SDBNl8Na4e6MV6TNDgw==", + "dev": true, + "requires": { + "circular-json": "^0.3.1", + "lodash": "^4.17.4", + "postinstall-build": "^5.0.1" + } + }, + "typescript": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.1.tgz", + "integrity": "sha512-h6pM2f/GDchCFlldnriOhs1QHuwbnmj6/v7499eMHqPeW4V2G0elua2eIc2nu8v2NdHV0Gm+tzX83Hr6nUFjQA==", + "dev": true + }, + "typescript-char": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/typescript-char/-/typescript-char-0.0.0.tgz", + "integrity": "sha1-VY/tpzfHZaYQtzfu+7F3Xum8jas=" + }, + "typescript-formatter": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/typescript-formatter/-/typescript-formatter-7.2.0.tgz", + "integrity": "sha512-A16UqkHtkQOF340cf21LJXchcftyBTPqNOAmP1J8Plu2m3Q8o+2fAYwgFjLXMKP6ooSPjDoOS6z8j9q+1nEnXg==", + "dev": true, + "requires": { + "commandpost": "^1.0.0", + "editorconfig": "^0.15.0" + }, + "dependencies": { + "editorconfig": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.0.tgz", + "integrity": "sha512-j7JBoj/bpNzvoTQylfRZSc85MlLNKWQiq5y6gwKhmqD2h1eZ+tH4AXbkhEJD468gjDna/XMx2YtSkCxBRX9OGg==", + "dev": true, + "requires": { + "@types/commander": "^2.11.0", + "@types/semver": "^5.4.0", + "commander": "^2.11.0", + "lru-cache": "^4.1.1", + "semver": "^5.4.1", + "sigmund": "^1.0.1" + } + }, + "lru-cache": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", + "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + } + } + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dev": true, + "optional": true, + "requires": { + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true, + "optional": true + }, + "uint64be": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uint64be/-/uint64be-1.0.1.tgz", + "integrity": "sha1-H3FUIC8qG4rzU4cd2mUb80zpPpU=" + }, + "unbzip2-stream": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.2.5.tgz", + "integrity": "sha512-izD3jxT8xkzwtXRUZjtmRwKnZoeECrfZ8ra/ketwOcusbZEp4mjULMnJOCfTDZBgGQAAY1AJ/IgxcwkavcX9Og==", + "dev": true, + "requires": { + "buffer": "^3.0.1", + "through": "^2.3.6" + } + }, + "unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", + "dev": true + }, + "underscore": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", + "dev": true + }, + "unicode": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/unicode/-/unicode-10.0.0.tgz", + "integrity": "sha1-5dUcHbk7bHGguHngsMSvfm/faI4=" + }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + } + } + } + }, + "unique-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-1.0.0.tgz", + "integrity": "sha1-1ZpKdUJ0R9mqbJHnAmP40mpLEEs=", + "dev": true + }, + "universalify": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", + "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=" + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "untildify": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.2.tgz", + "integrity": "sha1-fx8wIFWz/qDz6B3HjrNnZstl4/E=" + }, + "upath": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", + "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", + "dev": true + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url-parse": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.0.tgz", + "integrity": "sha512-ERuGxDiQ6Xw/agN4tuoCRbmwRuZP0cJ1lJxJubXr5Q/5cDa78+Dc4wfvtxzhzhkm5VvmW6Mf8EVj9SPGN4l8Lg==", + "dev": true, + "requires": { + "querystringify": "^2.0.0", + "requires-port": "^1.0.0" + } + }, + "url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "dev": true, + "requires": { + "prepend-http": "^2.0.0" + } + }, + "url-to-options": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", + "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=", + "dev": true + }, + "urlgrey": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/urlgrey/-/urlgrey-0.4.4.tgz", + "integrity": "sha1-iS/pWWCAXoVRnxzUOJ8stMu3ZS8=", + "dev": true + }, + "use": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", + "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, + "user-home": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", + "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "uuid": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", + "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" + }, + "v8flags": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz", + "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", + "dev": true, + "requires": { + "user-home": "^1.1.1" + } + }, + "vali-date": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz", + "integrity": "sha1-G5BKWWCfsyjvB4E4Qgk09rhnCaY=", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", + "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validator": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/validator/-/validator-9.4.1.tgz", + "integrity": "sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA==", + "dev": true + }, + "value-or-function": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-3.0.0.tgz", + "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "vinyl": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", + "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", + "dev": true, + "requires": { + "clone": "^1.0.0", + "clone-stats": "^0.0.1", + "replace-ext": "0.0.1" + } + }, + "vinyl-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-file/-/vinyl-file-2.0.0.tgz", + "integrity": "sha1-p+v1/779obfRjRQPyweyI++2dRo=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.3.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0", + "strip-bom-stream": "^2.0.0", + "vinyl": "^1.1.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "vinyl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", + "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", + "dev": true, + "requires": { + "clone": "^1.0.0", + "clone-stats": "^0.0.1", + "replace-ext": "0.0.1" + } + } + } + }, + "vinyl-fs": { + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-0.3.14.tgz", + "integrity": "sha1-mmhRzhysHBzqX+hsCTHWIMLPqeY=", + "dev": true, + "requires": { + "defaults": "^1.0.0", + "glob-stream": "^3.1.5", + "glob-watcher": "^0.0.6", + "graceful-fs": "^3.0.0", + "mkdirp": "^0.5.0", + "strip-bom": "^1.0.0", + "through2": "^0.6.1", + "vinyl": "^0.4.0" + }, + "dependencies": { + "clone": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", + "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", + "dev": true + }, + "graceful-fs": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz", + "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", + "dev": true, + "requires": { + "natives": "^1.1.0" + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "through2": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", + "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", + "dev": true, + "requires": { + "readable-stream": ">=1.0.33-1 <1.1.0-0", + "xtend": ">=4.0.0 <4.1.0-0" + } + }, + "vinyl": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", + "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", + "dev": true, + "requires": { + "clone": "^0.2.0", + "clone-stats": "^0.0.1" + } + } + } + }, + "vinyl-source-stream": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vinyl-source-stream/-/vinyl-source-stream-1.1.2.tgz", + "integrity": "sha1-YrU6E1YQqJbpjKlr7jqH8Aio54A=", + "dev": true, + "requires": { + "through2": "^2.0.3", + "vinyl": "^0.4.3" + }, + "dependencies": { + "clone": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", + "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", + "dev": true + }, + "vinyl": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", + "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", + "dev": true, + "requires": { + "clone": "^0.2.0", + "clone-stats": "^0.0.1" + } + } + } + }, + "vinyl-sourcemap": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz", + "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", + "dev": true, + "requires": { + "append-buffer": "^1.0.2", + "convert-source-map": "^1.5.0", + "graceful-fs": "^4.1.6", + "normalize-path": "^2.1.1", + "now-and-later": "^2.0.0", + "remove-bom-buffer": "^3.0.0", + "vinyl": "^2.0.0" + }, + "dependencies": { + "clone": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", + "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true + }, + "vinyl": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.1.0.tgz", + "integrity": "sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=", + "dev": true, + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + } + } + }, + "vscode": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/vscode/-/vscode-1.1.18.tgz", + "integrity": "sha512-SyDw4qFwZ+WthZX7RWp71PNiWLF7VhpM65j2oryY/6jtSORd8qH6J8vclwWZJ6Jvu0EH7JamO2RWNfBfsMR9Zw==", + "dev": true, + "requires": { + "glob": "^7.1.2", + "gulp-chmod": "^2.0.0", + "gulp-filter": "^5.0.1", + "gulp-gunzip": "1.0.0", + "gulp-remote-src-vscode": "^0.5.0", + "gulp-symdest": "^1.1.0", + "gulp-untar": "^0.0.7", + "gulp-vinyl-zip": "^2.1.0", + "mocha": "^4.0.1", + "request": "^2.83.0", + "semver": "^5.4.1", + "source-map-support": "^0.5.0", + "url-parse": "^1.1.9", + "vinyl-source-stream": "^1.1.0" + }, + "dependencies": { + "browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, + "commander": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", + "dev": true + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "diff": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", + "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", + "dev": true + }, + "growl": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", + "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", + "dev": true + }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "mocha": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.1.0.tgz", + "integrity": "sha512-0RVnjg1HJsXY2YFDoTNzcc1NKhYuXKRrBAG2gDygmJJA136Cs2QlRliZG1mA0ap7cuaT30mw16luAeln+4RiNA==", + "dev": true, + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.11.0", + "debug": "3.1.0", + "diff": "3.3.1", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.3", + "he": "1.1.1", + "mkdirp": "0.5.1", + "supports-color": "4.4.0" + } + }, + "supports-color": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", + "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "dev": true, + "requires": { + "has-flag": "^2.0.0" + } + } + } + }, + "vscode-debugadapter": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/vscode-debugadapter/-/vscode-debugadapter-1.28.0.tgz", + "integrity": "sha512-GCR1326LFtfYjl7SDN1wmU2pBJ98HgUCnbWoU3s3bz0GhUWYN1xSYGg7MfuwxY6WwZk2cuqzANhy/oaKADMXaw==", + "requires": { + "vscode-debugprotocol": "1.28.0", + "vscode-uri": "1.0.1" + } + }, + "vscode-debugadapter-testsupport": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/vscode-debugadapter-testsupport/-/vscode-debugadapter-testsupport-1.29.0.tgz", + "integrity": "sha512-4P0h3gfe7MNw9FXx0k/TpKBJMA4s880Gu+puxuOHOY3txYpIGC1I2jQdPlWt0XPWK2Qcz7q/biQ221cfavqifw==", + "dev": true, + "requires": { + "vscode-debugprotocol": "1.29.0" + }, + "dependencies": { + "vscode-debugprotocol": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.29.0.tgz", + "integrity": "sha512-jrbSayWof7jyXo7VRhIcTcsjWeiPloi6vzbrucVarKvuSrZUV7Bc+ggQRSG1lzNiMmBG5AHIe/Npf6G2q4SBiw==", + "dev": true + } + } + }, + "vscode-debugprotocol": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.28.0.tgz", + "integrity": "sha512-QM4J8A13jBY9I7OPWXN0ZO1cqydnD4co2j/O81jIj6em8VkmJT4VyJQkq4HmwJe3af+u9+7IYCIEDrowgvKxTA==" + }, + "vscode-extension-telemetry": { + "version": "0.0.15", + "resolved": "https://registry.npmjs.org/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.15.tgz", + "integrity": "sha512-Yf6dL9r2x2GISI1xh22XsAaydSTQG/4aBitu8sGBwGr42n2TyOsIXGtXSDgqQBNZgYD6+P1EHqrrzetn9ekWTQ==", + "requires": { + "applicationinsights": "1.0.1" + } + }, + "vscode-jsonrpc": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz", + "integrity": "sha1-hyOdnhZrLXNSJFuKgTWXgEwdY6o=" + }, + "vscode-languageclient": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-3.5.1.tgz", + "integrity": "sha512-GTQ+hSq/o4c/y6GYmyP9XNrVoIu0NFZ67KltSkqN+tO0eUNDIlrVNX+3DJzzyLhSsrctuGzuYWm3t87mNAcBmQ==", + "requires": { + "vscode-languageserver-protocol": "3.5.1" + } + }, + "vscode-languageserver": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-3.5.1.tgz", + "integrity": "sha512-RYUKn0DgHTFcS8kS4VaNCjNMaQXYqiXdN9bKrFjXzu5RPKfjIYcoh47oVWwZj4L3R/DPB0Se7HPaDatvYY2XgQ==", + "requires": { + "vscode-languageserver-protocol": "3.5.1", + "vscode-uri": "^1.0.1" + } + }, + "vscode-languageserver-protocol": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.1.tgz", + "integrity": "sha512-1fPDIwsAv1difCV+8daOrJEGunClNJWqnUHq/ncWrjhitKWXgGmRCjlwZ3gDUTt54yRcvXz1PXJDaRNvNH6pYA==", + "requires": { + "vscode-jsonrpc": "3.5.0", + "vscode-languageserver-types": "3.5.0" + } + }, + "vscode-languageserver-types": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz", + "integrity": "sha1-5I15li8LjgLelV4/UkkI4rGcA3Q=" + }, + "vscode-uri": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.1.tgz", + "integrity": "sha1-Eahr7+rDxKo+wIYjZRo8gabQu8g=" + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true, + "optional": true + }, + "winreg": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.4.tgz", + "integrity": "sha1-ugZWKbepJRMOFXeRCM9UCZDpjRs=" + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "optional": true, + "requires": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + }, + "dependencies": { + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true, + "optional": true + } + } + }, + "yauzl": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.9.1.tgz", + "integrity": "sha1-qBmB6nCleUYTOIPwKcWCGok1mn8=", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.0.1" + } + }, + "yazl": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.4.3.tgz", + "integrity": "sha1-7CblzIfVYBud+EMtvdPNLlFzoHE=", + "dev": true, + "requires": { + "buffer-crc32": "~0.2.3" + } + }, + "zone.js": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.7.6.tgz", + "integrity": "sha1-+7w50+AmHQmG8boGMG6zrrDSIAk=" + } + } +} diff --git a/package.json b/package.json index 1dabfa1e8ac0..cf1c76791481 100644 --- a/package.json +++ b/package.json @@ -1915,7 +1915,7 @@ "@types/lodash": "^4.14.104", "@types/md5": "^2.1.32", "@types/mocha": "^2.2.48", - "@types/node": "^9.4.7", + "@types/node": "9.4.7", "@types/request": "^2.47.0", "@types/semver": "^5.5.0", "@types/shortid": "^0.0.29", diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index 8426a5bbe67e..000000000000 --- a/yarn.lock +++ /dev/null @@ -1,5605 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@gulp-sourcemaps/identity-map@1.X": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@gulp-sourcemaps/identity-map/-/identity-map-1.0.1.tgz#cfa23bc5840f9104ce32a65e74db7e7a974bbee1" - dependencies: - acorn "^5.0.3" - css "^2.2.1" - normalize-path "^2.1.1" - source-map "^0.5.6" - through2 "^2.0.3" - -"@gulp-sourcemaps/map-sources@1.X": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@gulp-sourcemaps/map-sources/-/map-sources-1.0.0.tgz#890ae7c5d8c877f6d384860215ace9d7ec945bda" - dependencies: - normalize-path "^2.0.1" - through2 "^2.0.3" - -"@sindresorhus/is@^0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" - -"@sinonjs/formatio@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-2.0.0.tgz#84db7e9eb5531df18a8c5e0bfb6e449e55e654b2" - dependencies: - samsam "1.3.0" - -"@types/caseless@*": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.1.tgz#9794c69c8385d0192acc471a540d1f8e0d16218a" - -"@types/chai-arrays@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@types/chai-arrays/-/chai-arrays-1.0.2.tgz#1f89c183c960334c47d9f24105195c4326db0cc7" - dependencies: - "@types/chai" "*" - -"@types/chai-as-promised@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.0.tgz#010b04cde78eacfb6e72bfddb3e58fe23c2e78b9" - dependencies: - "@types/chai" "*" - -"@types/chai@*", "@types/chai@^4.1.2": - version "4.1.2" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.1.2.tgz#f1af664769cfb50af805431c407425ed619daa21" - -"@types/commander@^2.11.0": - version "2.12.2" - resolved "https://registry.yarnpkg.com/@types/commander/-/commander-2.12.2.tgz#183041a23842d4281478fa5d23c5ca78e6fd08ae" - dependencies: - commander "*" - -"@types/decompress@*": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@types/decompress/-/decompress-4.2.2.tgz#38a299e981862a898e5ac84eb1adc9329a0bad56" - dependencies: - "@types/node" "*" - -"@types/del@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/del/-/del-3.0.0.tgz#1c8cd8b6e38da3b572352ca8eaf5527931426288" - dependencies: - "@types/glob" "*" - -"@types/download@^6.2.2": - version "6.2.2" - resolved "https://registry.yarnpkg.com/@types/download/-/download-6.2.2.tgz#11385a3fd6e1f3300362fb52db119420ecc0fb34" - dependencies: - "@types/decompress" "*" - "@types/got" "*" - -"@types/dotenv@^4.0.3": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/dotenv/-/dotenv-4.0.3.tgz#ebcfc40da7bc0728b705945b7db48485ec5b4b67" - dependencies: - "@types/node" "*" - -"@types/event-stream@^3.3.33": - version "3.3.33" - resolved "https://registry.yarnpkg.com/@types/event-stream/-/event-stream-3.3.33.tgz#ca155f6e805b606175322c03e6d75bc3b940cb95" - dependencies: - "@types/node" "*" - -"@types/events@*": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" - -"@types/form-data@*": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-2.2.1.tgz#ee2b3b8eaa11c0938289953606b745b738c54b1e" - dependencies: - "@types/node" "*" - -"@types/fs-extra@^5.0.1": - version "5.0.1" - resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.0.1.tgz#cd856fbbdd6af2c11f26f8928fd8644c9e9616c9" - dependencies: - "@types/node" "*" - -"@types/get-port@^3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@types/get-port/-/get-port-3.2.0.tgz#f9e0a11443cc21336470185eae3dfba4495d29bc" - -"@types/glob@*", "@types/glob@^5.0.35": - version "5.0.35" - resolved "https://registry.yarnpkg.com/@types/glob/-/glob-5.0.35.tgz#1ae151c802cece940443b5ac246925c85189f32a" - dependencies: - "@types/events" "*" - "@types/minimatch" "*" - "@types/node" "*" - -"@types/got@*": - version "8.3.1" - resolved "https://registry.yarnpkg.com/@types/got/-/got-8.3.1.tgz#823ded0ce469895be3d01553fec31ff86339ff9f" - dependencies: - "@types/node" "*" - -"@types/iconv-lite@^0.0.1": - version "0.0.1" - resolved "https://registry.yarnpkg.com/@types/iconv-lite/-/iconv-lite-0.0.1.tgz#aa3b8bda2be512b1ae0a057b942e869c370a5569" - dependencies: - "@types/node" "*" - -"@types/istanbul@^0.4.29": - version "0.4.29" - resolved "https://registry.yarnpkg.com/@types/istanbul/-/istanbul-0.4.29.tgz#29c8cbb747ac57280965545dc58514ba0dbb99af" - -"@types/lodash@^4.14.104": - version "4.14.104" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.104.tgz#53ee2357fa2e6e68379341d92eb2ecea4b11bb80" - -"@types/md5@^2.1.32": - version "2.1.32" - resolved "https://registry.yarnpkg.com/@types/md5/-/md5-2.1.32.tgz#93e23437fcd17a7b9ca98d02aa6002e835842fe8" - dependencies: - "@types/node" "*" - -"@types/minimatch@*": - version "3.0.3" - resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" - -"@types/mocha@^2.2.48": - version "2.2.48" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.48.tgz#3523b126a0b049482e1c3c11877460f76622ffab" - -"@types/node@*", "@types/node@^9.4.7": - version "9.4.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-9.4.7.tgz#57d81cd98719df2c9de118f2d5f3b1120dcd7275" - -"@types/request@^2.47.0": - version "2.47.0" - resolved "https://registry.yarnpkg.com/@types/request/-/request-2.47.0.tgz#76a666cee4cb85dcffea6cd4645227926d9e114e" - dependencies: - "@types/caseless" "*" - "@types/form-data" "*" - "@types/node" "*" - "@types/tough-cookie" "*" - -"@types/semver@^5.4.0", "@types/semver@^5.5.0": - version "5.5.0" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45" - -"@types/shortid@^0.0.29": - version "0.0.29" - resolved "https://registry.yarnpkg.com/@types/shortid/-/shortid-0.0.29.tgz#8093ee0416a6e2bf2aa6338109114b3fbffa0e9b" - -"@types/sinon@^4.3.0": - version "4.3.0" - resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-4.3.0.tgz#7f53915994a00ccea24f4e0c24709822ed11a3b1" - -"@types/tough-cookie@*": - version "2.3.3" - resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.3.tgz#7f226d67d654ec9070e755f46daebf014628e9d9" - -"@types/untildify@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/untildify/-/untildify-3.0.0.tgz#cd3e6624e46ccf292d3823fb48fa90dda0deaec0" - -"@types/uuid@^3.4.3": - version "3.4.3" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.3.tgz#121ace265f5569ce40f4f6d0ff78a338c732a754" - dependencies: - "@types/node" "*" - -"@types/winreg@^1.2.30": - version "1.2.30" - resolved "https://registry.yarnpkg.com/@types/winreg/-/winreg-1.2.30.tgz#91d6710e536d345b9c9b017c574cf6a8da64c518" - -"@types/xml2js@^0.4.2": - version "0.4.2" - resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.2.tgz#a4b84b3879ffd4710953fd92cabfde9a8a4e8456" - dependencies: - "@types/node" "*" - -JSONStream@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.2.tgz#c102371b6ec3a7cf3b847ca00c20bb0fce4c6dea" - dependencies: - jsonparse "^1.2.0" - through ">=2.2.7 <3" - -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - -abbrev@1.0.x: - version "1.0.9" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" - -acorn@5.X, acorn@^5.0.3: - version "5.5.3" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.5.3.tgz#f473dd47e0277a08e28e9bec5aeeb04751f0b8c9" - -ajv@^4.9.1: - version "4.11.8" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" - dependencies: - co "^4.6.0" - json-stable-stringify "^1.0.1" - -ajv@^5.1.0: - version "5.5.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" - dependencies: - co "^4.6.0" - fast-deep-equal "^1.0.0" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.3.0" - -align-text@^0.1.1, align-text@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" - dependencies: - kind-of "^3.0.2" - longest "^1.0.1" - repeat-string "^1.5.2" - -amdefine@>=0.0.4, amdefine@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" - -ansi-colors@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-1.1.0.tgz#6374b4dd5d4718ff3ce27a671a3b1cad077132a9" - dependencies: - ansi-wrap "^0.1.0" - -ansi-cyan@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ansi-cyan/-/ansi-cyan-0.1.1.tgz#538ae528af8982f28ae30d86f2f17456d2609873" - dependencies: - ansi-wrap "0.1.0" - -ansi-gray@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ansi-gray/-/ansi-gray-0.1.1.tgz#2962cf54ec9792c48510a3deb524436861ef7251" - dependencies: - ansi-wrap "0.1.0" - -ansi-red@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ansi-red/-/ansi-red-0.1.1.tgz#8c638f9d1080800a353c9c28c8a81ca4705d946c" - dependencies: - ansi-wrap "0.1.0" - -ansi-regex@^0.2.0, ansi-regex@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-0.2.1.tgz#0d8e946967a3d8143f93e24e298525fc1b2235f9" - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - -ansi-styles@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.1.0.tgz#eaecbf66cd706882760b2f4691582b8f55d7a7de" - -ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - dependencies: - color-convert "^1.9.0" - -ansi-wrap@0.1.0, ansi-wrap@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" - -anymatch@^1.3.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" - dependencies: - micromatch "^2.1.5" - normalize-path "^2.0.0" - -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" - dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" - -append-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/append-buffer/-/append-buffer-1.0.2.tgz#d8220cf466081525efea50614f3de6514dfa58f1" - dependencies: - buffer-equal "^1.0.0" - -applicationinsights@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.1.tgz#53446b830fe8d5d619eee2a278b31d3d25030927" - dependencies: - diagnostic-channel "0.2.0" - diagnostic-channel-publishers "0.2.1" - zone.js "0.7.6" - -aproba@^1.0.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - -arch@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/arch/-/arch-2.1.0.tgz#3613aa46149064b3c1f0607919bf1d4786e82889" - -archy@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" - -are-we-there-yet@~1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d" - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - dependencies: - sprintf-js "~1.0.2" - -argv@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/argv/-/argv-0.0.2.tgz#ecbd16f8949b157183711b1bda334f37840185ab" - -arr-diff@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-1.1.0.tgz#687c32758163588fef7de7b36fabe495eb1a399a" - dependencies: - arr-flatten "^1.0.1" - array-slice "^0.2.3" - -arr-diff@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" - dependencies: - arr-flatten "^1.0.1" - -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - -arr-flatten@^1.0.1, arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - -arr-union@^2.0.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-2.1.0.tgz#20f9eab5ec70f5c7d215b1077b1c39161d292c7d" - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - -array-differ@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031" - -array-each@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" - -array-find-index@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" - -array-slice@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5" - -array-slice@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-1.1.0.tgz#e368ea15f89bc7069f7ffb89aec3a6c7d4ac22d4" - -array-union@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" - dependencies: - array-uniq "^1.0.1" - -array-uniq@^1.0.1, array-uniq@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" - -array-unique@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" - -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - -arrify@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" - -asn1@~0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" - -assert-plus@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" - -assertion-error@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" - -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - -async-each@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" - -async@*: - version "2.6.0" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4" - dependencies: - lodash "^4.14.0" - -async@1.x, async@^1.4.0: - version "1.5.2" - resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - -atob@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.0.3.tgz#19c7a760473774468f20b2d2d03372ad7d4cbf5d" - -atob@~1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/atob/-/atob-1.1.3.tgz#95f13629b12c3a51a5d215abdce2aa9f32f80773" - -aws-sign2@~0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" - -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" - -aws4@^1.2.1, aws4@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" - -azure-storage@^2.8.1: - version "2.8.1" - resolved "https://registry.yarnpkg.com/azure-storage/-/azure-storage-2.8.1.tgz#ecb9d050ef1395e79ffbb652c02fe643687bec63" - dependencies: - browserify-mime "~1.2.9" - extend "~1.2.1" - json-edm-parser "0.1.2" - md5.js "1.3.4" - readable-stream "~2.0.0" - request "~2.83.0" - underscore "~1.8.3" - uuid "^3.0.0" - validator "~9.4.1" - xml2js "0.2.8" - xmlbuilder "0.4.3" - -babel-code-frame@^6.22.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" - dependencies: - chalk "^1.1.3" - esutils "^2.0.2" - js-tokens "^3.0.2" - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - -base64-js@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978" - -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - -bcrypt-pbkdf@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" - dependencies: - tweetnacl "^0.14.3" - -beeper@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/beeper/-/beeper-1.1.1.tgz#e6d5ea8c5dad001304a70b22638447f69cb2f809" - -binary-extensions@^1.0.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205" - -bl@^1.0.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c" - dependencies: - readable-stream "^2.3.5" - safe-buffer "^5.1.1" - -block-stream@*: - version "0.0.9" - resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" - dependencies: - inherits "~2.0.0" - -bluebird@^3.0.5: - version "3.5.1" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" - -boom@2.x.x: - version "2.10.1" - resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" - dependencies: - hoek "2.x.x" - -boom@4.x.x: - version "4.3.1" - resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31" - dependencies: - hoek "4.x.x" - -boom@5.x.x: - version "5.2.0" - resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02" - dependencies: - hoek "4.x.x" - -brace-expansion@^1.0.0, brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^1.8.2: - version "1.8.5" - resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" - dependencies: - expand-range "^1.8.1" - preserve "^0.2.0" - repeat-element "^1.1.2" - -braces@^2.3.0, braces@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.1.tgz#7086c913b4e5a08dbe37ac0ee6a2500c4ba691bb" - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - define-property "^1.0.0" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - kind-of "^6.0.2" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - -browser-stdout@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" - -browser-stdout@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" - -browserify-mime@~1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/browserify-mime/-/browserify-mime-1.2.9.tgz#aeb1af28de6c0d7a6a2ce40adb68ff18422af31f" - -buffer-alloc-unsafe@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" - -buffer-alloc@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec" - dependencies: - buffer-alloc-unsafe "^1.1.0" - buffer-fill "^1.0.0" - -buffer-crc32@~0.2.3: - version "0.2.13" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - -buffer-equal@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" - -buffer-fill@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" - -buffer@^3.0.1: - version "3.6.0" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-3.6.0.tgz#a72c936f77b96bf52f5f7e7b467180628551defb" - dependencies: - base64-js "0.0.8" - ieee754 "^1.1.4" - isarray "^1.0.0" - -builtin-modules@^1.0.0, builtin-modules@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" - -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - -cacheable-request@^2.1.1: - version "2.1.4" - resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d" - dependencies: - clone-response "1.0.2" - get-stream "3.0.0" - http-cache-semantics "3.8.1" - keyv "3.0.0" - lowercase-keys "1.0.0" - normalize-url "2.0.1" - responselike "1.0.2" - -callsite@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" - -camelcase-keys@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" - dependencies: - camelcase "^2.0.0" - map-obj "^1.0.0" - -camelcase@^1.0.2: - version "1.2.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" - -camelcase@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" - -caseless@~0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" - -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" - -caw@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/caw/-/caw-2.0.1.tgz#6c3ca071fc194720883c2dc5da9b074bfc7e9e95" - dependencies: - get-proxy "^2.0.0" - isurl "^1.0.0-alpha5" - tunnel-agent "^0.6.0" - url-to-options "^1.0.1" - -center-align@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" - dependencies: - align-text "^0.1.3" - lazy-cache "^1.0.3" - -chai-arrays@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/chai-arrays/-/chai-arrays-2.0.0.tgz#d95820d1b39dc2e4abaa01f984b7f9123986a7cc" - -chai-as-promised@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/chai-as-promised/-/chai-as-promised-7.1.1.tgz#08645d825deb8696ee61725dbf590c012eb00ca0" - dependencies: - check-error "^1.0.2" - -chai@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chai/-/chai-4.1.2.tgz#0f64584ba642f0f2ace2806279f4f06ca23ad73c" - dependencies: - assertion-error "^1.0.1" - check-error "^1.0.1" - deep-eql "^3.0.0" - get-func-name "^2.0.0" - pathval "^1.0.0" - type-detect "^4.0.0" - -chalk@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.5.1.tgz#663b3a648b68b55d04690d49167aa837858f2174" - dependencies: - ansi-styles "^1.1.0" - escape-string-regexp "^1.0.0" - has-ansi "^0.1.0" - strip-ansi "^0.3.0" - supports-color "^0.2.0" - -chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" - -chalk@^2.3.0: - version "2.3.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.2.tgz#250dc96b07491bfd601e648d66ddf5f60c7a5c65" - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -charenc@~0.0.1: - version "0.0.2" - resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" - -check-error@^1.0.1, check-error@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" - -chokidar@^1.6.1: - version "1.7.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" - dependencies: - anymatch "^1.3.0" - async-each "^1.0.0" - glob-parent "^2.0.0" - inherits "^2.0.1" - is-binary-path "^1.0.0" - is-glob "^2.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.0.0" - optionalDependencies: - fsevents "^1.0.0" - -chokidar@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.2.tgz#4dc65139eeb2714977735b6a35d06e97b494dfd7" - dependencies: - anymatch "^2.0.0" - async-each "^1.0.0" - braces "^2.3.0" - glob-parent "^3.1.0" - inherits "^2.0.1" - is-binary-path "^1.0.0" - is-glob "^4.0.0" - normalize-path "^2.1.1" - path-is-absolute "^1.0.0" - readdirp "^2.0.0" - upath "^1.0.0" - optionalDependencies: - fsevents "^1.0.0" - -ci-info@^1.0.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.3.tgz#710193264bb05c77b8c90d02f5aaf22216a667b2" - -circular-json@^0.3.1: - version "0.3.3" - resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" - -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - -cliui@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" - dependencies: - center-align "^0.1.1" - right-align "^0.1.1" - wordwrap "0.0.2" - -clone-buffer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" - -clone-response@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" - dependencies: - mimic-response "^1.0.0" - -clone-stats@^0.0.1, clone-stats@~0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1" - -clone-stats@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" - -clone@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/clone/-/clone-0.2.0.tgz#c6126a90ad4f72dbf5acdb243cc37724fe93fc1f" - -clone@^1.0.0, clone@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.3.tgz#298d7e2231660f40c003c2ed3140decf3f53085f" - -clone@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.1.tgz#d217d1e961118e3ac9a4b8bba3285553bf647cdb" - -cloneable-readable@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.1.1.tgz#c27a4f3a943ca37bed9b01c7d572ee61b1302b15" - dependencies: - inherits "^2.0.1" - process-nextick-args "^2.0.0" - readable-stream "^2.3.5" - -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - -codecov@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/codecov/-/codecov-3.0.0.tgz#c273b8c4f12945723e8dc9d25803d89343e5f28e" - dependencies: - argv "0.0.2" - request "2.81.0" - urlgrey "0.4.4" - -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - -color-convert@^1.9.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed" - dependencies: - color-name "^1.1.1" - -color-name@^1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - -color-support@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" - -colors@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.1.tgz#f4a3d302976aaf042356ba1ade3b1a2c62d9d794" - -combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" - dependencies: - delayed-stream "~1.0.0" - -commander@*, commander@^2.11.0, commander@^2.12.1, commander@^2.9.0: - version "2.15.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.0.tgz#ad2a23a1c3b036e392469b8012cec6b33b4c1322" - -commander@2.11.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" - -commander@~2.8.1: - version "2.8.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4" - dependencies: - graceful-readlink ">= 1.0.0" - -commandpost@^1.0.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/commandpost/-/commandpost-1.3.0.tgz#e0654e4933abf58406c7d3b77ce747083da178c4" - -component-emitter@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - -config-chain@^1.1.11, config-chain@~1.1.5: - version "1.1.11" - resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.11.tgz#aba09747dfbe4c3e70e766a6e41586e1859fc6f2" - dependencies: - ini "^1.3.4" - proto-list "~1.2.1" - -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - -content-disposition@^0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" - -convert-source-map@1.X, convert-source-map@^1.1.1, convert-source-map@^1.5.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" - -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - -core-util-is@1.0.2, core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - -crypt@~0.0.1: - version "0.0.2" - resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" - -cryptiles@2.x.x: - version "2.0.5" - resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" - dependencies: - boom "2.x.x" - -cryptiles@3.x.x: - version "3.1.2" - resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe" - dependencies: - boom "5.x.x" - -css@2.X, css@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/css/-/css-2.2.1.tgz#73a4c81de85db664d4ee674f7d47085e3b2d55dc" - dependencies: - inherits "^2.0.1" - source-map "^0.1.38" - source-map-resolve "^0.3.0" - urix "^0.1.0" - -currently-unhandled@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" - dependencies: - array-find-index "^1.0.1" - -d@1: - version "1.0.0" - resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" - dependencies: - es5-ext "^0.10.9" - -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" - dependencies: - assert-plus "^1.0.0" - -dateformat@^1.0.7-1.2.3: - version "1.0.12" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-1.0.12.tgz#9f124b67594c937ff706932e4a642cca8dbbfee9" - dependencies: - get-stdin "^4.0.1" - meow "^3.3.0" - -dateformat@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.2.0.tgz#4065e2013cf9fb916ddfd82efb506ad4c6769062" - -debounce-hashed@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/debounce-hashed/-/debounce-hashed-0.1.2.tgz#a0dfe307c1a7db30f6911c8cfbc0ef841f37d4af" - -debounce@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.1.0.tgz#6a1a4ee2a9dc4b7c24bb012558dbcdb05b37f408" - -debug-fabulous@1.X: - version "1.0.0" - resolved "https://registry.yarnpkg.com/debug-fabulous/-/debug-fabulous-1.0.0.tgz#57f6648646097b1b0849dcda0017362c1ec00f8b" - dependencies: - debug "3.X" - memoizee "0.4.X" - object-assign "4.X" - -debug@3.1.0, debug@3.X: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - dependencies: - ms "2.0.0" - -debug@^2.2.0, debug@^2.3.3: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - dependencies: - ms "2.0.0" - -decache@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/decache/-/decache-4.4.0.tgz#6f6df6b85d7e7c4410a932ffc26489b78e9acd13" - dependencies: - callsite "^1.0.0" - -decamelize@^1.0.0, decamelize@^1.1.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - -decompress-response@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" - dependencies: - mimic-response "^1.0.0" - -decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1" - dependencies: - file-type "^5.2.0" - is-stream "^1.1.0" - tar-stream "^1.5.2" - -decompress-tarbz2@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz#3082a5b880ea4043816349f378b56c516be1a39b" - dependencies: - decompress-tar "^4.1.0" - file-type "^6.1.0" - is-stream "^1.1.0" - seek-bzip "^1.0.5" - unbzip2-stream "^1.0.9" - -decompress-targz@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/decompress-targz/-/decompress-targz-4.1.1.tgz#c09bc35c4d11f3de09f2d2da53e9de23e7ce1eee" - dependencies: - decompress-tar "^4.1.1" - file-type "^5.2.0" - is-stream "^1.1.0" - -decompress-unzip@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69" - dependencies: - file-type "^3.8.0" - get-stream "^2.2.0" - pify "^2.3.0" - yauzl "^2.4.2" - -decompress@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/decompress/-/decompress-4.2.0.tgz#7aedd85427e5a92dacfe55674a7c505e96d01f9d" - dependencies: - decompress-tar "^4.0.0" - decompress-tarbz2 "^4.0.0" - decompress-targz "^4.0.0" - decompress-unzip "^4.0.1" - graceful-fs "^4.1.10" - make-dir "^1.0.0" - pify "^2.3.0" - strip-dirs "^2.0.0" - -deep-assign@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/deep-assign/-/deep-assign-1.0.0.tgz#b092743be8427dc621ea0067cdec7e70dd19f37b" - dependencies: - is-obj "^1.0.0" - -deep-eql@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" - dependencies: - type-detect "^4.0.0" - -deep-extend@~0.4.0: - version "0.4.2" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" - -deep-is@~0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" - -deepmerge@^2.0.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.1.0.tgz#511a54fff405fc346f0240bb270a3e9533a31102" - -defaults@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" - dependencies: - clone "^1.0.2" - -define-properties@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" - dependencies: - foreach "^2.0.5" - object-keys "^1.0.8" - -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - -del@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/del/-/del-3.0.0.tgz#53ecf699ffcbcb39637691ab13baf160819766e5" - dependencies: - globby "^6.1.0" - is-path-cwd "^1.0.0" - is-path-in-cwd "^1.0.0" - p-map "^1.1.1" - pify "^3.0.0" - rimraf "^2.2.8" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - -deprecated@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/deprecated/-/deprecated-0.0.1.tgz#f9c9af5464afa1e7a971458a8bdef2aa94d5bb19" - -detect-file@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" - -detect-indent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" - -detect-libc@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - -detect-newline@2.X: - version "2.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" - -diagnostic-channel-publishers@0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3" - -diagnostic-channel@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17" - dependencies: - semver "^5.3.0" - -diff-match-patch@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.0.tgz#1cc3c83a490d67f95d91e39f6ad1f2e086b63048" - -diff@3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75" - -diff@3.5.0, diff@^3.1.0, diff@^3.2.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" - -doctrine@0.7.2: - version "0.7.2" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-0.7.2.tgz#7cb860359ba3be90e040b26b729ce4bfa654c523" - dependencies: - esutils "^1.1.6" - isarray "0.0.1" - -download@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/download/-/download-7.0.0.tgz#8711f09174b11d8ded14840d1881e3d05190195c" - dependencies: - caw "^2.0.1" - content-disposition "^0.5.2" - decompress "^4.2.0" - ext-name "^5.0.0" - file-type "^7.7.1" - filenamify "^2.0.0" - get-stream "^3.0.0" - got "^8.3.1" - make-dir "^1.2.0" - p-event "^1.3.0" - pify "^3.0.0" - -dotenv@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-5.0.1.tgz#a5317459bd3d79ab88cff6e44057a6a3fbb1fcef" - -duplexer2@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db" - dependencies: - readable-stream "~1.1.9" - -duplexer3@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" - -duplexer@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" - -duplexify@^3.2.0, duplexify@^3.5.3: - version "3.5.4" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.4.tgz#4bb46c1796eabebeec4ca9a2e66b808cb7a3d8b4" - dependencies: - end-of-stream "^1.0.0" - inherits "^2.0.1" - readable-stream "^2.0.0" - stream-shift "^1.0.0" - -ecc-jsbn@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" - dependencies: - jsbn "~0.1.0" - -editorconfig@^0.13.2: - version "0.13.3" - resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.13.3.tgz#e5219e587951d60958fd94ea9a9a008cdeff1b34" - dependencies: - bluebird "^3.0.5" - commander "^2.9.0" - lru-cache "^3.2.0" - semver "^5.1.0" - sigmund "^1.0.1" - -editorconfig@^0.15.0: - version "0.15.0" - resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.0.tgz#b6dd4a0b6b9e76ce48e066bdc15381aebb8804fd" - dependencies: - "@types/commander" "^2.11.0" - "@types/semver" "^5.4.0" - commander "^2.11.0" - lru-cache "^4.1.1" - semver "^5.4.1" - sigmund "^1.0.1" - -end-of-stream@^1.0.0, end-of-stream@^1.1.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" - dependencies: - once "^1.4.0" - -end-of-stream@~0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-0.1.5.tgz#8e177206c3c80837d85632e8b9359dfe8b2f6eaf" - dependencies: - once "~1.3.0" - -error-ex@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" - dependencies: - is-arrayish "^0.2.1" - -es5-ext@^0.10.14, es5-ext@^0.10.30, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14, es5-ext@~0.10.2: - version "0.10.40" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.40.tgz#ab3d2179b943008c5e9ef241beb25ef41424c774" - dependencies: - es6-iterator "~2.0.3" - es6-symbol "~3.1.1" - -es6-iterator@^2.0.1, es6-iterator@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" - dependencies: - d "1" - es5-ext "^0.10.35" - es6-symbol "^3.1.1" - -es6-symbol@^3.1.1, es6-symbol@~3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" - dependencies: - d "1" - es5-ext "~0.10.14" - -es6-weak-map@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.2.tgz#5e3ab32251ffd1538a1f8e5ffa1357772f92d96f" - dependencies: - d "1" - es5-ext "^0.10.14" - es6-iterator "^2.0.1" - es6-symbol "^3.1.1" - -escape-string-regexp@1.0.5, escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - -escodegen@1.8.x: - version "1.8.1" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018" - dependencies: - esprima "^2.7.1" - estraverse "^1.9.1" - esutils "^2.0.2" - optionator "^0.8.1" - optionalDependencies: - source-map "~0.2.0" - -esprima@2.7.x, esprima@^2.7.1: - version "2.7.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" - -esprima@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" - -estraverse@^1.9.1: - version "1.9.3" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44" - -esutils@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-1.1.6.tgz#c01ccaa9ae4b897c6d0c3e210ae52f3c7a844375" - -esutils@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" - -event-emitter@^0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" - dependencies: - d "1" - es5-ext "~0.10.14" - -event-stream@^3.3.1, event-stream@^3.3.4, event-stream@~3.3.4: - version "3.3.4" - resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571" - dependencies: - duplexer "~0.1.1" - from "~0" - map-stream "~0.1.0" - pause-stream "0.0.11" - split "0.3" - stream-combiner "~0.0.4" - through "~2.3.1" - -expand-brackets@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" - dependencies: - is-posix-bracket "^0.1.0" - -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -expand-range@^1.8.1: - version "1.8.2" - resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" - dependencies: - fill-range "^2.1.0" - -expand-tilde@^2.0.0, expand-tilde@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" - dependencies: - homedir-polyfill "^1.0.1" - -ext-list@^2.0.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/ext-list/-/ext-list-2.2.2.tgz#0b98e64ed82f5acf0f2931babf69212ef52ddd37" - dependencies: - mime-db "^1.28.0" - -ext-name@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ext-name/-/ext-name-5.0.0.tgz#70781981d183ee15d13993c8822045c506c8f0a6" - dependencies: - ext-list "^2.0.0" - sort-keys-length "^1.0.0" - -extend-shallow@^1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-1.1.4.tgz#19d6bf94dfc09d76ba711f39b872d21ff4dd9071" - dependencies: - kind-of "^1.1.0" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - -extend@^3.0.0, extend@~3.0.0, extend@~3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" - -extend@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/extend/-/extend-1.2.1.tgz#a0f5fd6cfc83a5fe49ef698d60ec8a624dd4576c" - -extglob@^0.3.1: - version "0.3.2" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" - dependencies: - is-extglob "^1.0.0" - -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -extsprintf@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" - -extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" - -fancy-log@^1.1.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.2.tgz#f41125e3d84f2e7d89a43d06d958c8f78be16be1" - dependencies: - ansi-gray "^0.1.1" - color-support "^1.1.3" - time-stamp "^1.0.0" - -fast-deep-equal@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" - -fast-json-stable-stringify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" - -fast-levenshtein@~2.0.4: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - -fd-slicer@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65" - dependencies: - pend "~1.2.0" - -file-type@^3.8.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9" - -file-type@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6" - -file-type@^6.1.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919" - -file-type@^7.7.1: - version "7.7.1" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-7.7.1.tgz#91c2f5edb8ce70688b9b68a90d931bbb6cb21f65" - -filename-regex@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" - -filename-reserved-regex@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229" - -filenamify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-2.0.0.tgz#bd162262c0b6e94bfbcdcf19a3bbb3764f785695" - dependencies: - filename-reserved-regex "^2.0.0" - strip-outer "^1.0.0" - trim-repeated "^1.0.0" - -fill-range@^2.1.0: - version "2.2.3" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" - dependencies: - is-number "^2.1.0" - isobject "^2.0.0" - randomatic "^1.1.3" - repeat-element "^1.1.2" - repeat-string "^1.5.2" - -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - -find-index@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/find-index/-/find-index-0.1.1.tgz#675d358b2ca3892d795a1ab47232f8b6e2e0dde4" - -find-up@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" - dependencies: - path-exists "^2.0.0" - pinkie-promise "^2.0.0" - -findup-sync@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc" - dependencies: - detect-file "^1.0.0" - is-glob "^3.1.0" - micromatch "^3.0.4" - resolve-dir "^1.0.1" - -fined@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fined/-/fined-1.1.0.tgz#b37dc844b76a2f5e7081e884f7c0ae344f153476" - dependencies: - expand-tilde "^2.0.2" - is-plain-object "^2.0.3" - object.defaults "^1.1.0" - object.pick "^1.2.0" - parse-filepath "^1.0.1" - -first-chunk-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz#59bfb50cd905f60d7c394cd3d9acaab4e6ad934e" - -first-chunk-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz#1bdecdb8e083c0664b91945581577a43a9f31d70" - dependencies: - readable-stream "^2.0.2" - -flagged-respawn@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-1.0.0.tgz#4e79ae9b2eb38bf86b3bb56bf3e0a56aa5fcabd7" - -flat@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/flat/-/flat-4.0.0.tgz#3abc7f3b588e64ce77dc42fd59aa35806622fea8" - dependencies: - is-buffer "~1.1.5" - -flush-write-stream@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.2.tgz#c81b90d8746766f1a609a46809946c45dd8ae417" - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.4" - -for-in@^1.0.1, for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - -for-own@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" - dependencies: - for-in "^1.0.1" - -for-own@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b" - dependencies: - for-in "^1.0.1" - -foreach@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" - -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" - -form-data@~2.1.1: - version "2.1.4" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.5" - mime-types "^2.1.12" - -form-data@~2.3.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" - dependencies: - asynckit "^0.4.0" - combined-stream "1.0.6" - mime-types "^2.1.12" - -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - dependencies: - map-cache "^0.2.2" - -from2@^2.1.1: - version "2.3.0" - resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.0" - -from@~0: - version "0.1.7" - resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" - -fs-constants@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" - -fs-extra@4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" - dependencies: - graceful-fs "^4.1.2" - jsonfile "^4.0.0" - universalify "^0.1.0" - -fs-mkdirp-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz#0b7815fc3201c6a69e14db98ce098c16935259eb" - dependencies: - graceful-fs "^4.1.11" - through2 "^2.0.3" - -fs-walk@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/fs-walk/-/fs-walk-0.0.1.tgz#f7fc91c3ae1eead07c998bc5d0dd41f2dbebd335" - dependencies: - async "*" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - -fsevents@^1.0.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.3.tgz#11f82318f5fe7bb2cd22965a108e9306208216d8" - dependencies: - nan "^2.3.0" - node-pre-gyp "^0.6.39" - -fstream-ignore@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105" - dependencies: - fstream "^1.0.0" - inherits "2" - minimatch "^3.0.0" - -fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: - version "1.0.11" - resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" - dependencies: - graceful-fs "^4.1.2" - inherits "~2.0.0" - mkdirp ">=0.5 0" - rimraf "2" - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - -fuzzy@0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/fuzzy/-/fuzzy-0.1.3.tgz#4c76ec2ff0ac1a36a9dccf9a00df8623078d4ed8" - -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - -gaze@^0.5.1: - version "0.5.2" - resolved "https://registry.yarnpkg.com/gaze/-/gaze-0.5.2.tgz#40b709537d24d1d45767db5a908689dfe69ac44f" - dependencies: - globule "~0.1.0" - -generate-function@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74" - -generate-object-property@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" - dependencies: - is-property "^1.0.0" - -get-func-name@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" - -get-port@3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.2.0.tgz#dd7ce7de187c06c8bf353796ac71e099f0980ebc" - -get-proxy@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/get-proxy/-/get-proxy-2.1.0.tgz#349f2b4d91d44c4d4d4e9cba2ad90143fac5ef93" - dependencies: - npm-conf "^1.1.0" - -get-stdin@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" - -get-stream@3.0.0, get-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" - -get-stream@^2.2.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" - dependencies: - object-assign "^4.0.1" - pinkie-promise "^2.0.0" - -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" - dependencies: - assert-plus "^1.0.0" - -glob-base@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" - dependencies: - glob-parent "^2.0.0" - is-glob "^2.0.0" - -glob-parent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" - dependencies: - is-glob "^2.0.0" - -glob-parent@^3.0.0, glob-parent@^3.0.1, glob-parent@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" - dependencies: - is-glob "^3.1.0" - path-dirname "^1.0.0" - -glob-stream@^3.1.5: - version "3.1.18" - resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-3.1.18.tgz#9170a5f12b790306fdfe598f313f8f7954fd143b" - dependencies: - glob "^4.3.1" - glob2base "^0.0.12" - minimatch "^2.0.1" - ordered-read-streams "^0.1.0" - through2 "^0.6.1" - unique-stream "^1.0.0" - -glob-stream@^5.3.2: - version "5.3.5" - resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-5.3.5.tgz#a55665a9a8ccdc41915a87c701e32d4e016fad22" - dependencies: - extend "^3.0.0" - glob "^5.0.3" - glob-parent "^3.0.0" - micromatch "^2.3.7" - ordered-read-streams "^0.3.0" - through2 "^0.6.0" - to-absolute-glob "^0.1.1" - unique-stream "^2.0.2" - -glob-stream@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-6.1.0.tgz#7045c99413b3eb94888d83ab46d0b404cc7bdde4" - dependencies: - extend "^3.0.0" - glob "^7.1.1" - glob-parent "^3.1.0" - is-negated-glob "^1.0.0" - ordered-read-streams "^1.0.0" - pumpify "^1.3.5" - readable-stream "^2.1.5" - remove-trailing-separator "^1.0.1" - to-absolute-glob "^2.0.0" - unique-stream "^2.0.2" - -glob-watcher@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-0.0.6.tgz#b95b4a8df74b39c83298b0c05c978b4d9a3b710b" - dependencies: - gaze "^0.5.1" - -glob2base@^0.0.12: - version "0.0.12" - resolved "https://registry.yarnpkg.com/glob2base/-/glob2base-0.0.12.tgz#9d419b3e28f12e83a362164a277055922c9c0d56" - dependencies: - find-index "^0.1.1" - -glob@7.1.2, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^4.3.1: - version "4.5.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-4.5.3.tgz#c6cb73d3226c1efef04de3c56d012f03377ee15f" - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "^2.0.1" - once "^1.3.0" - -glob@^5.0.15, glob@^5.0.3: - version "5.0.15" - resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "2 || 3" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@~3.1.21: - version "3.1.21" - resolved "https://registry.yarnpkg.com/glob/-/glob-3.1.21.tgz#d29e0a055dea5138f4d07ed40e8982e83c2066cd" - dependencies: - graceful-fs "~1.2.0" - inherits "1" - minimatch "~0.2.11" - -global-modules@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" - dependencies: - global-prefix "^1.0.1" - is-windows "^1.0.1" - resolve-dir "^1.0.0" - -global-prefix@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" - dependencies: - expand-tilde "^2.0.2" - homedir-polyfill "^1.0.1" - ini "^1.3.4" - is-windows "^1.0.1" - which "^1.2.14" - -globby@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" - dependencies: - array-union "^1.0.1" - glob "^7.0.3" - object-assign "^4.0.1" - pify "^2.0.0" - pinkie-promise "^2.0.0" - -globule@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/globule/-/globule-0.1.0.tgz#d9c8edde1da79d125a151b79533b978676346ae5" - dependencies: - glob "~3.1.21" - lodash "~1.0.1" - minimatch "~0.2.11" - -glogg@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.1.tgz#dcf758e44789cc3f3d32c1f3562a3676e6a34810" - dependencies: - sparkles "^1.0.0" - -got@^8.3.1: - version "8.3.1" - resolved "https://registry.yarnpkg.com/got/-/got-8.3.1.tgz#093324403d4d955f5a16a7a8d39955d055ae10ed" - dependencies: - "@sindresorhus/is" "^0.7.0" - cacheable-request "^2.1.1" - decompress-response "^3.3.0" - duplexer3 "^0.1.4" - get-stream "^3.0.0" - into-stream "^3.1.0" - is-retry-allowed "^1.1.0" - isurl "^1.0.0-alpha5" - lowercase-keys "^1.0.0" - mimic-response "^1.0.0" - p-cancelable "^0.4.0" - p-timeout "^2.0.1" - pify "^3.0.0" - safe-buffer "^5.1.1" - timed-out "^4.0.1" - url-parse-lax "^3.0.0" - url-to-options "^1.0.1" - -graceful-fs@4.X, graceful-fs@^4.0.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6: - version "4.1.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" - -graceful-fs@^3.0.0: - version "3.0.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-3.0.11.tgz#7613c778a1afea62f25c630a086d7f3acbbdd818" - dependencies: - natives "^1.1.0" - -graceful-fs@~1.2.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-1.2.3.tgz#15a4806a57547cb2d2dbf27f42e89a8c3451b364" - -"graceful-readlink@>= 1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" - -growl@1.10.3: - version "1.10.3" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.3.tgz#1926ba90cf3edfe2adb4927f5880bc22c66c790f" - -gulp-chmod@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/gulp-chmod/-/gulp-chmod-2.0.0.tgz#00c390b928a0799b251accf631aa09e01cc6299c" - dependencies: - deep-assign "^1.0.0" - stat-mode "^0.2.0" - through2 "^2.0.0" - -gulp-debounced-watch@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/gulp-debounced-watch/-/gulp-debounced-watch-1.0.4.tgz#5a47d4e24ce46365ce82ecac32a2a303e432b12a" - dependencies: - debounce-hashed "^0.1.1" - gulp-watch "^4.3.4" - object-assign "^3.0.0" - -gulp-filter@^5.0.1, gulp-filter@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/gulp-filter/-/gulp-filter-5.1.0.tgz#a05e11affb07cf7dcf41a7de1cb7b63ac3783e73" - dependencies: - multimatch "^2.0.0" - plugin-error "^0.1.2" - streamfilter "^1.0.5" - -gulp-gitmodified@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/gulp-gitmodified/-/gulp-gitmodified-1.1.1.tgz#85f3675915c1d51b66807f28de0ebb591e7e9df4" - dependencies: - gulp-util "~2.2.12" - lodash.find "^3.2.1" - through2 "^2.0.0" - vinyl "^0.4.3" - which "~1.0.5" - -gulp-gunzip@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/gulp-gunzip/-/gulp-gunzip-1.0.0.tgz#15b741145e83a9c6f50886241b57cc5871f151a9" - dependencies: - through2 "~0.6.5" - vinyl "~0.4.6" - -gulp-json-editor@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/gulp-json-editor/-/gulp-json-editor-2.2.2.tgz#8ae627a083a95d86d14d9bffe0527e906d0e61ee" - dependencies: - deepmerge "^2.0.1" - detect-indent "^5.0.0" - js-beautify "^1.7.5" - plugin-error "^1.0.1" - through2 "^2.0.3" - -gulp-remote-src@^0.4.3: - version "0.4.3" - resolved "https://registry.yarnpkg.com/gulp-remote-src/-/gulp-remote-src-0.4.3.tgz#5728cfd643433dd4845ddef0969f0f971a2ab4a1" - dependencies: - event-stream "~3.3.4" - node.extend "~1.1.2" - request "~2.79.0" - through2 "~2.0.3" - vinyl "~2.0.1" - -gulp-sourcemaps@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/gulp-sourcemaps/-/gulp-sourcemaps-1.6.0.tgz#b86ff349d801ceb56e1d9e7dc7bbcb4b7dee600c" - dependencies: - convert-source-map "^1.1.1" - graceful-fs "^4.1.2" - strip-bom "^2.0.0" - through2 "^2.0.0" - vinyl "^1.0.0" - -gulp-sourcemaps@^2.6.4: - version "2.6.4" - resolved "https://registry.yarnpkg.com/gulp-sourcemaps/-/gulp-sourcemaps-2.6.4.tgz#cbb2008450b1bcce6cd23bf98337be751bf6e30a" - dependencies: - "@gulp-sourcemaps/identity-map" "1.X" - "@gulp-sourcemaps/map-sources" "1.X" - acorn "5.X" - convert-source-map "1.X" - css "2.X" - debug-fabulous "1.X" - detect-newline "2.X" - graceful-fs "4.X" - source-map "~0.6.0" - strip-bom-string "1.X" - through2 "2.X" - -gulp-symdest@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/gulp-symdest/-/gulp-symdest-1.1.0.tgz#c165320732d192ce56fd94271ffa123234bf2ae0" - dependencies: - event-stream "^3.3.1" - mkdirp "^0.5.1" - queue "^3.1.0" - vinyl-fs "^2.4.3" - -gulp-typescript@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/gulp-typescript/-/gulp-typescript-4.0.1.tgz#fd9d2e06a06ea3c1c15885b82ebfb037c07d75b2" - dependencies: - ansi-colors "^1.0.1" - plugin-error "^0.1.2" - source-map "^0.6.1" - through2 "^2.0.3" - vinyl "^2.1.0" - vinyl-fs "^3.0.0" - -gulp-untar@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/gulp-untar/-/gulp-untar-0.0.6.tgz#d6bdefde7e9a8e054c9f162385a0782c4be74000" - dependencies: - event-stream "~3.3.4" - gulp-util "~3.0.8" - streamifier "~0.1.1" - tar "^2.2.1" - through2 "~2.0.3" - -gulp-util@^3.0.0, gulp-util@^3.0.7, gulp-util@~3.0.8: - version "3.0.8" - resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f" - dependencies: - array-differ "^1.0.0" - array-uniq "^1.0.2" - beeper "^1.0.0" - chalk "^1.0.0" - dateformat "^2.0.0" - fancy-log "^1.1.0" - gulplog "^1.0.0" - has-gulplog "^0.1.0" - lodash._reescape "^3.0.0" - lodash._reevaluate "^3.0.0" - lodash._reinterpolate "^3.0.0" - lodash.template "^3.0.0" - minimist "^1.1.0" - multipipe "^0.1.2" - object-assign "^3.0.0" - replace-ext "0.0.1" - through2 "^2.0.0" - vinyl "^0.5.0" - -gulp-util@~2.2.12: - version "2.2.20" - resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-2.2.20.tgz#d7146e5728910bd8f047a6b0b1e549bc22dbd64c" - dependencies: - chalk "^0.5.0" - dateformat "^1.0.7-1.2.3" - lodash._reinterpolate "^2.4.1" - lodash.template "^2.4.1" - minimist "^0.2.0" - multipipe "^0.1.0" - through2 "^0.5.0" - vinyl "^0.2.1" - -gulp-vinyl-zip@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/gulp-vinyl-zip/-/gulp-vinyl-zip-2.1.0.tgz#24e40685dc05b7149995245099e0590263be8dad" - dependencies: - event-stream "^3.3.1" - queue "^4.2.1" - through2 "^2.0.3" - vinyl "^2.0.2" - vinyl-fs "^2.0.0" - yauzl "^2.2.1" - yazl "^2.2.1" - -gulp-watch@^4.3.4: - version "4.3.11" - resolved "https://registry.yarnpkg.com/gulp-watch/-/gulp-watch-4.3.11.tgz#162fc563de9fc770e91f9a7ce3955513a9a118c0" - dependencies: - anymatch "^1.3.0" - chokidar "^1.6.1" - glob-parent "^3.0.1" - gulp-util "^3.0.7" - object-assign "^4.1.0" - path-is-absolute "^1.0.1" - readable-stream "^2.2.2" - slash "^1.0.0" - vinyl "^1.2.0" - vinyl-file "^2.0.0" - -gulp-watch@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/gulp-watch/-/gulp-watch-5.0.0.tgz#6fb03ab1735972e0d2866475b568555836dfd0eb" - dependencies: - anymatch "^1.3.0" - chokidar "^2.0.0" - glob-parent "^3.0.1" - gulp-util "^3.0.7" - object-assign "^4.1.0" - path-is-absolute "^1.0.1" - readable-stream "^2.2.2" - slash "^1.0.0" - vinyl "^2.1.0" - vinyl-file "^2.0.0" - -gulp@^3.9.1: - version "3.9.1" - resolved "https://registry.yarnpkg.com/gulp/-/gulp-3.9.1.tgz#571ce45928dd40af6514fc4011866016c13845b4" - dependencies: - archy "^1.0.0" - chalk "^1.0.0" - deprecated "^0.0.1" - gulp-util "^3.0.0" - interpret "^1.0.0" - liftoff "^2.1.0" - minimist "^1.1.0" - orchestrator "^0.3.0" - pretty-hrtime "^1.0.0" - semver "^4.1.0" - tildify "^1.0.0" - v8flags "^2.0.2" - vinyl-fs "^0.3.0" - -gulplog@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/gulplog/-/gulplog-1.0.0.tgz#e28c4d45d05ecbbed818363ce8f9c5926229ffe5" - dependencies: - glogg "^1.0.0" - -handlebars@^4.0.1: - version "4.0.11" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc" - dependencies: - async "^1.4.0" - optimist "^0.6.1" - source-map "^0.4.4" - optionalDependencies: - uglify-js "^2.6" - -har-schema@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" - -har-schema@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" - -har-validator@~2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d" - dependencies: - chalk "^1.1.1" - commander "^2.9.0" - is-my-json-valid "^2.12.4" - pinkie-promise "^2.0.0" - -har-validator@~4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" - dependencies: - ajv "^4.9.1" - har-schema "^1.0.5" - -har-validator@~5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" - dependencies: - ajv "^5.1.0" - har-schema "^2.0.0" - -has-ansi@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-0.1.0.tgz#84f265aae8c0e6a88a12d7022894b7568894c62e" - dependencies: - ansi-regex "^0.2.0" - -has-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - dependencies: - ansi-regex "^2.0.0" - -has-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" - -has-flag@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - -has-gulplog@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/has-gulplog/-/has-gulplog-0.1.0.tgz#6414c82913697da51590397dafb12f22967811ce" - dependencies: - sparkles "^1.0.0" - -has-symbol-support-x@^1.4.1: - version "1.4.2" - resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455" - -has-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" - -has-to-string-tag-x@^1.2.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d" - dependencies: - has-symbol-support-x "^1.4.1" - -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - -hash-base@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -hawk@3.1.3, hawk@~3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" - dependencies: - boom "2.x.x" - cryptiles "2.x.x" - hoek "2.x.x" - sntp "1.x.x" - -hawk@~6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038" - dependencies: - boom "4.x.x" - cryptiles "3.x.x" - hoek "4.x.x" - sntp "2.x.x" - -he@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" - -hoek@2.x.x: - version "2.16.3" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" - -hoek@4.x.x: - version "4.2.1" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb" - -homedir-polyfill@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc" - dependencies: - parse-passwd "^1.0.0" - -hosted-git-info@^2.1.4: - version "2.6.0" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.6.0.tgz#23235b29ab230c576aab0d4f13fc046b0b038222" - -http-cache-semantics@3.8.1: - version "3.8.1" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" - -http-signature@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" - dependencies: - assert-plus "^0.2.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - -http-signature@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" - dependencies: - assert-plus "^1.0.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - -husky@^0.14.3: - version "0.14.3" - resolved "https://registry.yarnpkg.com/husky/-/husky-0.14.3.tgz#c69ed74e2d2779769a17ba8399b54ce0b63c12c3" - dependencies: - is-ci "^1.0.10" - normalize-path "^1.0.0" - strip-indent "^2.0.0" - -iconv-lite@0.4.21: - version "0.4.21" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.21.tgz#c47f8733d02171189ebc4a400f3218d348094798" - dependencies: - safer-buffer "^2.1.0" - -ieee754@^1.1.4: - version "1.1.11" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.11.tgz#c16384ffe00f5b7835824e67b6f2bd44a5229455" - -indent-string@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" - dependencies: - repeating "^2.0.0" - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-1.0.2.tgz#ca4309dadee6b54cc0b8d247e8d7c7a0975bdc9b" - -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - -ini@^1.3.4, ini@~1.3.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - -interpret@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" - -into-stream@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6" - dependencies: - from2 "^2.1.1" - p-is-promise "^1.1.0" - -inversify@4.11.1: - version "4.11.1" - resolved "https://registry.yarnpkg.com/inversify/-/inversify-4.11.1.tgz#9a10635d1fd347da11da96475b3608babd5945a6" - -is-absolute@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" - dependencies: - is-relative "^1.0.0" - is-windows "^1.0.1" - -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - dependencies: - kind-of "^3.0.2" - -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - dependencies: - kind-of "^6.0.0" - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - -is-binary-path@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" - dependencies: - binary-extensions "^1.0.0" - -is-buffer@^1.1.5, is-buffer@~1.1.1, is-buffer@~1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - -is-builtin-module@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" - dependencies: - builtin-modules "^1.0.0" - -is-ci@^1.0.10: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.1.0.tgz#247e4162e7860cebbdaf30b774d6b0ac7dcfe7a5" - dependencies: - ci-info "^1.0.0" - -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - dependencies: - kind-of "^6.0.0" - -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - -is-dotfile@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" - -is-equal-shallow@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" - dependencies: - is-primitive "^2.0.0" - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - dependencies: - is-plain-object "^2.0.4" - -is-extglob@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" - -is-extglob@^2.1.0, is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - -is-finite@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" - dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - dependencies: - number-is-nan "^1.0.0" - -is-glob@^2.0.0, is-glob@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" - dependencies: - is-extglob "^1.0.0" - -is-glob@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" - dependencies: - is-extglob "^2.1.0" - -is-glob@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" - dependencies: - is-extglob "^2.1.1" - -is-my-ip-valid@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824" - -is-my-json-valid@^2.12.4: - version "2.17.2" - resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz#6b2103a288e94ef3de5cf15d29dd85fc4b78d65c" - dependencies: - generate-function "^2.0.0" - generate-object-property "^1.1.0" - is-my-ip-valid "^1.0.0" - jsonpointer "^4.0.0" - xtend "^4.0.0" - -is-natural-number@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" - -is-negated-glob@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" - -is-number@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" - dependencies: - kind-of "^3.0.2" - -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - dependencies: - kind-of "^3.0.2" - -is-number@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" - -is-obj@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" - -is-object@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" - -is-odd@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-2.0.0.tgz#7646624671fd7ea558ccd9a2795182f2958f1b24" - dependencies: - is-number "^4.0.0" - -is-path-cwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" - -is-path-in-cwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc" - dependencies: - is-path-inside "^1.0.0" - -is-path-inside@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" - dependencies: - path-is-inside "^1.0.1" - -is-plain-obj@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" - -is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - dependencies: - isobject "^3.0.1" - -is-posix-bracket@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" - -is-primitive@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" - -is-promise@^2.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" - -is-property@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" - -is-relative@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" - dependencies: - is-unc-path "^1.0.0" - -is-retry-allowed@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" - -is-running@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-running/-/is-running-2.1.0.tgz#30a73ff5cc3854e4fc25490809e9f5abf8de09e0" - -is-stream@^1.0.1, is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - -is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - -is-unc-path@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d" - dependencies: - unc-path-regex "^0.1.2" - -is-utf8@^0.2.0, is-utf8@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" - -is-valid-glob@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-0.3.0.tgz#d4b55c69f51886f9b65c70d6c2622d37e29f48fe" - -is-valid-glob@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-1.0.0.tgz#29bf3eff701be2d4d315dbacc39bc39fe8f601aa" - -is-windows@^1.0.1, is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - -is-wsl@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" - -is@^3.1.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/is/-/is-3.2.1.tgz#d0ac2ad55eb7b0bec926a5266f6c662aaa83dca5" - -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - -isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - -istanbul@0.4.5, istanbul@^0.4.5: - version "0.4.5" - resolved "https://registry.yarnpkg.com/istanbul/-/istanbul-0.4.5.tgz#65c7d73d4c4da84d4f3ac310b918fb0b8033733b" - dependencies: - abbrev "1.0.x" - async "1.x" - escodegen "1.8.x" - esprima "2.7.x" - glob "^5.0.15" - handlebars "^4.0.1" - js-yaml "3.x" - mkdirp "0.5.x" - nopt "3.x" - once "1.x" - resolve "1.1.x" - supports-color "^3.1.0" - which "^1.1.1" - wordwrap "^1.0.0" - -isurl@^1.0.0-alpha5: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67" - dependencies: - has-to-string-tag-x "^1.2.0" - is-object "^1.0.1" - -js-beautify@^1.7.5: - version "1.7.5" - resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.7.5.tgz#69d9651ef60dbb649f65527b53674950138a7919" - dependencies: - config-chain "~1.1.5" - editorconfig "^0.13.2" - mkdirp "~0.5.0" - nopt "~3.0.1" - -js-tokens@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" - -js-yaml@3.x, js-yaml@^3.7.0: - version "3.11.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef" - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" - -json-buffer@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" - -json-edm-parser@0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/json-edm-parser/-/json-edm-parser-0.1.2.tgz#1e60b0fef1bc0af67bc0d146dfdde5486cd615b4" - dependencies: - jsonparse "~1.2.0" - -json-schema-traverse@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" - -json-schema@0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" - -json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" - dependencies: - jsonify "~0.0.0" - -json-stringify-safe@~5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - -jsonfile@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - optionalDependencies: - graceful-fs "^4.1.6" - -jsonify@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" - -jsonparse@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" - -jsonparse@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.2.0.tgz#5c0c5685107160e72fe7489bddea0b44c2bc67bd" - -jsonpointer@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" - -jsprim@^1.2.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.2.3" - verror "1.10.0" - -just-extend@^1.1.27: - version "1.1.27" - resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-1.1.27.tgz#ec6e79410ff914e472652abfa0e603c03d60e905" - -keyv@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.0.0.tgz#44923ba39e68b12a7cec7df6c3268c031f2ef373" - dependencies: - json-buffer "3.0.0" - -kind-of@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-1.1.0.tgz#140a3d2d41a36d2efcfa9377b62c24f8495a5c44" - -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.1.0, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" - -lazy-cache@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" - -lazystream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" - dependencies: - readable-stream "^2.0.5" - -lead@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lead/-/lead-1.0.0.tgz#6f14f99a37be3a9dd784f5495690e5903466ee42" - dependencies: - flush-write-stream "^1.0.2" - -levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" - -liftoff@^2.1.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-2.5.0.tgz#2009291bb31cea861bbf10a7c15a28caf75c31ec" - dependencies: - extend "^3.0.0" - findup-sync "^2.0.0" - fined "^1.0.1" - flagged-respawn "^1.0.0" - is-plain-object "^2.0.4" - object.map "^1.0.0" - rechoir "^0.6.2" - resolve "^1.1.7" - -line-by-line@0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/line-by-line/-/line-by-line-0.1.6.tgz#6236edd1db2d1695addf11f0268e74a181561c30" - -load-json-file@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" - dependencies: - graceful-fs "^4.1.2" - parse-json "^2.2.0" - pify "^2.0.0" - pinkie-promise "^2.0.0" - strip-bom "^2.0.0" - -lodash._basecallback@^3.0.0: - version "3.3.1" - resolved "https://registry.yarnpkg.com/lodash._basecallback/-/lodash._basecallback-3.3.1.tgz#b7b2bb43dc2160424a21ccf26c57e443772a8e27" - dependencies: - lodash._baseisequal "^3.0.0" - lodash._bindcallback "^3.0.0" - lodash.isarray "^3.0.0" - lodash.pairs "^3.0.0" - -lodash._basecopy@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36" - -lodash._baseeach@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/lodash._baseeach/-/lodash._baseeach-3.0.4.tgz#cf8706572ca144e8d9d75227c990da982f932af3" - dependencies: - lodash.keys "^3.0.0" - -lodash._basefind@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._basefind/-/lodash._basefind-3.0.0.tgz#b2bba05cc645f972de2cf925fa2bf63a0f60c8ae" - -lodash._basefindindex@^3.0.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/lodash._basefindindex/-/lodash._basefindindex-3.6.0.tgz#f083360a1b022418ed81bc899beb312e21e74a4f" - -lodash._baseisequal@^3.0.0: - version "3.0.7" - resolved "https://registry.yarnpkg.com/lodash._baseisequal/-/lodash._baseisequal-3.0.7.tgz#d8025f76339d29342767dcc887ce5cb95a5b51f1" - dependencies: - lodash.isarray "^3.0.0" - lodash.istypedarray "^3.0.0" - lodash.keys "^3.0.0" - -lodash._basetostring@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz#d1861d877f824a52f669832dcaf3ee15566a07d5" - -lodash._basevalues@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz#5b775762802bde3d3297503e26300820fdf661b7" - -lodash._bindcallback@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e" - -lodash._escapehtmlchar@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash._escapehtmlchar/-/lodash._escapehtmlchar-2.4.1.tgz#df67c3bb6b7e8e1e831ab48bfa0795b92afe899d" - dependencies: - lodash._htmlescapes "~2.4.1" - -lodash._escapestringchar@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash._escapestringchar/-/lodash._escapestringchar-2.4.1.tgz#ecfe22618a2ade50bfeea43937e51df66f0edb72" - -lodash._getnative@^3.0.0: - version "3.9.1" - resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" - -lodash._htmlescapes@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash._htmlescapes/-/lodash._htmlescapes-2.4.1.tgz#32d14bf0844b6de6f8b62a051b4f67c228b624cb" - -lodash._isiterateecall@^3.0.0: - version "3.0.9" - resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c" - -lodash._isnative@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash._isnative/-/lodash._isnative-2.4.1.tgz#3ea6404b784a7be836c7b57580e1cdf79b14832c" - -lodash._objecttypes@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz#7c0b7f69d98a1f76529f890b0cdb1b4dfec11c11" - -lodash._reescape@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reescape/-/lodash._reescape-3.0.0.tgz#2b1d6f5dfe07c8a355753e5f27fac7f1cde1616a" - -lodash._reevaluate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz#58bc74c40664953ae0b124d806996daca431e2ed" - -lodash._reinterpolate@^2.4.1, lodash._reinterpolate@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-2.4.1.tgz#4f1227aa5a8711fc632f5b07a1f4607aab8b3222" - -lodash._reinterpolate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" - -lodash._reunescapedhtml@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash._reunescapedhtml/-/lodash._reunescapedhtml-2.4.1.tgz#747c4fc40103eb3bb8a0976e571f7a2659e93ba7" - dependencies: - lodash._htmlescapes "~2.4.1" - lodash.keys "~2.4.1" - -lodash._root@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692" - -lodash._shimkeys@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash._shimkeys/-/lodash._shimkeys-2.4.1.tgz#6e9cc9666ff081f0b5a6c978b83e242e6949d203" - dependencies: - lodash._objecttypes "~2.4.1" - -lodash.defaults@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-2.4.1.tgz#a7e8885f05e68851144b6e12a8f3678026bc4c54" - dependencies: - lodash._objecttypes "~2.4.1" - lodash.keys "~2.4.1" - -lodash.escape@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-3.2.0.tgz#995ee0dc18c1b48cc92effae71a10aab5b487698" - dependencies: - lodash._root "^3.0.0" - -lodash.escape@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-2.4.1.tgz#2ce12c5e084db0a57dda5e5d1eeeb9f5d175a3b4" - dependencies: - lodash._escapehtmlchar "~2.4.1" - lodash._reunescapedhtml "~2.4.1" - lodash.keys "~2.4.1" - -lodash.find@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/lodash.find/-/lodash.find-3.2.1.tgz#046e319f3ace912ac6c9246c7f683c5ec07b36ad" - dependencies: - lodash._basecallback "^3.0.0" - lodash._baseeach "^3.0.0" - lodash._basefind "^3.0.0" - lodash._basefindindex "^3.0.0" - lodash.isarray "^3.0.0" - lodash.keys "^3.0.0" - -lodash.get@^4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" - -lodash.isarguments@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" - -lodash.isarray@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" - -lodash.isequal@^4.0.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" - -lodash.isobject@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-2.4.1.tgz#5a2e47fe69953f1ee631a7eba1fe64d2d06558f5" - dependencies: - lodash._objecttypes "~2.4.1" - -lodash.istypedarray@^3.0.0: - version "3.0.6" - resolved "https://registry.yarnpkg.com/lodash.istypedarray/-/lodash.istypedarray-3.0.6.tgz#c9a477498607501d8e8494d283b87c39281cef62" - -lodash.keys@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" - dependencies: - lodash._getnative "^3.0.0" - lodash.isarguments "^3.0.0" - lodash.isarray "^3.0.0" - -lodash.keys@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-2.4.1.tgz#48dea46df8ff7632b10d706b8acb26591e2b3727" - dependencies: - lodash._isnative "~2.4.1" - lodash._shimkeys "~2.4.1" - lodash.isobject "~2.4.1" - -lodash.pairs@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash.pairs/-/lodash.pairs-3.0.1.tgz#bbe08d5786eeeaa09a15c91ebf0dcb7d2be326a9" - dependencies: - lodash.keys "^3.0.0" - -lodash.restparam@^3.0.0: - version "3.6.1" - resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" - -lodash.template@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-2.4.1.tgz#9e611007edf629129a974ab3c48b817b3e1cf20d" - dependencies: - lodash._escapestringchar "~2.4.1" - lodash._reinterpolate "~2.4.1" - lodash.defaults "~2.4.1" - lodash.escape "~2.4.1" - lodash.keys "~2.4.1" - lodash.templatesettings "~2.4.1" - lodash.values "~2.4.1" - -lodash.template@^3.0.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-3.6.2.tgz#f8cdecc6169a255be9098ae8b0c53d378931d14f" - dependencies: - lodash._basecopy "^3.0.0" - lodash._basetostring "^3.0.0" - lodash._basevalues "^3.0.0" - lodash._isiterateecall "^3.0.0" - lodash._reinterpolate "^3.0.0" - lodash.escape "^3.0.0" - lodash.keys "^3.0.0" - lodash.restparam "^3.0.0" - lodash.templatesettings "^3.0.0" - -lodash.templatesettings@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz#fb307844753b66b9f1afa54e262c745307dba8e5" - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.escape "^3.0.0" - -lodash.templatesettings@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-2.4.1.tgz#ea76c75d11eb86d4dbe89a83893bb861929ac699" - dependencies: - lodash._reinterpolate "~2.4.1" - lodash.escape "~2.4.1" - -lodash.values@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-2.4.1.tgz#abf514436b3cb705001627978cbcf30b1280eea4" - dependencies: - lodash.keys "~2.4.1" - -lodash@4.17.5, lodash@^4.17.4: - version "4.17.5" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" - -lodash@^4.14.0: - version "4.17.10" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" - -lodash@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-1.0.2.tgz#8f57560c83b59fc270bd3d561b690043430e2551" - -lolex@^2.2.0, lolex@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/lolex/-/lolex-2.3.2.tgz#85f9450425103bf9e7a60668ea25dc43274ca807" - -longest@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" - -loud-rejection@^1.0.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" - dependencies: - currently-unhandled "^0.4.1" - signal-exit "^3.0.0" - -lowercase-keys@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" - -lowercase-keys@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" - -lru-cache@2: - version "2.7.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" - -lru-cache@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-3.2.0.tgz#71789b3b7f5399bec8565dda38aa30d2a097efee" - dependencies: - pseudomap "^1.0.1" - -lru-cache@^4.1.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.2.tgz#45234b2e6e2f2b33da125624c4664929a0224c3f" - dependencies: - pseudomap "^1.0.2" - yallist "^2.1.2" - -lru-queue@0.1: - version "0.1.0" - resolved "https://registry.yarnpkg.com/lru-queue/-/lru-queue-0.1.0.tgz#2738bd9f0d3cf4f84490c5736c48699ac632cda3" - dependencies: - es5-ext "~0.10.2" - -make-dir@^1.0.0, make-dir@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" - dependencies: - pify "^3.0.0" - -make-iterator@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.0.tgz#57bef5dc85d23923ba23767324d8e8f8f3d9694b" - dependencies: - kind-of "^3.1.0" - -map-cache@^0.2.0, map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - -map-obj@^1.0.0, map-obj@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" - -map-stream@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194" - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - dependencies: - object-visit "^1.0.0" - -md5.js@1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - -md5@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" - dependencies: - charenc "~0.0.1" - crypt "~0.0.1" - is-buffer "~1.1.1" - -memoizee@0.4.X: - version "0.4.12" - resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.12.tgz#780e99a219c50c549be6d0fc61765080975c58fb" - dependencies: - d "1" - es5-ext "^0.10.30" - es6-weak-map "^2.0.2" - event-emitter "^0.3.5" - is-promise "^2.1" - lru-queue "0.1" - next-tick "1" - timers-ext "^0.1.2" - -meow@^3.3.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" - dependencies: - camelcase-keys "^2.0.0" - decamelize "^1.1.2" - loud-rejection "^1.0.0" - map-obj "^1.0.1" - minimist "^1.1.3" - normalize-package-data "^2.3.4" - object-assign "^4.0.1" - read-pkg-up "^1.0.1" - redent "^1.0.0" - trim-newlines "^1.0.0" - -merge-stream@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-1.0.1.tgz#4041202d508a342ba00174008df0c251b8c135e1" - dependencies: - readable-stream "^2.0.1" - -micromatch@^2.1.5, micromatch@^2.3.7: - version "2.3.11" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" - dependencies: - arr-diff "^2.0.0" - array-unique "^0.2.1" - braces "^1.8.2" - expand-brackets "^0.1.4" - extglob "^0.3.1" - filename-regex "^2.0.0" - is-extglob "^1.0.0" - is-glob "^2.0.1" - kind-of "^3.0.2" - normalize-path "^2.0.1" - object.omit "^2.0.0" - parse-glob "^3.0.4" - regex-cache "^0.4.2" - -micromatch@^3.0.4, micromatch@^3.1.4: - version "3.1.9" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.9.tgz#15dc93175ae39e52e93087847096effc73efcf89" - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -mime-db@^1.28.0, mime-db@~1.33.0: - version "1.33.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" - -mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.7: - version "2.1.18" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" - dependencies: - mime-db "~1.33.0" - -mimic-response@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.0.tgz#df3d3652a73fded6b9b0b24146e6fd052353458e" - -"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - dependencies: - brace-expansion "^1.1.7" - -minimatch@^2.0.1: - version "2.0.10" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-2.0.10.tgz#8d087c39c6b38c001b97fca7ce6d0e1e80afbac7" - dependencies: - brace-expansion "^1.0.0" - -minimatch@~0.2.11: - version "0.2.14" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.2.14.tgz#c74e780574f63c6f9a090e90efbe6ef53a6a756a" - dependencies: - lru-cache "2" - sigmund "~1.0.0" - -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - -minimist@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.2.0.tgz#4dffe525dae2b864c66c2e23c6271d7afdecefce" - -minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - -minimist@~0.0.1: - version "0.0.10" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" - -mixin-deep@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - -mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - dependencies: - minimist "0.0.8" - -mocha@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-4.1.0.tgz#7d86cfbcf35cb829e2754c32e17355ec05338794" - dependencies: - browser-stdout "1.3.0" - commander "2.11.0" - debug "3.1.0" - diff "3.3.1" - escape-string-regexp "1.0.5" - glob "7.1.2" - growl "1.10.3" - he "1.1.1" - mkdirp "0.5.1" - supports-color "4.4.0" - -mocha@^5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.0.4.tgz#6b7aa328472da1088e69d47e75925fd3a3bb63c6" - dependencies: - browser-stdout "1.3.1" - commander "2.11.0" - debug "3.1.0" - diff "3.5.0" - escape-string-regexp "1.0.5" - glob "7.1.2" - growl "1.10.3" - he "1.1.1" - mkdirp "0.5.1" - supports-color "4.4.0" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - -multimatch@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-2.1.0.tgz#9c7906a22fb4c02919e2f5f75161b4cdbd4b2a2b" - dependencies: - array-differ "^1.0.0" - array-union "^1.0.1" - arrify "^1.0.0" - minimatch "^3.0.0" - -multipipe@^0.1.0, multipipe@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/multipipe/-/multipipe-0.1.2.tgz#2a8f2ddf70eed564dff2d57f1e1a137d9f05078b" - dependencies: - duplexer2 "0.0.2" - -named-js-regexp@1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/named-js-regexp/-/named-js-regexp-1.3.3.tgz#a2eb1655c74cb82213a4fc82777dfb67b895d8c8" - -nan@^2.3.0: - version "2.9.2" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.9.2.tgz#f564d75f5f8f36a6d9456cca7a6c4fe488ab7866" - -nanomatch@^1.2.9: - version "1.2.9" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.9.tgz#879f7150cb2dab7a471259066c104eee6e0fa7c2" - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-odd "^2.0.0" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -natives@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.1.tgz#011acce1f7cbd87f7ba6b3093d6cd9392be1c574" - -next-tick@1: - version "1.0.0" - resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" - -nise@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/nise/-/nise-1.3.0.tgz#7d6d506e64a0e37959495157f30a799c0436df72" - dependencies: - "@sinonjs/formatio" "^2.0.0" - just-extend "^1.1.27" - lolex "^2.3.2" - path-to-regexp "^1.7.0" - text-encoding "^0.6.4" - -node-has-native-dependencies@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/node-has-native-dependencies/-/node-has-native-dependencies-1.0.2.tgz#3152ec9753b6641e4d322d185dd4930649ada3da" - dependencies: - fs-walk "0.0.1" - -node-pre-gyp@^0.6.39: - version "0.6.39" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649" - dependencies: - detect-libc "^1.0.2" - hawk "3.1.3" - mkdirp "^0.5.1" - nopt "^4.0.1" - npmlog "^4.0.2" - rc "^1.1.7" - request "2.81.0" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^2.2.1" - tar-pack "^3.4.0" - -node-stream-zip@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/node-stream-zip/-/node-stream-zip-1.6.0.tgz#ad4b660dc72c33725af776de34785af6ffd7c203" - -node.extend@~1.1.2: - version "1.1.6" - resolved "https://registry.yarnpkg.com/node.extend/-/node.extend-1.1.6.tgz#a7b882c82d6c93a4863a5504bd5de8ec86258b96" - dependencies: - is "^3.1.0" - -nopt@3.x, nopt@~3.0.1: - version "3.0.6" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" - dependencies: - abbrev "1" - -nopt@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" - dependencies: - abbrev "1" - osenv "^0.1.4" - -normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: - version "2.4.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" - dependencies: - hosted-git-info "^2.1.4" - is-builtin-module "^1.0.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - -normalize-path@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-1.0.0.tgz#32d0e472f91ff345701c15a8311018d3b0a90379" - -normalize-path@^2.0.0, normalize-path@^2.0.1, normalize-path@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - dependencies: - remove-trailing-separator "^1.0.1" - -normalize-url@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6" - dependencies: - prepend-http "^2.0.0" - query-string "^5.0.1" - sort-keys "^2.0.0" - -now-and-later@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.0.tgz#bc61cbb456d79cb32207ce47ca05136ff2e7d6ee" - dependencies: - once "^1.3.2" - -npm-conf@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9" - dependencies: - config-chain "^1.1.11" - pify "^3.0.0" - -npmlog@^4.0.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - -oauth-sign@~0.8.1, oauth-sign@~0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" - -object-assign@4.X, object-assign@^4.0.0, object-assign@^4.0.1, object-assign@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - -object-assign@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" - -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - -object-keys@^1.0.11, object-keys@^1.0.8: - version "1.0.11" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" - -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - dependencies: - isobject "^3.0.0" - -object.assign@^4.0.4: - version "4.1.0" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" - dependencies: - define-properties "^1.1.2" - function-bind "^1.1.1" - has-symbols "^1.0.0" - object-keys "^1.0.11" - -object.defaults@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf" - dependencies: - array-each "^1.0.1" - array-slice "^1.0.0" - for-own "^1.0.0" - isobject "^3.0.0" - -object.map@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object.map/-/object.map-1.0.1.tgz#cf83e59dc8fcc0ad5f4250e1f78b3b81bd801d37" - dependencies: - for-own "^1.0.0" - make-iterator "^1.0.0" - -object.omit@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" - dependencies: - for-own "^0.1.4" - is-extendable "^0.1.1" - -object.pick@^1.2.0, object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - dependencies: - isobject "^3.0.1" - -once@1.x, once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.3.3, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - dependencies: - wrappy "1" - -once@~1.3.0: - version "1.3.3" - resolved "https://registry.yarnpkg.com/once/-/once-1.3.3.tgz#b2e261557ce4c314ec8304f3fa82663e4297ca20" - dependencies: - wrappy "1" - -opn@5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/opn/-/opn-5.3.0.tgz#64871565c863875f052cfdf53d3e3cb5adb53b1c" - dependencies: - is-wsl "^1.1.0" - -optimist@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" - dependencies: - minimist "~0.0.1" - wordwrap "~0.0.2" - -optionator@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" - dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.4" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - wordwrap "~1.0.0" - -orchestrator@^0.3.0: - version "0.3.8" - resolved "https://registry.yarnpkg.com/orchestrator/-/orchestrator-0.3.8.tgz#14e7e9e2764f7315fbac184e506c7aa6df94ad7e" - dependencies: - end-of-stream "~0.1.5" - sequencify "~0.0.7" - stream-consume "~0.1.0" - -ordered-read-streams@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz#fd565a9af8eb4473ba69b6ed8a34352cb552f126" - -ordered-read-streams@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz#7137e69b3298bb342247a1bbee3881c80e2fd78b" - dependencies: - is-stream "^1.0.1" - readable-stream "^2.0.1" - -ordered-read-streams@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz#77c0cb37c41525d64166d990ffad7ec6a0e1363e" - dependencies: - readable-stream "^2.0.1" - -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - -os-tmpdir@^1.0.0, os-tmpdir@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - -osenv@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - -p-cancelable@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0" - -p-event@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/p-event/-/p-event-1.3.0.tgz#8e6b4f4f65c72bc5b6fe28b75eda874f96a4a085" - dependencies: - p-timeout "^1.1.1" - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - -p-is-promise@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e" - -p-map@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" - -p-timeout@^1.1.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-1.2.1.tgz#5eb3b353b7fce99f101a1038880bb054ebbea386" - dependencies: - p-finally "^1.0.0" - -p-timeout@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038" - dependencies: - p-finally "^1.0.0" - -parse-filepath@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" - dependencies: - is-absolute "^1.0.0" - map-cache "^0.2.0" - path-root "^0.1.1" - -parse-glob@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" - dependencies: - glob-base "^0.3.0" - is-dotfile "^1.0.0" - is-extglob "^1.0.0" - is-glob "^2.0.0" - -parse-json@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" - dependencies: - error-ex "^1.2.0" - -parse-passwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" - -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - -path-dirname@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" - -path-exists@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" - dependencies: - pinkie-promise "^2.0.0" - -path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - -path-is-inside@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" - -path-parse@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" - -path-root-regex@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/path-root-regex/-/path-root-regex-0.1.2.tgz#bfccdc8df5b12dc52c8b43ec38d18d72c04ba96d" - -path-root@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/path-root/-/path-root-0.1.1.tgz#9a4a6814cac1c0cd73360a95f32083c8ea4745b7" - dependencies: - path-root-regex "^0.1.0" - -path-to-regexp@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" - dependencies: - isarray "0.0.1" - -path-type@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" - dependencies: - graceful-fs "^4.1.2" - pify "^2.0.0" - pinkie-promise "^2.0.0" - -pathval@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" - -pause-stream@0.0.11: - version "0.0.11" - resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445" - dependencies: - through "~2.3" - -pend@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" - -performance-now@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" - -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" - -pidusage@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/pidusage/-/pidusage-1.2.0.tgz#65ee96ace4e08a4cd3f9240996c85b367171ee92" - -pify@^2.0.0, pify@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" - -pify@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" - -pinkie-promise@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" - dependencies: - pinkie "^2.0.0" - -pinkie@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" - -plugin-error@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-0.1.2.tgz#3b9bb3335ccf00f425e07437e19276967da47ace" - dependencies: - ansi-cyan "^0.1.1" - ansi-red "^0.1.1" - arr-diff "^1.0.1" - arr-union "^2.0.1" - extend-shallow "^1.1.2" - -plugin-error@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-1.0.1.tgz#77016bd8919d0ac377fdcdd0322328953ca5781c" - dependencies: - ansi-colors "^1.0.1" - arr-diff "^4.0.0" - arr-union "^3.1.0" - extend-shallow "^3.0.2" - -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - -postinstall-build@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/postinstall-build/-/postinstall-build-5.0.1.tgz#b917a9079b26178d9a24af5a5cd8cb4a991d11b9" - -prelude-ls@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - -prepend-http@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" - -preserve@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" - -pretty-hrtime@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" - -process-nextick-args@^2.0.0, process-nextick-args@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" - -process-nextick-args@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" - -proto-list@~1.2.1: - version "1.2.4" - resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" - -pseudomap@^1.0.1, pseudomap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" - -pump@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -pumpify@^1.3.5: - version "1.4.0" - resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.4.0.tgz#80b7c5df7e24153d03f0e7ac8a05a5d068bd07fb" - dependencies: - duplexify "^3.5.3" - inherits "^2.0.3" - pump "^2.0.0" - -punycode@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - -qs@~6.3.0: - version "6.3.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c" - -qs@~6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" - -qs@~6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" - -query-string@^5.0.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" - dependencies: - decode-uri-component "^0.2.0" - object-assign "^4.1.0" - strict-uri-encode "^1.0.0" - -querystringify@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-1.0.0.tgz#6286242112c5b712fa654e526652bf6a13ff05cb" - -queue@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/queue/-/queue-3.1.0.tgz#6c49d01f009e2256788789f2bffac6b8b9990585" - dependencies: - inherits "~2.0.0" - -queue@^4.2.1: - version "4.4.2" - resolved "https://registry.yarnpkg.com/queue/-/queue-4.4.2.tgz#5a9733d9a8b8bd1b36e934bc9c55ab89b28e29c7" - dependencies: - inherits "~2.0.0" - -randomatic@^1.1.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - -rc@^1.1.7: - version "1.2.6" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.6.tgz#eb18989c6d4f4f162c399f79ddd29f3835568092" - dependencies: - deep-extend "~0.4.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -read-pkg-up@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" - dependencies: - find-up "^1.0.0" - read-pkg "^1.0.0" - -read-pkg@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" - dependencies: - load-json-file "^1.0.0" - normalize-package-data "^2.3.2" - path-type "^1.0.0" - -"readable-stream@>=1.0.33-1 <1.1.0-0", readable-stream@~1.0.17: - version "1.0.34" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - -readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5: - version "2.3.5" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.5.tgz#b4f85003a938cbb6ecbce2a124fb1012bd1a838d" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.0.3" - util-deprecate "~1.0.1" - -readable-stream@^2.3.0: - version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@~1.1.9: - version "1.1.14" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - -readable-stream@~2.0.0: - version "2.0.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - string_decoder "~0.10.x" - util-deprecate "~1.0.1" - -readdirp@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" - dependencies: - graceful-fs "^4.1.2" - minimatch "^3.0.2" - readable-stream "^2.0.2" - set-immediate-shim "^1.0.1" - -rechoir@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" - dependencies: - resolve "^1.1.6" - -redent@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" - dependencies: - indent-string "^2.1.0" - strip-indent "^1.0.1" - -reflect-metadata@0.1.12: - version "0.1.12" - resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.12.tgz#311bf0c6b63cd782f228a81abe146a2bfa9c56f2" - -regex-cache@^0.4.2: - version "0.4.4" - resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" - dependencies: - is-equal-shallow "^0.1.3" - -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - -relative@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/relative/-/relative-3.0.2.tgz#0dcd8ec54a5d35a3c15e104503d65375b5a5367f" - dependencies: - isobject "^2.0.0" - -remap-istanbul@^0.10.1: - version "0.10.1" - resolved "https://registry.yarnpkg.com/remap-istanbul/-/remap-istanbul-0.10.1.tgz#3aa58dd5021d499f336d3ba5bf3bbb91c1b88e37" - dependencies: - amdefine "^1.0.0" - istanbul "0.4.5" - minimatch "^3.0.3" - plugin-error "^0.1.2" - source-map "^0.6.1" - through2 "2.0.1" - -remove-bom-buffer@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz#c2bf1e377520d324f623892e33c10cac2c252b53" - dependencies: - is-buffer "^1.1.5" - is-utf8 "^0.2.1" - -remove-bom-stream@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz#05f1a593f16e42e1fb90ebf59de8e569525f9523" - dependencies: - remove-bom-buffer "^3.0.0" - safe-buffer "^5.1.0" - through2 "^2.0.3" - -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - -repeat-element@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" - -repeat-string@^1.5.2, repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - -repeating@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" - dependencies: - is-finite "^1.0.0" - -replace-ext@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924" - -replace-ext@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb" - -request-progress@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-3.0.0.tgz#4ca754081c7fec63f505e4faa825aa06cd669dbe" - dependencies: - throttleit "^1.0.0" - -request@2.81.0: - version "2.81.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" - dependencies: - aws-sign2 "~0.6.0" - aws4 "^1.2.1" - caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.0" - forever-agent "~0.6.1" - form-data "~2.1.1" - har-validator "~4.2.1" - hawk "~3.1.3" - http-signature "~1.1.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.7" - oauth-sign "~0.8.1" - performance-now "^0.2.0" - qs "~6.4.0" - safe-buffer "^5.0.1" - stringstream "~0.0.4" - tough-cookie "~2.3.0" - tunnel-agent "^0.6.0" - uuid "^3.0.0" - -request@2.85.0, request@^2.83.0: - version "2.85.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.85.0.tgz#5a03615a47c61420b3eb99b7dba204f83603e1fa" - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.6.0" - caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.1" - forever-agent "~0.6.1" - form-data "~2.3.1" - har-validator "~5.0.3" - hawk "~6.0.2" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.17" - oauth-sign "~0.8.2" - performance-now "^2.1.0" - qs "~6.5.1" - safe-buffer "^5.1.1" - stringstream "~0.0.5" - tough-cookie "~2.3.3" - tunnel-agent "^0.6.0" - uuid "^3.1.0" - -request@~2.79.0: - version "2.79.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" - dependencies: - aws-sign2 "~0.6.0" - aws4 "^1.2.1" - caseless "~0.11.0" - combined-stream "~1.0.5" - extend "~3.0.0" - forever-agent "~0.6.1" - form-data "~2.1.1" - har-validator "~2.0.6" - hawk "~3.1.3" - http-signature "~1.1.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.7" - oauth-sign "~0.8.1" - qs "~6.3.0" - stringstream "~0.0.4" - tough-cookie "~2.3.0" - tunnel-agent "~0.4.1" - uuid "^3.0.0" - -request@~2.83.0: - version "2.83.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.6.0" - caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.1" - forever-agent "~0.6.1" - form-data "~2.3.1" - har-validator "~5.0.3" - hawk "~6.0.2" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.17" - oauth-sign "~0.8.2" - performance-now "^2.1.0" - qs "~6.5.1" - safe-buffer "^5.1.1" - stringstream "~0.0.5" - tough-cookie "~2.3.3" - tunnel-agent "^0.6.0" - uuid "^3.1.0" - -requires-port@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - -resolve-dir@^1.0.0, resolve-dir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" - dependencies: - expand-tilde "^2.0.0" - global-modules "^1.0.0" - -resolve-options@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/resolve-options/-/resolve-options-1.1.0.tgz#32bb9e39c06d67338dc9378c0d6d6074566ad131" - dependencies: - value-or-function "^3.0.0" - -resolve-url@^0.2.1, resolve-url@~0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - -resolve@1.1.x: - version "1.1.7" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" - -resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.2: - version "1.5.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36" - dependencies: - path-parse "^1.0.5" - -responselike@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" - dependencies: - lowercase-keys "^1.0.0" - -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - -retyped-diff-match-patch-tsd-ambient@^1.0.0-0: - version "1.0.0-1" - resolved "https://registry.yarnpkg.com/retyped-diff-match-patch-tsd-ambient/-/retyped-diff-match-patch-tsd-ambient-1.0.0-1.tgz#26482bf4915c7ed9f8300bb5cbec48fd4ff5bc62" - -right-align@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" - dependencies: - align-text "^0.1.1" - -rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.1: - version "2.6.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" - dependencies: - glob "^7.0.5" - -rxjs@5.5.9: - version "5.5.9" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.9.tgz#12a0487794b00f5eb370fec2751bd973a89886fb" - dependencies: - symbol-observable "1.0.1" - -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" - -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - dependencies: - ret "~0.1.10" - -safer-buffer@^2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - -samsam@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.3.0.tgz#8d1d9350e25622da30de3e44ba692b5221ab7c50" - -sax@0.5.x: - version "0.5.8" - resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1" - -sax@>=0.6.0: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - -seek-bzip@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.5.tgz#cfe917cb3d274bcffac792758af53173eb1fabdc" - dependencies: - commander "~2.8.1" - -"semver@2 || 3 || 4 || 5", semver@5.5.0, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1: - version "5.5.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" - -semver@^4.1.0: - version "4.3.6" - resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" - -sequencify@~0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/sequencify/-/sequencify-0.0.7.tgz#90cff19d02e07027fd767f5ead3e7b95d1e7380c" - -set-blocking@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - -set-immediate-shim@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" - -set-value@^0.4.3: - version "0.4.3" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1" - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.1" - to-object-path "^0.3.0" - -set-value@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.0.tgz#71ae4a88f0feefbbf52d1ea604f3fb315ebb6274" - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - -shortid@^2.2.8: - version "2.2.8" - resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.8.tgz#033b117d6a2e975804f6f0969dbe7d3d0b355131" - -sigmund@^1.0.1, sigmund@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" - -signal-exit@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" - -sinon@^4.4.5: - version "4.4.5" - resolved "https://registry.yarnpkg.com/sinon/-/sinon-4.4.5.tgz#b625f992f0f0998d068a270c34e8f50ddcfd846b" - dependencies: - "@sinonjs/formatio" "^2.0.0" - diff "^3.1.0" - lodash.get "^4.4.2" - lolex "^2.2.0" - nise "^1.2.0" - supports-color "^5.1.0" - type-detect "^4.0.5" - -slash@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" - -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - -sntp@1.x.x: - version "1.0.9" - resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" - dependencies: - hoek "2.x.x" - -sntp@2.x.x: - version "2.1.0" - resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8" - dependencies: - hoek "4.x.x" - -sort-keys-length@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/sort-keys-length/-/sort-keys-length-1.0.1.tgz#9cb6f4f4e9e48155a6aa0671edd336ff1479a188" - dependencies: - sort-keys "^1.0.0" - -sort-keys@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" - dependencies: - is-plain-obj "^1.0.0" - -sort-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" - dependencies: - is-plain-obj "^1.0.0" - -source-map-resolve@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.3.1.tgz#610f6122a445b8dd51535a2a71b783dfc1248761" - dependencies: - atob "~1.1.0" - resolve-url "~0.2.1" - source-map-url "~0.3.0" - urix "~0.1.0" - -source-map-resolve@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.1.tgz#7ad0f593f2281598e854df80f19aae4b92d7a11a" - dependencies: - atob "^2.0.0" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - -source-map-support@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.3.tgz#2b3d5fff298cfa4d1afd7d4352d569e9a0158e76" - dependencies: - source-map "^0.6.0" - -source-map-url@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" - -source-map-url@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.3.0.tgz#7ecaf13b57bcd09da8a40c5d269db33799d4aaf9" - -source-map@^0.1.38: - version "0.1.43" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.1.43.tgz#c24bc146ca517c1471f5dacbe2571b2b7f9e3346" - dependencies: - amdefine ">=0.0.4" - -source-map@^0.4.4: - version "0.4.4" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" - dependencies: - amdefine ">=0.0.4" - -source-map@^0.5.6, source-map@~0.5.1: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - -source-map@~0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d" - dependencies: - amdefine ">=0.0.4" - -sparkles@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3" - -spdx-correct@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.0.tgz#05a5b4d7153a195bc92c3c425b69f3b2a9524c82" - dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" - -spdx-exceptions@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz#2c7ae61056c714a5b9b9b2b2af7d311ef5c78fe9" - -spdx-expression-parse@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" - dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" - -spdx-license-ids@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz#7a7cd28470cc6d3a1cfe6d66886f6bc430d3ac87" - -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - dependencies: - extend-shallow "^3.0.0" - -split@0.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/split/-/split-0.3.3.tgz#cd0eea5e63a211dfff7eb0f091c4133e2d0dd28f" - dependencies: - through "2" - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - -sshpk@^1.7.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.1.tgz#130f5975eddad963f1d56f92b9ac6c51fa9f83eb" - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - dashdash "^1.12.0" - getpass "^0.1.1" - optionalDependencies: - bcrypt-pbkdf "^1.0.0" - ecc-jsbn "~0.1.1" - jsbn "~0.1.0" - tweetnacl "~0.14.0" - -stat-mode@^0.2.0: - version "0.2.2" - resolved "https://registry.yarnpkg.com/stat-mode/-/stat-mode-0.2.2.tgz#e6c80b623123d7d80cf132ce538f346289072502" - -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - -stream-combiner@~0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14" - dependencies: - duplexer "~0.1.1" - -stream-consume@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/stream-consume/-/stream-consume-0.1.1.tgz#d3bdb598c2bd0ae82b8cac7ac50b1107a7996c48" - -stream-shift@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" - -streamfilter@^1.0.5: - version "1.0.7" - resolved "https://registry.yarnpkg.com/streamfilter/-/streamfilter-1.0.7.tgz#ae3e64522aa5a35c061fd17f67620c7653c643c9" - dependencies: - readable-stream "^2.0.2" - -streamifier@~0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/streamifier/-/streamifier-0.1.1.tgz#97e98d8fa4d105d62a2691d1dc07e820db8dfc4f" - -strict-uri-encode@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" - -string-width@^1.0.1, string-width@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - -string_decoder@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" - dependencies: - safe-buffer "~5.1.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - dependencies: - safe-buffer "~5.1.0" - -stringstream@~0.0.4, stringstream@~0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" - -strip-ansi@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.3.0.tgz#25f48ea22ca79187f3174a4db8759347bb126220" - dependencies: - ansi-regex "^0.2.1" - -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - dependencies: - ansi-regex "^2.0.0" - -strip-bom-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz#e7144398577d51a6bed0fa1994fa05f43fd988ee" - dependencies: - first-chunk-stream "^1.0.0" - strip-bom "^2.0.0" - -strip-bom-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz#f87db5ef2613f6968aa545abfe1ec728b6a829ca" - dependencies: - first-chunk-stream "^2.0.0" - strip-bom "^2.0.0" - -strip-bom-string@1.X: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92" - -strip-bom@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-1.0.0.tgz#85b8862f3844b5a6d5ec8467a93598173a36f794" - dependencies: - first-chunk-stream "^1.0.0" - is-utf8 "^0.2.0" - -strip-bom@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" - dependencies: - is-utf8 "^0.2.0" - -strip-dirs@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5" - dependencies: - is-natural-number "^4.0.1" - -strip-indent@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" - dependencies: - get-stdin "^4.0.1" - -strip-indent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68" - -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - -strip-outer@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-1.0.1.tgz#b2fd2abf6604b9d1e6013057195df836b8a9d631" - dependencies: - escape-string-regexp "^1.0.2" - -sudo-prompt@8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-8.2.0.tgz#bcd4aaacdb367b77b4bffcce1c658c2b1dd327f3" - -supports-color@4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e" - dependencies: - has-flag "^2.0.0" - -supports-color@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a" - -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - -supports-color@^3.1.0: - version "3.2.3" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" - dependencies: - has-flag "^1.0.0" - -supports-color@^5.1.0, supports-color@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.3.0.tgz#5b24ac15db80fa927cf5227a4a33fd3c4c7676c0" - dependencies: - has-flag "^3.0.0" - -symbol-observable@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4" - -tar-pack@^3.4.0: - version "3.4.1" - resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f" - dependencies: - debug "^2.2.0" - fstream "^1.0.10" - fstream-ignore "^1.0.5" - once "^1.3.3" - readable-stream "^2.1.4" - rimraf "^2.5.1" - tar "^2.2.1" - uid-number "^0.0.6" - -tar-stream@^1.5.2: - version "1.6.1" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.1.tgz#f84ef1696269d6223ca48f6e1eeede3f7e81f395" - dependencies: - bl "^1.0.0" - buffer-alloc "^1.1.0" - end-of-stream "^1.0.0" - fs-constants "^1.0.0" - readable-stream "^2.3.0" - to-buffer "^1.1.0" - xtend "^4.0.0" - -tar@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" - dependencies: - block-stream "*" - fstream "^1.0.2" - inherits "2" - -text-encoding@^0.6.4: - version "0.6.4" - resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19" - -throttleit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-1.0.0.tgz#9e785836daf46743145a5984b6268d828528ac6c" - -through2-filter@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-2.0.0.tgz#60bc55a0dacb76085db1f9dae99ab43f83d622ec" - dependencies: - through2 "~2.0.0" - xtend "~4.0.0" - -through2@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.1.tgz#384e75314d49f32de12eebb8136b8eb6b5d59da9" - dependencies: - readable-stream "~2.0.0" - xtend "~4.0.0" - -through2@2.X, through2@^2.0.0, through2@^2.0.3, through2@~2.0.0, through2@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" - dependencies: - readable-stream "^2.1.5" - xtend "~4.0.1" - -through2@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/through2/-/through2-0.5.1.tgz#dfdd012eb9c700e2323fd334f38ac622ab372da7" - dependencies: - readable-stream "~1.0.17" - xtend "~3.0.0" - -through2@^0.6.0, through2@^0.6.1, through2@~0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" - dependencies: - readable-stream ">=1.0.33-1 <1.1.0-0" - xtend ">=4.0.0 <4.1.0-0" - -through@2, "through@>=2.2.7 <3", through@^2.3.6, through@~2.3, through@~2.3.1: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - -tildify@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/tildify/-/tildify-1.2.0.tgz#dcec03f55dca9b7aa3e5b04f21817eb56e63588a" - dependencies: - os-homedir "^1.0.0" - -time-stamp@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" - -timed-out@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" - -timers-ext@^0.1.2: - version "0.1.5" - resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.5.tgz#77147dd4e76b660c2abb8785db96574cbbd12922" - dependencies: - es5-ext "~0.10.14" - next-tick "1" - -tmp@0.0.29: - version "0.0.29" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.29.tgz#f25125ff0dd9da3ccb0c2dd371ee1288bb9128c0" - dependencies: - os-tmpdir "~1.0.1" - -to-absolute-glob@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-0.1.1.tgz#1cdfa472a9ef50c239ee66999b662ca0eb39937f" - dependencies: - extend-shallow "^2.0.1" - -to-absolute-glob@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1865f43d9e74b0822db9f145b78cff7d0f7c849b" - dependencies: - is-absolute "^1.0.0" - is-negated-glob "^1.0.0" - -to-buffer@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" - -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - -to-regex@^3.0.1: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - -to-through@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-through/-/to-through-2.0.0.tgz#fc92adaba072647bc0b67d6b03664aa195093af6" - dependencies: - through2 "^2.0.3" - -tough-cookie@~2.3.0, tough-cookie@~2.3.3: - version "2.3.4" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" - dependencies: - punycode "^1.4.1" - -tree-kill@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.0.tgz#5846786237b4239014f05db156b643212d4c6f36" - -trim-newlines@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" - -trim-repeated@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/trim-repeated/-/trim-repeated-1.0.0.tgz#e3646a2ea4e891312bf7eace6cfb05380bc01c21" - dependencies: - escape-string-regexp "^1.0.2" - -tslib@1.9.0, tslib@^1.7.1, tslib@^1.8.0, tslib@^1.8.1: - version "1.9.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8" - -tslint-eslint-rules@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/tslint-eslint-rules/-/tslint-eslint-rules-5.1.0.tgz#3232b318da55dbb5a83e3f5d657c1ddbb27b9ff2" - dependencies: - doctrine "0.7.2" - tslib "1.9.0" - tsutils "2.8.0" - -tslint-microsoft-contrib@^5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/tslint-microsoft-contrib/-/tslint-microsoft-contrib-5.0.3.tgz#6fc3e238179cd72045c2b422e4d655f4183a8d5c" - dependencies: - tsutils "^2.12.1" - -tslint@^5.9.1: - version "5.9.1" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.9.1.tgz#1255f87a3ff57eb0b0e1f0e610a8b4748046c9ae" - dependencies: - babel-code-frame "^6.22.0" - builtin-modules "^1.1.1" - chalk "^2.3.0" - commander "^2.12.1" - diff "^3.2.0" - glob "^7.1.1" - js-yaml "^3.7.0" - minimatch "^3.0.4" - resolve "^1.3.2" - semver "^5.3.0" - tslib "^1.8.0" - tsutils "^2.12.1" - -tsutils@2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.8.0.tgz#0160173729b3bf138628dd14a1537e00851d814a" - dependencies: - tslib "^1.7.1" - -tsutils@^2.12.1: - version "2.22.2" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.22.2.tgz#0b9f3d87aa3eb95bd32d26ce2b88aa329a657951" - dependencies: - tslib "^1.8.1" - -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" - dependencies: - safe-buffer "^5.0.1" - -tunnel-agent@~0.4.1: - version "0.4.3" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" - -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - dependencies: - prelude-ls "~1.1.2" - -type-detect@^4.0.0, type-detect@^4.0.5: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - -typemoq@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/typemoq/-/typemoq-2.1.0.tgz#4452ce360d92cf2a1a180f0c29de2803f87af1e8" - dependencies: - circular-json "^0.3.1" - lodash "^4.17.4" - postinstall-build "^5.0.1" - -typescript-char@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/typescript-char/-/typescript-char-0.0.0.tgz#558feda737c765a610b737eefbb1775ee9bc8dab" - -typescript-formatter@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/typescript-formatter/-/typescript-formatter-7.1.0.tgz#dd1b5547de211065221f765263e15f18c84c66b8" - dependencies: - commandpost "^1.0.0" - editorconfig "^0.15.0" - -typescript@^2.9.1: - version "2.9.1" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.9.1.tgz#fdb19d2c67a15d11995fd15640e373e09ab09961" - -uglify-js@^2.6: - version "2.8.29" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" - dependencies: - source-map "~0.5.1" - yargs "~3.10.0" - optionalDependencies: - uglify-to-browserify "~1.0.0" - -uglify-to-browserify@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" - -uid-number@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" - -uint64be@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/uint64be/-/uint64be-1.0.1.tgz#1f7154202f2a1b8af353871dda651bf34ce93e95" - -unbzip2-stream@^1.0.9: - version "1.2.5" - resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.2.5.tgz#73a033a567bbbde59654b193c44d48a7e4f43c47" - dependencies: - buffer "^3.0.1" - through "^2.3.6" - -unc-path-regex@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" - -underscore@~1.8.3: - version "1.8.3" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022" - -unicode@10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/unicode/-/unicode-10.0.0.tgz#e5d51c1db93b6c71a0b879e0b0c4af7e6fdf688e" - -union-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^0.4.3" - -unique-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-1.0.0.tgz#d59a4a75427447d9aa6c91e70263f8d26a4b104b" - -unique-stream@^2.0.2: - version "2.2.1" - resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.2.1.tgz#5aa003cfbe94c5ff866c4e7d668bb1c4dbadb369" - dependencies: - json-stable-stringify "^1.0.0" - through2-filter "^2.0.0" - -universalify@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7" - -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - -untildify@3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/untildify/-/untildify-3.0.2.tgz#7f1f302055b3fea0f3e81dc78eb36766cb65e3f1" - -upath@^1.0.0: - version "1.0.4" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.0.4.tgz#ee2321ba0a786c50973db043a50b7bcba822361d" - -urix@^0.1.0, urix@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - -url-parse-lax@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c" - dependencies: - prepend-http "^2.0.0" - -url-parse@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.2.0.tgz#3a19e8aaa6d023ddd27dcc44cb4fc8f7fec23986" - dependencies: - querystringify "~1.0.0" - requires-port "~1.0.0" - -url-to-options@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" - -urlgrey@0.4.4: - version "0.4.4" - resolved "https://registry.yarnpkg.com/urlgrey/-/urlgrey-0.4.4.tgz#892fe95960805e85519f1cd4389f2cb4cbb7652f" - -use@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.0.tgz#14716bf03fdfefd03040aef58d8b4b85f3a7c544" - dependencies: - kind-of "^6.0.2" - -user-home@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190" - -util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - -uuid@^3.0.0, uuid@^3.1.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14" - -v8flags@^2.0.2: - version "2.1.1" - resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4" - dependencies: - user-home "^1.1.1" - -vali-date@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/vali-date/-/vali-date-1.0.0.tgz#1b904a59609fb328ef078138420934f6b86709a6" - -validate-npm-package-license@^3.0.1: - version "3.0.3" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz#81643bcbef1bdfecd4623793dc4648948ba98338" - dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" - -validator@~9.4.1: - version "9.4.1" - resolved "https://registry.yarnpkg.com/validator/-/validator-9.4.1.tgz#abf466d398b561cd243050112c6ff1de6cc12663" - -value-or-function@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813" - -verror@1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - -vinyl-file@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/vinyl-file/-/vinyl-file-2.0.0.tgz#a7ebf5ffbefda1b7d18d140fcb07b223efb6751a" - dependencies: - graceful-fs "^4.1.2" - pify "^2.3.0" - pinkie-promise "^2.0.0" - strip-bom "^2.0.0" - strip-bom-stream "^2.0.0" - vinyl "^1.1.0" - -vinyl-fs@^0.3.0: - version "0.3.14" - resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-0.3.14.tgz#9a6851ce1cac1c1cea5fe86c0931d620c2cfa9e6" - dependencies: - defaults "^1.0.0" - glob-stream "^3.1.5" - glob-watcher "^0.0.6" - graceful-fs "^3.0.0" - mkdirp "^0.5.0" - strip-bom "^1.0.0" - through2 "^0.6.1" - vinyl "^0.4.0" - -vinyl-fs@^2.0.0, vinyl-fs@^2.4.3: - version "2.4.4" - resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-2.4.4.tgz#be6ff3270cb55dfd7d3063640de81f25d7532239" - dependencies: - duplexify "^3.2.0" - glob-stream "^5.3.2" - graceful-fs "^4.0.0" - gulp-sourcemaps "1.6.0" - is-valid-glob "^0.3.0" - lazystream "^1.0.0" - lodash.isequal "^4.0.0" - merge-stream "^1.0.0" - mkdirp "^0.5.0" - object-assign "^4.0.0" - readable-stream "^2.0.4" - strip-bom "^2.0.0" - strip-bom-stream "^1.0.0" - through2 "^2.0.0" - through2-filter "^2.0.0" - vali-date "^1.0.0" - vinyl "^1.0.0" - -vinyl-fs@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.2.tgz#1b86258844383f57581fcaac081fe09ef6d6d752" - dependencies: - fs-mkdirp-stream "^1.0.0" - glob-stream "^6.1.0" - graceful-fs "^4.0.0" - is-valid-glob "^1.0.0" - lazystream "^1.0.0" - lead "^1.0.0" - object.assign "^4.0.4" - pumpify "^1.3.5" - readable-stream "^2.3.3" - remove-bom-buffer "^3.0.0" - remove-bom-stream "^1.2.0" - resolve-options "^1.1.0" - through2 "^2.0.0" - to-through "^2.0.0" - value-or-function "^3.0.0" - vinyl "^2.0.0" - vinyl-sourcemap "^1.1.0" - -vinyl-source-stream@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vinyl-source-stream/-/vinyl-source-stream-1.1.2.tgz#62b53a135610a896e98ca96bee3a87f008a8e780" - dependencies: - through2 "^2.0.3" - vinyl "^0.4.3" - -vinyl-sourcemap@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz#92a800593a38703a8cdb11d8b300ad4be63b3e16" - dependencies: - append-buffer "^1.0.2" - convert-source-map "^1.5.0" - graceful-fs "^4.1.6" - normalize-path "^2.1.1" - now-and-later "^2.0.0" - remove-bom-buffer "^3.0.0" - vinyl "^2.0.0" - -vinyl@^0.2.1: - version "0.2.3" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.2.3.tgz#bca938209582ec5a49ad538a00fa1f125e513252" - dependencies: - clone-stats "~0.0.1" - -vinyl@^0.4.0, vinyl@^0.4.3, vinyl@~0.4.6: - version "0.4.6" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.4.6.tgz#2f356c87a550a255461f36bbeb2a5ba8bf784847" - dependencies: - clone "^0.2.0" - clone-stats "^0.0.1" - -vinyl@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.5.3.tgz#b0455b38fc5e0cf30d4325132e461970c2091cde" - dependencies: - clone "^1.0.0" - clone-stats "^0.0.1" - replace-ext "0.0.1" - -vinyl@^1.0.0, vinyl@^1.1.0, vinyl@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-1.2.0.tgz#5c88036cf565e5df05558bfc911f8656df218884" - dependencies: - clone "^1.0.0" - clone-stats "^0.0.1" - replace-ext "0.0.1" - -vinyl@^2.0.0, vinyl@^2.0.2, vinyl@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.1.0.tgz#021f9c2cf951d6b939943c89eb5ee5add4fd924c" - dependencies: - clone "^2.1.1" - clone-buffer "^1.0.0" - clone-stats "^1.0.0" - cloneable-readable "^1.0.0" - remove-trailing-separator "^1.0.1" - replace-ext "^1.0.0" - -vinyl@~2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.0.2.tgz#0a3713d8d4e9221c58f10ca16c0116c9e25eda7c" - dependencies: - clone "^1.0.0" - clone-buffer "^1.0.0" - clone-stats "^1.0.0" - cloneable-readable "^1.0.0" - is-stream "^1.1.0" - remove-trailing-separator "^1.0.1" - replace-ext "^1.0.0" - -vscode-debugadapter-testsupport@^1.27.0: - version "1.27.0" - resolved "https://registry.yarnpkg.com/vscode-debugadapter-testsupport/-/vscode-debugadapter-testsupport-1.27.0.tgz#bab26880ea2f13efb5a120964c4c48ed75d3d15d" - dependencies: - vscode-debugprotocol "1.27.0" - -vscode-debugadapter@1.28.0: - version "1.28.0" - resolved "https://registry.yarnpkg.com/vscode-debugadapter/-/vscode-debugadapter-1.28.0.tgz#ebd6653e3f41db324d9547595375571a8732e966" - dependencies: - vscode-debugprotocol "1.28.0" - vscode-uri "1.0.1" - -vscode-debugprotocol@1.27.0: - version "1.27.0" - resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.27.0.tgz#735a43a3cc1235fe587c0ef93fe4e328def7b17c" - -vscode-debugprotocol@1.28.0: - version "1.28.0" - resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.28.0.tgz#b9fb97c3fb2dadbec78e5c1619ff12bf741ce406" - -vscode-extension-telemetry@0.0.15: - version "0.0.15" - resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.15.tgz#685c32f3b67e8fb85ba689c1d7f88ff90ff87856" - dependencies: - applicationinsights "1.0.1" - -vscode-jsonrpc@3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz#87239d9e166b2d7352245b8a813597804c1d63aa" - -vscode-languageclient@3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-3.5.1.tgz#c78e582459c24e58f88020dfa34065e976186a98" - dependencies: - vscode-languageserver-protocol "3.5.1" - -vscode-languageserver-protocol@3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.1.tgz#5144a3a9eeccbd83fe2745bd4ed75fad6cc45f0d" - dependencies: - vscode-jsonrpc "3.5.0" - vscode-languageserver-types "3.5.0" - -vscode-languageserver-types@3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz#e48d79962f0b8e02de955e3f524908e2b19c0374" - -vscode-languageserver@3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-3.5.1.tgz#e0044b7df4d2447ce12632dfc98f1ab0afacbdff" - dependencies: - vscode-languageserver-protocol "3.5.1" - vscode-uri "^1.0.1" - -vscode-uri@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-1.0.1.tgz#11a86befeac3c4aa3ec08623651a3c81a6d0bbc8" - -vscode-uri@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-1.0.3.tgz#631bdbf716dccab0e65291a8dc25c23232085a52" - -vscode@^1.1.5: - version "1.1.13" - resolved "https://registry.yarnpkg.com/vscode/-/vscode-1.1.13.tgz#dcea0c5f3ec1ff6eca333216b4b20dd994d18d9a" - dependencies: - glob "^7.1.2" - gulp-chmod "^2.0.0" - gulp-filter "^5.0.1" - gulp-gunzip "1.0.0" - gulp-remote-src "^0.4.3" - gulp-symdest "^1.1.0" - gulp-untar "^0.0.6" - gulp-vinyl-zip "^2.1.0" - mocha "^4.0.1" - request "^2.83.0" - semver "^5.4.1" - source-map-support "^0.5.0" - url-parse "^1.1.9" - vinyl-source-stream "^1.1.0" - -which@^1.1.1, which@^1.2.14: - version "1.3.0" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" - dependencies: - isexe "^2.0.0" - -which@~1.0.5: - version "1.0.9" - resolved "https://registry.yarnpkg.com/which/-/which-1.0.9.tgz#460c1da0f810103d0321a9b633af9e575e64486f" - -wide-align@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710" - dependencies: - string-width "^1.0.2" - -window-size@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" - -winreg@1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/winreg/-/winreg-1.2.4.tgz#ba065629b7a925130e15779108cf540990e98d1b" - -wordwrap@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" - -wordwrap@^1.0.0, wordwrap@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" - -wordwrap@~0.0.2: - version "0.0.3" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - -xml2js@0.2.8: - version "0.2.8" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.2.8.tgz#9b81690931631ff09d1957549faf54f4f980b3c2" - dependencies: - sax "0.5.x" - -xml2js@0.4.19: - version "0.4.19" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" - dependencies: - sax ">=0.6.0" - xmlbuilder "~9.0.1" - -xmlbuilder@0.4.3: - version "0.4.3" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-0.4.3.tgz#c4614ba74e0ad196e609c9272cd9e1ddb28a8a58" - -xmlbuilder@~9.0.1: - version "9.0.7" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" - -"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" - -xtend@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-3.0.0.tgz#5cce7407baf642cba7becda568111c493f59665a" - -yallist@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" - -yargs@~3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" - dependencies: - camelcase "^1.0.2" - cliui "^2.1.0" - decamelize "^1.0.0" - window-size "0.1.0" - -yauzl@^2.2.1, yauzl@^2.4.2: - version "2.9.1" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.9.1.tgz#a81981ea70a57946133883f029c5821a89359a7f" - dependencies: - buffer-crc32 "~0.2.3" - fd-slicer "~1.0.1" - -yazl@^2.2.1: - version "2.4.3" - resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.4.3.tgz#ec26e5cc87d5601b9df8432dbdd3cd2e5173a071" - dependencies: - buffer-crc32 "~0.2.3" - -zone.js@0.7.6: - version "0.7.6" - resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009" From fa948595c4605a0d1b5b85af6bfcee525cb142cb Mon Sep 17 00:00:00 2001 From: Bence Nagy Date: Thu, 7 Jun 2018 02:06:07 +0200 Subject: [PATCH 311/433] Add the `geventCompatible` launch configuration option (#1699) * Fix tslint errors brought in by typescript@2.8.3 * Add the `geventCompatible` launch configuration option This makes the experimental Python debugger work with projects using gevent's monkey patching. Closes https://github.com/Microsoft/vscode-python/issues/127 --- news/1 Enhancements/127.md | 1 + package.json | 16 ++++++++++++++++ package.nls.json | 2 ++ src/client/debugger/Common/Contracts.ts | 2 ++ src/client/debugger/DebugClients/helper.ts | 7 ++++++- 5 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 news/1 Enhancements/127.md diff --git a/news/1 Enhancements/127.md b/news/1 Enhancements/127.md new file mode 100644 index 000000000000..1bcda6fbb75d --- /dev/null +++ b/news/1 Enhancements/127.md @@ -0,0 +1 @@ +Add `gevent` launch configuration option to enable debugging of gevent monkey patched code. diff --git a/package.json b/package.json index cf1c76791481..2264d825db4a 100644 --- a/package.json +++ b/package.json @@ -818,6 +818,17 @@ "jinja": true } }, + { + "label": "Python Experimental: Gevent", + "description": "%python.snippet.launch.gevent.description%", + "body": { + "name": "Gevent", + "type": "pythonExperimental", + "request": "launch", + "program": "^\"\\${file}\"", + "gevent": true + } + }, { "label": "Python Experimental: PySpark", "description": "%python.snippet.launch.pyspark.description%", @@ -975,6 +986,11 @@ "description": "Debug standard library code.", "default": false }, + "gevent": { + "type": "boolean", + "description": "Enable debugging of gevent monkey-patched code.", + "default": false + }, "django": { "type": "boolean", "description": "Django debugging.", diff --git a/package.nls.json b/package.nls.json index e951092e3762..23adf86f4e0b 100644 --- a/package.nls.json +++ b/package.nls.json @@ -39,6 +39,8 @@ "python.snippet.launch.flask.description": "Debug a Flask Application", "python.snippet.launch.flaskOld.label": "Python: Flask (0.10.x or earlier)", "python.snippet.launch.flaskOld.description": "Debug an older styled Flask Application", + "python.snippet.launch.gevent.label": "Python: Gevent", + "python.snippet.launch.gevent.description": "Debug a Gevent Application", "python.snippet.launch.pyramid.label": "Python: Pyramid Application", "python.snippet.launch.pyramid.description": "Debug a Pyramid Application", "python.snippet.launch.watson.label": "Python: Watson Application", diff --git a/src/client/debugger/Common/Contracts.ts b/src/client/debugger/Common/Contracts.ts index 6806986feca6..567ed2eda866 100644 --- a/src/client/debugger/Common/Contracts.ts +++ b/src/client/debugger/Common/Contracts.ts @@ -61,6 +61,7 @@ export type DebuggerType = 'python' | 'pythonExperimental'; export interface AdditionalLaunchDebugOptions { redirectOutput?: boolean; django?: boolean; + gevent?: boolean; jinja?: boolean; debugStdLib?: boolean; sudo?: boolean; @@ -70,6 +71,7 @@ export interface AdditionalLaunchDebugOptions { export interface AdditionalAttachDebugOptions { redirectOutput?: boolean; django?: boolean; + gevent?: boolean; jinja?: boolean; debugStdLib?: boolean; } diff --git a/src/client/debugger/DebugClients/helper.ts b/src/client/debugger/DebugClients/helper.ts index 033427b5bdae..a9d9beb1fdab 100644 --- a/src/client/debugger/DebugClients/helper.ts +++ b/src/client/debugger/DebugClients/helper.ts @@ -10,7 +10,8 @@ export class DebugClientHelper { // Merge variables from both .env file and env json variables. const envFileVars = await this.envParser.parseFile(args.envFile); - const debugLaunchEnvVars = (args.env && Object.keys(args.env).length > 0) ? { ...args.env } as EnvironmentVariables : {}; + // tslint:disable-next-line:no-any + const debugLaunchEnvVars: {[key: string]: string} = (args.env && Object.keys(args.env).length > 0) ? { ...args.env } as any : {} as any; const env = envFileVars ? { ...envFileVars! } : {}; this.envParser.mergeVariables(debugLaunchEnvVars, env); @@ -51,6 +52,10 @@ export class DebugClientHelper { env.PYTHONUNBUFFERED = '1'; } + if (args.gevent) { + env.GEVENT_SUPPORT = 'True'; // this is read in pydevd_constants.py + } + return env; } } From 2a5bcafc2f1dc0822f2ccc4031b233361db22a5b Mon Sep 17 00:00:00 2001 From: Larry Li Date: Thu, 7 Jun 2018 10:06:16 +1000 Subject: [PATCH 312/433] Add setting for auto run test discover on save (#1687) --- news/1 Enhancements/1037.md | 2 ++ package.json | 6 ++++++ src/client/common/configSettings.ts | 4 ++-- src/client/common/types.ts | 1 + src/client/unittests/main.ts | 9 ++++++++- 5 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 news/1 Enhancements/1037.md diff --git a/news/1 Enhancements/1037.md b/news/1 Enhancements/1037.md new file mode 100644 index 000000000000..08e41c35ff60 --- /dev/null +++ b/news/1 Enhancements/1037.md @@ -0,0 +1,2 @@ +Add setting for auto run test discover on save +(thanks [Lingyu Li](http://github.com/lingyv-li/)) \ No newline at end of file diff --git a/package.json b/package.json index 2264d825db4a..41797da423c2 100644 --- a/package.json +++ b/package.json @@ -1722,6 +1722,12 @@ "description": "Use the experimental debugger when debugging unit tests.", "scope": "resource" }, + "python.unitTest.autoTestDiscoverOnSaveEnabled": { + "type": "boolean", + "default": true, + "description": "Whether to enable or disable auto run test discovery when saving a unit test file.", + "scope": "resource" + }, "python.venvFolders": { "type": "array", "default": [ diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index 73128e89ba18..d6d5c9bdcedb 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -267,7 +267,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { nosetestArgs: [], pyTestArgs: [], unittestArgs: [], promptToConfigure: true, debugPort: 3000, nosetestsEnabled: false, pyTestEnabled: false, unittestEnabled: false, - nosetestPath: 'nosetests', pyTestPath: 'pytest' + nosetestPath: 'nosetests', pyTestPath: 'pytest', autoTestDiscoverOnSaveEnabled: true } as IUnitTestSettings; } } @@ -278,7 +278,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { debugPort: 3000, nosetestArgs: [], nosetestPath: 'nosetest', nosetestsEnabled: false, pyTestArgs: [], pyTestEnabled: false, pyTestPath: 'pytest', - unittestArgs: [], unittestEnabled: false + unittestArgs: [], unittestEnabled: false, autoTestDiscoverOnSaveEnabled: true }; this.unitTest.pyTestPath = getAbsolutePath(systemVariables.resolveAny(this.unitTest.pyTestPath), workspaceRoot); this.unitTest.nosetestPath = getAbsolutePath(systemVariables.resolveAny(this.unitTest.nosetestPath), workspaceRoot); diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 5791899f6492..6cc808d62107 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -137,6 +137,7 @@ export interface IUnitTestSettings { unittestArgs: string[]; cwd?: string; readonly useExperimentalDebugger?: boolean; + readonly autoTestDiscoverOnSaveEnabled: boolean; } export interface IPylintCategorySeverity { readonly convention: DiagnosticSeverity; diff --git a/src/client/unittests/main.ts b/src/client/unittests/main.ts index 4dd1f63cfe3c..2f76ad448f9f 100644 --- a/src/client/unittests/main.ts +++ b/src/client/unittests/main.ts @@ -308,10 +308,17 @@ export class UnitTestManagementService implements IUnitTestManagementService, Di disposablesRegistry.push(...disposables); } + private onDocumentSaved(doc: TextDocument) { + const settings = this.serviceContainer.get(IConfigurationService).getSettings(doc.uri); + if (!settings.unitTest.autoTestDiscoverOnSaveEnabled) { + return; + } + this.discoverTestsForDocument(doc); + } private registerHandlers() { const documentManager = this.serviceContainer.get(IDocumentManager); - this.disposableRegistry.push(documentManager.onDidSaveTextDocument(this.discoverTestsForDocument.bind(this))); + this.disposableRegistry.push(documentManager.onDidSaveTextDocument(this.onDocumentSaved.bind(this))); this.disposableRegistry.push(this.workspaceService.onDidChangeConfiguration(e => { if (this.configChangedTimer) { clearTimeout(this.configChangedTimer); From 02d2c44138748a8ceac598385863de6f1025bfc2 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 6 Jun 2018 18:17:18 -0700 Subject: [PATCH 313/433] Resolve warnings in CI Tests and fix some broken CI Tests (#1886) --- news/3 Code Health/1885.md | 1 + src/client/common/configSettings.ts | 2 +- src/client/common/variables/environment.ts | 2 +- src/test/performance/load.perf.test.ts | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 news/3 Code Health/1885.md diff --git a/news/3 Code Health/1885.md b/news/3 Code Health/1885.md new file mode 100644 index 000000000000..ed55aae702af --- /dev/null +++ b/news/3 Code Health/1885.md @@ -0,0 +1 @@ +Resolve warnings in CI Tests and fix some broken CI Tests. diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index d6d5c9bdcedb..d15bfefee183 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -71,7 +71,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { if (!PythonSettings.pythonSettings.has(workspaceFolderKey)) { const settings = new PythonSettings(workspaceFolderUri); PythonSettings.pythonSettings.set(workspaceFolderKey, settings); - const formatOnType = workspace.getConfiguration('editor', resource).get('formatOnType', false); + const formatOnType = workspace.getConfiguration('editor', resource ? resource : null).get('formatOnType', false); sendTelemetryEvent(COMPLETION_ADD_BRACKETS, undefined, { enabled: settings.autoComplete.addBrackets }); sendTelemetryEvent(FORMAT_ON_TYPE, undefined, { enabled: formatOnType }); } diff --git a/src/client/common/variables/environment.ts b/src/client/common/variables/environment.ts index 99d2de6bbaf3..245d2f3c15e2 100644 --- a/src/client/common/variables/environment.ts +++ b/src/client/common/variables/environment.ts @@ -22,7 +22,7 @@ export class EnvironmentVariablesService implements IEnvironmentVariablesService if (!fs.lstatSync(filePath).isFile()) { return undefined; } - return dotenv.parse(filePath); + return dotenv.parse(await fs.readFile(filePath)); } public mergeVariables(source: EnvironmentVariables, target: EnvironmentVariables) { if (!target) { diff --git a/src/test/performance/load.perf.test.ts b/src/test/performance/load.perf.test.ts index d68ce859d597..a7d54adff6f0 100644 --- a/src/test/performance/load.perf.test.ts +++ b/src/test/performance/load.perf.test.ts @@ -23,7 +23,7 @@ suite('Activation Times', () => { } test(`Capture Extension Activation Times (Version: ${process.env.ACTIVATION_TIMES_EXT_VERSION}, sample: ${sampleCounter})`, async () => { const pythonExtension = extensions.getExtension('ms-python.python'); - if (pythonExtension) { + if (!pythonExtension) { throw new Error('Python Extension not found'); } const stopWatch = new StopWatch(); From 99c0df53a4dd22250118c9e76111aebe29699799 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 6 Jun 2018 19:45:01 -0700 Subject: [PATCH 314/433] Reduce test time by lowering the sample count for perf tests (#1888) --- news/3 Code Health/1887.md | 1 + src/test/performance/load.perf.test.ts | 2 +- src/test/performanceTest.ts | 6 +++++- 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 news/3 Code Health/1887.md diff --git a/news/3 Code Health/1887.md b/news/3 Code Health/1887.md new file mode 100644 index 000000000000..f9e26d3bd5c4 --- /dev/null +++ b/news/3 Code Health/1887.md @@ -0,0 +1 @@ +Reduce sample count used to capture performance metrics in order to reduce time taken to complete the tests. diff --git a/src/test/performance/load.perf.test.ts b/src/test/performance/load.perf.test.ts index a7d54adff6f0..3c6ef65c11cb 100644 --- a/src/test/performance/load.perf.test.ts +++ b/src/test/performance/load.perf.test.ts @@ -18,7 +18,7 @@ suite('Activation Times', () => { if (process.env.ACTIVATION_TIMES_LOG_FILE_PATH) { const logFile = process.env.ACTIVATION_TIMES_LOG_FILE_PATH; const sampleCounter = fs.existsSync(logFile) ? fs.readFileSync(logFile, { encoding: 'utf8' }).toString().split(/\r?\n/g).length : 1; - if (sampleCounter > 10) { + if (sampleCounter > 5) { return; } test(`Capture Extension Activation Times (Version: ${process.env.ACTIVATION_TIMES_EXT_VERSION}, sample: ${sampleCounter})`, async () => { diff --git a/src/test/performanceTest.ts b/src/test/performanceTest.ts index c84adbc93ce8..781841960a7d 100644 --- a/src/test/performanceTest.ts +++ b/src/test/performanceTest.ts @@ -38,7 +38,7 @@ class TestRunner { await del([path.join(tmpFolder, '**')]); await this.extractLatestExtension(publishedExtensionPath); - const timesToLoadEachVersion = 3; + const timesToLoadEachVersion = 2; const devLogFiles: string[] = []; const releaseLogFiles: string[] = []; const newAnalysisEngineLogFiles: string[] = []; @@ -47,20 +47,24 @@ class TestRunner { await this.enableNewAnalysisEngine(false); const devLogFile = path.join(logFilesPath, `dev_loadtimes${i}.txt`); + console.log(`Start Performance Tests: Counter ${i}, for Dev version with Jedi`); await this.capturePerfTimes(Version.Dev, devLogFile); devLogFiles.push(devLogFile); const releaseLogFile = path.join(logFilesPath, `release_loadtimes${i}.txt`); + console.log(`Start Performance Tests: Counter ${i}, for Release version with Jedi`); await this.capturePerfTimes(Version.Release, releaseLogFile); releaseLogFiles.push(releaseLogFile); // New Analysis engine. await this.enableNewAnalysisEngine(true); const newAnalysisEngineLogFile = path.join(logFilesPath, `newAnalysisEngine_loadtimes${i}.txt`); + console.log(`Start Performance Tests: Counter ${i}, for Release version with Analysis Engine`); await this.capturePerfTimes(Version.Release, newAnalysisEngineLogFile); newAnalysisEngineLogFiles.push(newAnalysisEngineLogFile); } + console.log('Compare Performance Results'); await this.runPerfTest(devLogFiles, releaseLogFiles, newAnalysisEngineLogFiles); } private async enableNewAnalysisEngine(enable: boolean) { From 5f0070287457f2df31848ecdec7ab3aa05fdc770 Mon Sep 17 00:00:00 2001 From: Derek Keeler Date: Wed, 6 Jun 2018 20:29:39 -0700 Subject: [PATCH 315/433] Updates to envvars for CI build to support VSTS automation (#1872) Check for the existance of TF_BUILD and set IS_VSTS and IS_CI_SERVER if it is there. If IS_VSTS is set, turn colourization of the output off as the VSTS build agent output cannot deal with that yet. --- src/test/constants.ts | 12 ++++++------ src/test/index.ts | 7 ++++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/test/constants.ts b/src/test/constants.ts index 72f6dcf1b696..f60cf9ac50b0 100644 --- a/src/test/constants.ts +++ b/src/test/constants.ts @@ -1,16 +1,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -// tslint:disable:no-string-literal import { workspace } from 'vscode'; import { PythonSettings } from '../client/common/configSettings'; -export const IS_APPVEYOR = process.env['APPVEYOR'] === 'true'; -export const IS_TRAVIS = process.env['TRAVIS'] === 'true'; -export const IS_CI_SERVER = IS_TRAVIS || IS_APPVEYOR; +export const IS_APPVEYOR = process.env.APPVEYOR === 'true'; +export const IS_TRAVIS = process.env.TRAVIS === 'true'; +export const IS_VSTS = process.env.TF_BUILD !== undefined; +export const IS_CI_SERVER = IS_TRAVIS || IS_APPVEYOR || IS_VSTS; export const TEST_TIMEOUT = 25000; export const IS_MULTI_ROOT_TEST = isMultitrootTest(); -export const IS_CI_SERVER_TEST_DEBUGGER = process.env['IS_CI_SERVER_TEST_DEBUGGER'] === '1'; +export const IS_CI_SERVER_TEST_DEBUGGER = process.env.IS_CI_SERVER_TEST_DEBUGGER === '1'; // If running on CI server, then run debugger tests ONLY if the corresponding flag is enabled. export const TEST_DEBUGGER = IS_CI_SERVER ? IS_CI_SERVER_TEST_DEBUGGER : true; @@ -19,4 +19,4 @@ function isMultitrootTest() { } export const IS_ANALYSIS_ENGINE_TEST = - !IS_TRAVIS && (process.env['VSC_PYTHON_ANALYSIS'] === '1' || !PythonSettings.getInstance().jediEnabled); + !IS_TRAVIS && (process.env.VSC_PYTHON_ANALYSIS === '1' || !PythonSettings.getInstance().jediEnabled); diff --git a/src/test/index.ts b/src/test/index.ts index c80bdf385aac..da440f03485a 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -3,7 +3,9 @@ if ((Reflect as any).metadata === undefined) { // tslint:disable-next-line:no-require-imports no-var-requires require('reflect-metadata'); } -import { IS_CI_SERVER, IS_CI_SERVER_TEST_DEBUGGER, IS_MULTI_ROOT_TEST } from './constants'; + +import { IS_CI_SERVER, IS_CI_SERVER_TEST_DEBUGGER, + IS_MULTI_ROOT_TEST, IS_VSTS } from './constants'; import * as testRunner from './testRunner'; process.env.VSC_PYTHON_CI_TEST = '1'; @@ -13,7 +15,6 @@ process.env.IS_MULTI_ROOT_TEST = IS_MULTI_ROOT_TEST.toString(); // We do this to ensure we only run debugger test, as debugger tests are very flaky on CI. // So the solution is to run them separately and first on CI. const grep = IS_CI_SERVER && IS_CI_SERVER_TEST_DEBUGGER ? 'Debug' : undefined; - const testFilesSuffix = process.env.TEST_FILES_SUFFIX; // You can directly control Mocha options by uncommenting the following lines. @@ -21,7 +22,7 @@ const testFilesSuffix = process.env.TEST_FILES_SUFFIX; // Hack, as retries is not supported as setting in tsd. const options: testRunner.SetupOptions & { retries: number } = { ui: 'tdd', - useColors: true, + useColors: !IS_VSTS, timeout: 25000, retries: 3, grep, From 992509241257961375e894b2f3796dc73bf1b284 Mon Sep 17 00:00:00 2001 From: Shiming Ge Date: Fri, 8 Jun 2018 00:07:21 +0800 Subject: [PATCH 316/433] update text to test (#1889) --- .github/test_plan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/test_plan.md b/.github/test_plan.md index 41b8c7fc97bd..99b26b971f09 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -176,7 +176,7 @@ def foo():pass **ALWAYS**: - Test the current debugger -- Text the experimental debugger (and note whether it is _at least_ as fast as the old debugger) +- Test the experimental debugger (and note whether it is _at least_ as fast as the old debugger) - [ ] [Configurations](https://code.visualstudio.com/docs/python/debugging#_debugging-specific-app-types) work - [ ] `Current File` From 4006d7a388fa7e5beefa9d7074e651fb719fe5fe Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 7 Jun 2018 09:20:49 -0700 Subject: [PATCH 317/433] Update the zh-cn translation --- package.nls.zh-cn.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/package.nls.zh-cn.json b/package.nls.zh-cn.json index b884240efa16..45ec545f83b2 100644 --- a/package.nls.zh-cn.json +++ b/package.nls.zh-cn.json @@ -1,6 +1,7 @@ { "python.command.python.sortImports.title": "排序 import 语句", "python.command.python.startREPL.title": "启动 REPL", + "python.command.python.createTerminal.title": "创建终端", "python.command.python.buildWorkspaceSymbols.title": "构建工作区符号", "python.command.python.runtests.title": "运行所有单元测试", "python.command.python.debugtests.title": "调试所有单元测试", @@ -15,9 +16,14 @@ "python.command.python.selectAndRunTestFile.title": "运行单元测试文件...", "python.command.python.runCurrentTestFile.title": "运行当前单元测试文件", "python.command.python.runFailedTests.title": "运行失败的单元测试", + "python.command.python.discoverTests.title": "检测单元测试", "python.command.python.execSelectionInTerminal.title": "在 Python 终端中运行选定内容/行", "python.command.python.execSelectionInDjangoShell.title": "在 Django Shell 中运行选定内容/行", "python.command.python.goToPythonObject.title": "转到 Python 对象", + "python.command.python.setLinter.title": "选择 Linter 插件", + "python.command.python.enableLinting.title": "启用 Linting", + "python.command.python.runLinting.title": "运行 Linting", + "python.snippet.launch.standard.label": "Python: 当前文件", "python.snippet.launch.standard.label": "Python: Current File", "python.snippet.launch.standard.description": "使用标准输出调试 Python 应用", "python.snippet.launch.pyspark.label": "Python: PySpark", @@ -34,6 +40,8 @@ "python.snippet.launch.flask.description": "调试 Flask 应用", "python.snippet.launch.flaskOld.label": "Python: Flask (0.10.x 或之前)", "python.snippet.launch.flaskOld.description": "调试旧式 Flask 应用", + "python.snippet.launch.gevent.label": "Python: Gevent 应用", + "python.snippet.launch.gevent.description": "调试 Gevent 应用", "python.snippet.launch.pyramid.label": "Python: Pyramid 应用", "python.snippet.launch.pyramid.description": "调试 Pyramid 应用", "python.snippet.launch.watson.label": "Python: Watson 应用", From 70bc513c708366793b931a7a32aff3160df839df Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 7 Jun 2018 09:22:48 -0700 Subject: [PATCH 318/433] Fix whitespace in zh-cn translation Apparently copying and pasting from O365 Excel is a little funky. --- package.nls.zh-cn.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/package.nls.zh-cn.json b/package.nls.zh-cn.json index 45ec545f83b2..4580d1fa4b10 100644 --- a/package.nls.zh-cn.json +++ b/package.nls.zh-cn.json @@ -1,7 +1,7 @@ { "python.command.python.sortImports.title": "排序 import 语句", "python.command.python.startREPL.title": "启动 REPL", - "python.command.python.createTerminal.title": "创建终端", + "python.command.python.createTerminal.title": "创建终端", "python.command.python.buildWorkspaceSymbols.title": "构建工作区符号", "python.command.python.runtests.title": "运行所有单元测试", "python.command.python.debugtests.title": "调试所有单元测试", @@ -16,14 +16,14 @@ "python.command.python.selectAndRunTestFile.title": "运行单元测试文件...", "python.command.python.runCurrentTestFile.title": "运行当前单元测试文件", "python.command.python.runFailedTests.title": "运行失败的单元测试", - "python.command.python.discoverTests.title": "检测单元测试", + "python.command.python.discoverTests.title": "检测单元测试", "python.command.python.execSelectionInTerminal.title": "在 Python 终端中运行选定内容/行", "python.command.python.execSelectionInDjangoShell.title": "在 Django Shell 中运行选定内容/行", "python.command.python.goToPythonObject.title": "转到 Python 对象", - "python.command.python.setLinter.title": "选择 Linter 插件", - "python.command.python.enableLinting.title": "启用 Linting", - "python.command.python.runLinting.title": "运行 Linting", - "python.snippet.launch.standard.label": "Python: 当前文件", + "python.command.python.setLinter.title": "选择 Linter 插件", + "python.command.python.enableLinting.title": "启用 Linting", + "python.command.python.runLinting.title": "运行 Linting", + "python.snippet.launch.standard.label": "Python: 当前文件", "python.snippet.launch.standard.label": "Python: Current File", "python.snippet.launch.standard.description": "使用标准输出调试 Python 应用", "python.snippet.launch.pyspark.label": "Python: PySpark", @@ -40,8 +40,8 @@ "python.snippet.launch.flask.description": "调试 Flask 应用", "python.snippet.launch.flaskOld.label": "Python: Flask (0.10.x 或之前)", "python.snippet.launch.flaskOld.description": "调试旧式 Flask 应用", - "python.snippet.launch.gevent.label": "Python: Gevent 应用", - "python.snippet.launch.gevent.description": "调试 Gevent 应用", + "python.snippet.launch.gevent.label": "Python: Gevent 应用", + "python.snippet.launch.gevent.description": "调试 Gevent 应用", "python.snippet.launch.pyramid.label": "Python: Pyramid 应用", "python.snippet.launch.pyramid.description": "调试 Pyramid 应用", "python.snippet.launch.watson.label": "Python: Watson 应用", From 8203ba0e0830e63f007bca02b39eb3b60b28d3a4 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 7 Jun 2018 11:40:47 -0700 Subject: [PATCH 319/433] Ensure resource is passed into isInstalled method (#1895) Ensure resource is passed into isInstalled method --- news/3 Code Health/1893.md | 1 + .../common/installer/productInstaller.ts | 48 +++++++++---------- 2 files changed, 25 insertions(+), 24 deletions(-) create mode 100644 news/3 Code Health/1893.md diff --git a/news/3 Code Health/1893.md b/news/3 Code Health/1893.md new file mode 100644 index 000000000000..88cf91a7e1b0 --- /dev/null +++ b/news/3 Code Health/1893.md @@ -0,0 +1 @@ +Ensure workspace information is passed into installer when determining whether a product/tool is installed. diff --git a/src/client/common/installer/productInstaller.ts b/src/client/common/installer/productInstaller.ts index 8e3bde192995..fa8c72aae939 100644 --- a/src/client/common/installer/productInstaller.ts +++ b/src/client/common/installer/productInstaller.ts @@ -1,7 +1,7 @@ import { inject, injectable, named } from 'inversify'; import * as os from 'os'; import * as path from 'path'; -import * as vscode from 'vscode'; +import { OutputChannel, Uri } from 'vscode'; import '../../common/extensions'; import { IFormatterHelper } from '../../formatters/types'; import { IServiceContainer } from '../../ioc/types'; @@ -35,13 +35,13 @@ export abstract class BaseInstaller { protected configService: IConfigurationService; private readonly workspaceService: IWorkspaceService; - constructor(protected serviceContainer: IServiceContainer, protected outputChannel: vscode.OutputChannel) { + constructor(protected serviceContainer: IServiceContainer, protected outputChannel: OutputChannel) { this.appShell = serviceContainer.get(IApplicationShell); this.configService = serviceContainer.get(IConfigurationService); this.workspaceService = serviceContainer.get(IWorkspaceService); } - public promptToInstall(product: Product, resource?: vscode.Uri): Promise { + public promptToInstall(product: Product, resource?: Uri): Promise { // If this method gets called twice, while previous promise has not been resolved, then return that same promise. // E.g. previous promise is not resolved as a message has been displayed to the user, so no point displaying // another message. @@ -58,7 +58,7 @@ export abstract class BaseInstaller { return promise; } - public async install(product: Product, resource?: vscode.Uri): Promise { + public async install(product: Product, resource?: Uri): Promise { if (product === Product.unittest) { return InstallerResponse.Installed; } @@ -74,11 +74,11 @@ export abstract class BaseInstaller { await installer.installModule(moduleName, resource) .catch(logger.logError.bind(logger, `Error in installing the module '${moduleName}'`)); - return this.isInstalled(product) + return this.isInstalled(product, resource) .then(isInstalled => isInstalled ? InstallerResponse.Installed : InstallerResponse.Ignore); } - public async isInstalled(product: Product, resource?: vscode.Uri): Promise { + public async isInstalled(product: Product, resource?: Uri): Promise { if (product === Product.unittest) { return true; } @@ -102,18 +102,18 @@ export abstract class BaseInstaller { .catch(() => false); } } - protected abstract promptToInstallImplementation(product: Product, resource?: vscode.Uri): Promise; - protected getExecutableNameFromSettings(product: Product, resource?: vscode.Uri): string { + protected abstract promptToInstallImplementation(product: Product, resource?: Uri): Promise; + protected getExecutableNameFromSettings(product: Product, resource?: Uri): string { throw new Error('getExecutableNameFromSettings is not supported on this object'); } } export class CTagsInstaller extends BaseInstaller { - constructor(serviceContainer: IServiceContainer, outputChannel: vscode.OutputChannel) { + constructor(serviceContainer: IServiceContainer, outputChannel: OutputChannel) { super(serviceContainer, outputChannel); } - public async install(product: Product, resource?: vscode.Uri): Promise { + public async install(product: Product, resource?: Uri): Promise { if (this.serviceContainer.get(IPlatformService).isWindows) { this.outputChannel.appendLine('Install Universal Ctags Win32 to enable support for Workspace Symbols'); this.outputChannel.appendLine('Download the CTags binary from the Universal CTags site.'); @@ -129,19 +129,19 @@ export class CTagsInstaller extends BaseInstaller { } return InstallerResponse.Ignore; } - protected async promptToInstallImplementation(product: Product, resource?: vscode.Uri): Promise { + protected async promptToInstallImplementation(product: Product, resource?: Uri): Promise { const item = await this.appShell.showErrorMessage('Install CTags to enable Python workspace symbols?', 'Yes', 'No'); return item === 'Yes' ? this.install(product, resource) : InstallerResponse.Ignore; } - protected getExecutableNameFromSettings(product: Product, resource?: vscode.Uri): string { + protected getExecutableNameFromSettings(product: Product, resource?: Uri): string { const settings = this.configService.getSettings(resource); return settings.workspaceSymbols.ctagsPath; } } export class FormatterInstaller extends BaseInstaller { - protected async promptToInstallImplementation(product: Product, resource?: vscode.Uri): Promise { + protected async promptToInstallImplementation(product: Product, resource?: Uri): Promise { // Hard-coded on purpose because the UI won't necessarily work having // another formatter. const formatters = [Product.autopep8, Product.black, Product.yapf]; @@ -168,7 +168,7 @@ export class FormatterInstaller extends BaseInstaller { return InstallerResponse.Ignore; } - protected getExecutableNameFromSettings(product: Product, resource?: vscode.Uri): string { + protected getExecutableNameFromSettings(product: Product, resource?: Uri): string { const settings = this.configService.getSettings(resource); const formatHelper = this.serviceContainer.get(IFormatterHelper); const settingsPropNames = formatHelper.getSettingsPropertyNames(product); @@ -178,7 +178,7 @@ export class FormatterInstaller extends BaseInstaller { // tslint:disable-next-line:max-classes-per-file export class LinterInstaller extends BaseInstaller { - protected async promptToInstallImplementation(product: Product, resource?: vscode.Uri): Promise { + protected async promptToInstallImplementation(product: Product, resource?: Uri): Promise { const productName = ProductNames.get(product)!; const install = 'Install'; const disableAllLinting = 'Disable linting'; @@ -199,7 +199,7 @@ export class LinterInstaller extends BaseInstaller { } return InstallerResponse.Ignore; } - protected getExecutableNameFromSettings(product: Product, resource?: vscode.Uri): string { + protected getExecutableNameFromSettings(product: Product, resource?: Uri): string { const linterManager = this.serviceContainer.get(ILinterManager); return linterManager.getLinterInfo(product).pathName(resource); } @@ -207,13 +207,13 @@ export class LinterInstaller extends BaseInstaller { // tslint:disable-next-line:max-classes-per-file export class TestFrameworkInstaller extends BaseInstaller { - protected async promptToInstallImplementation(product: Product, resource?: vscode.Uri): Promise { + protected async promptToInstallImplementation(product: Product, resource?: Uri): Promise { const productName = ProductNames.get(product)!; const item = await this.appShell.showErrorMessage(`Test framework ${productName} is not installed. Install?`, 'Yes', 'No'); return item === 'Yes' ? this.install(product, resource) : InstallerResponse.Ignore; } - protected getExecutableNameFromSettings(product: Product, resource?: vscode.Uri): string { + protected getExecutableNameFromSettings(product: Product, resource?: Uri): string { const testHelper = this.serviceContainer.get(ITestsHelper); const settingsPropNames = testHelper.getSettingsPropertyNames(product); if (!settingsPropNames.pathName) { @@ -227,12 +227,12 @@ export class TestFrameworkInstaller extends BaseInstaller { // tslint:disable-next-line:max-classes-per-file export class RefactoringLibraryInstaller extends BaseInstaller { - protected async promptToInstallImplementation(product: Product, resource?: vscode.Uri): Promise { + protected async promptToInstallImplementation(product: Product, resource?: Uri): Promise { const productName = ProductNames.get(product)!; const item = await this.appShell.showErrorMessage(`Refactoring library ${productName} is not installed. Install?`, 'Yes', 'No'); return item === 'Yes' ? this.install(product, resource) : InstallerResponse.Ignore; } - protected getExecutableNameFromSettings(product: Product, resource?: vscode.Uri): string { + protected getExecutableNameFromSettings(product: Product, resource?: Uri): string { return translateProductToModule(product, ModuleNamePurpose.run); } } @@ -243,7 +243,7 @@ export class ProductInstaller implements IInstaller { private ProductTypes = new Map(); constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer, - @inject(IOutputChannel) @named(STANDARD_OUTPUT_CHANNEL) private outputChannel: vscode.OutputChannel) { + @inject(IOutputChannel) @named(STANDARD_OUTPUT_CHANNEL) private outputChannel: OutputChannel) { this.ProductTypes.set(Product.flake8, ProductType.Linter); this.ProductTypes.set(Product.mypy, ProductType.Linter); this.ProductTypes.set(Product.pep8, ProductType.Linter); @@ -263,13 +263,13 @@ export class ProductInstaller implements IInstaller { // tslint:disable-next-line:no-empty public dispose() { } - public async promptToInstall(product: Product, resource?: vscode.Uri): Promise { + public async promptToInstall(product: Product, resource?: Uri): Promise { return this.createInstaller(product).promptToInstall(product, resource); } - public async install(product: Product, resource?: vscode.Uri): Promise { + public async install(product: Product, resource?: Uri): Promise { return this.createInstaller(product).install(product, resource); } - public async isInstalled(product: Product, resource?: vscode.Uri): Promise { + public async isInstalled(product: Product, resource?: Uri): Promise { return this.createInstaller(product).isInstalled(product, resource); } public translateProductToModuleName(product: Product, purpose: ModuleNamePurpose): string { From 15c24d1d9661644388304d4473a7aabd1649672e Mon Sep 17 00:00:00 2001 From: Derek Keeler Date: Thu, 7 Jun 2018 19:56:40 -0700 Subject: [PATCH 320/433] Enable JUnit report output during testRunner processing. (#1901) Reporting support for VSTS primarily. - Add reporter & reporterOptions to testRunner - Add support for JUnit output file --- news/3 Code Health/1897.md | 1 + package-lock.json | 36 ++++++++++++++++++++++++++++++++++++ package.json | 3 ++- src/test/constants.ts | 6 ++++++ src/test/index.ts | 22 ++++++++++++++++++++-- src/test/testRunner.ts | 10 +++++++++- 6 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 news/3 Code Health/1897.md diff --git a/news/3 Code Health/1897.md b/news/3 Code Health/1897.md new file mode 100644 index 000000000000..57d93b45447a --- /dev/null +++ b/news/3 Code Health/1897.md @@ -0,0 +1 @@ +Add JUnit file output to enable CI integration with VSTS. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 698b873fd15b..f6c9ff778c7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6586,6 +6586,36 @@ } } }, + "mocha-junit-reporter": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/mocha-junit-reporter/-/mocha-junit-reporter-1.17.0.tgz", + "integrity": "sha1-LlFJ7UD8XS48px5C21qx/snG2Fw=", + "dev": true, + "requires": { + "debug": "^2.2.0", + "md5": "^2.1.0", + "mkdirp": "~0.5.1", + "strip-ansi": "^4.0.0", + "xml": "^1.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -9443,6 +9473,12 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=", + "dev": true + }, "xml2js": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", diff --git a/package.json b/package.json index 41797da423c2..8e1f8c845a52 100644 --- a/package.json +++ b/package.json @@ -1926,8 +1926,8 @@ "@types/chai-arrays": "^1.0.2", "@types/chai-as-promised": "^7.1.0", "@types/del": "^3.0.0", - "@types/download": "^6.2.2", "@types/dotenv": "^4.0.3", + "@types/download": "^6.2.2", "@types/event-stream": "^3.3.33", "@types/fs-extra": "^5.0.1", "@types/get-port": "^3.2.0", @@ -1971,6 +1971,7 @@ "is-running": "^2.1.0", "istanbul": "^0.4.5", "mocha": "^5.0.4", + "mocha-junit-reporter": "^1.17.0", "node-has-native-dependencies": "^1.0.2", "relative": "^3.0.2", "remap-istanbul": "^0.10.1", diff --git a/src/test/constants.ts b/src/test/constants.ts index f60cf9ac50b0..733fad899506 100644 --- a/src/test/constants.ts +++ b/src/test/constants.ts @@ -8,6 +8,12 @@ export const IS_APPVEYOR = process.env.APPVEYOR === 'true'; export const IS_TRAVIS = process.env.TRAVIS === 'true'; export const IS_VSTS = process.env.TF_BUILD !== undefined; export const IS_CI_SERVER = IS_TRAVIS || IS_APPVEYOR || IS_VSTS; + +// allow the CI server to specify JUnit output... +export const MOCHA_REPORTER_JUNIT: boolean = IS_CI_SERVER && process.env.MOCHA_REPORTER_JUNIT !== undefined; +export const MOCHA_CI_REPORTFILE: string = MOCHA_REPORTER_JUNIT && process.env.MOCHA_CI_REPORTFILE !== undefined ? process.env.MOCHA_CI_REPORTFILE.toString() : './junit-out.xml'; +export const MOCHA_CI_PROPERTIES: string = MOCHA_REPORTER_JUNIT && process.env.MOCHA_CI_PROPERTIES !== undefined ? process.env.MOCHA_CI_PROPERTIES.toString() : ''; + export const TEST_TIMEOUT = 25000; export const IS_MULTI_ROOT_TEST = isMultitrootTest(); export const IS_CI_SERVER_TEST_DEBUGGER = process.env.IS_CI_SERVER_TEST_DEBUGGER === '1'; diff --git a/src/test/index.ts b/src/test/index.ts index da440f03485a..da4866260751 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -5,7 +5,8 @@ if ((Reflect as any).metadata === undefined) { } import { IS_CI_SERVER, IS_CI_SERVER_TEST_DEBUGGER, - IS_MULTI_ROOT_TEST, IS_VSTS } from './constants'; + IS_MULTI_ROOT_TEST, IS_VSTS, MOCHA_CI_PROPERTIES, + MOCHA_CI_REPORTFILE, MOCHA_REPORTER_JUNIT } from './constants'; import * as testRunner from './testRunner'; process.env.VSC_PYTHON_CI_TEST = '1'; @@ -22,11 +23,28 @@ const testFilesSuffix = process.env.TEST_FILES_SUFFIX; // Hack, as retries is not supported as setting in tsd. const options: testRunner.SetupOptions & { retries: number } = { ui: 'tdd', - useColors: !IS_VSTS, + useColors: true, timeout: 25000, retries: 3, grep, testFilesSuffix }; + +// VSTS CI doesn't display colours correctly (yet). +if (IS_VSTS) { + options.useColors = false; +} + +// CI can ask for a JUnit reporter if the environment variable +// 'MOCHA_REPORTER_JUNIT' is defined, further control is afforded +// by other 'MOCHA_CI_...' variables. See constants.ts for info. +if (MOCHA_REPORTER_JUNIT) { + options.reporter = 'mocha-junit-reporter'; + options.reporterOptions = { + mochaFile: MOCHA_CI_REPORTFILE, + properties: MOCHA_CI_PROPERTIES + }; +} + testRunner.configure(options, { coverageConfig: '../coverconfig.json' }); module.exports = testRunner; diff --git a/src/test/testRunner.ts b/src/test/testRunner.ts index 7c90ef5f1a13..a30c80b11bbc 100644 --- a/src/test/testRunner.ts +++ b/src/test/testRunner.ts @@ -49,7 +49,15 @@ let mocha = new Mocha({ useColors: true }); -export type SetupOptions = MochaSetupOptions & { testFilesSuffix?: string }; +export type SetupOptions = MochaSetupOptions & { + testFilesSuffix?: string; + reporter?: string; + reporterOptions?: { + mochaFile?: string; + properties?: string; + }; +}; + let testFilesGlob = 'test'; let coverageOptions: { coverageConfig: string } | undefined; From d3fd6d8b11648d01bd43b3d0a12042cc17df1b05 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 8 Jun 2018 00:57:10 -0700 Subject: [PATCH 321/433] Use a glob pattern to look for conda executables (#1904) Fixes #256 --- .appveyor.yml | 2 +- news/3 Code Health/256.md | 1 + package-lock.json | 15 ++++-------- package.json | 1 + src/client/common/platform/fileSystem.ts | 11 +++++++++ src/client/common/platform/types.ts | 1 + .../locators/services/condaService.ts | 11 ++++----- src/test/common/platform/filesystem.test.ts | 8 +++++++ src/test/interpreters/condaService.test.ts | 24 +++++++++++-------- 9 files changed, 46 insertions(+), 28 deletions(-) create mode 100644 news/3 Code Health/256.md diff --git a/.appveyor.yml b/.appveyor.yml index 10e71d558beb..88aa597ac85e 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -72,7 +72,7 @@ init: install: - ps: Install-Product node $env:nodejs_version - - npm ci + - npm i - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - python -m pip install -U pip - pip install -t ./pythonFiles/experimental/ptvsd git+https://github.com/Microsoft/ptvsd/ diff --git a/news/3 Code Health/256.md b/news/3 Code Health/256.md new file mode 100644 index 000000000000..60e5ca2d1222 --- /dev/null +++ b/news/3 Code Health/256.md @@ -0,0 +1 @@ +Use a glob pattern to look for `conda` executables. diff --git a/package-lock.json b/package-lock.json index f6c9ff778c7a..d8683fad54c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2648,8 +2648,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "1.2.4", @@ -3262,7 +3261,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -5269,7 +5267,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -5278,8 +5275,7 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { "version": "1.3.5", @@ -6925,7 +6921,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -7136,8 +7131,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { "version": "1.0.2", @@ -9470,8 +9464,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "xml": { "version": "1.0.1", diff --git a/package.json b/package.json index 8e1f8c845a52..e870c4de7ae8 100644 --- a/package.json +++ b/package.json @@ -1891,6 +1891,7 @@ "fs-extra": "4.0.3", "fuzzy": "0.1.3", "get-port": "3.2.0", + "glob": "^7.1.2", "iconv-lite": "0.4.21", "inversify": "4.11.1", "line-by-line": "0.1.6", diff --git a/src/client/common/platform/fileSystem.ts b/src/client/common/platform/fileSystem.ts index fd409584e00d..645aad5a2577 100644 --- a/src/client/common/platform/fileSystem.ts +++ b/src/client/common/platform/fileSystem.ts @@ -4,6 +4,7 @@ import { createHash } from 'crypto'; import * as fs from 'fs-extra'; +import * as glob from 'glob'; import { inject, injectable } from 'inversify'; import * as path from 'path'; import { createDeferred } from '../helpers'; @@ -130,4 +131,14 @@ export class FileSystem implements IFileSystem { }); }); } + public search(globPattern: string): Promise { + return new Promise((resolve, reject) => { + glob(globPattern, (ex, files) => { + if (ex) { + return reject(ex); + } + resolve(Array.isArray(files) ? files : []); + }); + }); + } } diff --git a/src/client/common/platform/types.ts b/src/client/common/platform/types.ts index d3a7fe118505..28b2af9ce5b2 100644 --- a/src/client/common/platform/types.ts +++ b/src/client/common/platform/types.ts @@ -47,4 +47,5 @@ export interface IFileSystem { copyFile(src: string, dest: string): Promise; deleteFile(filename: string): Promise; getFileHash(filePath: string): Promise; + search(globPattern: string): Promise; } diff --git a/src/client/interpreter/locators/services/condaService.ts b/src/client/interpreter/locators/services/condaService.ts index 58fe10a45045..ee7a1f5b780b 100644 --- a/src/client/interpreter/locators/services/condaService.ts +++ b/src/client/interpreter/locators/services/condaService.ts @@ -11,9 +11,9 @@ import { CondaHelper } from './condaHelper'; // tslint:disable-next-line:no-require-imports no-var-requires const untildify: (value: string) => string = require('untildify'); -export const KNOWN_CONDA_LOCATIONS = ['~/anaconda/bin/conda', '~/miniconda/bin/conda', - '~/anaconda2/bin/conda', '~/miniconda2/bin/conda', - '~/anaconda3/bin/conda', '~/miniconda3/bin/conda']; +// This glob pattern will match all of the following: +// ~/anaconda/bin/conda, ~/anaconda3/bin/conda, ~/miniconda/bin/conda, ~/miniconda3/bin/conda +export const CondaLocationsGlob = '~/*conda*/bin/conda'; @injectable() export class CondaService implements ICondaService { @@ -181,9 +181,8 @@ export class CondaService implements ICondaService { return this.getCondaFileFromKnownLocations(); } private async getCondaFileFromKnownLocations(): Promise { - const condaFiles = await Promise.all(KNOWN_CONDA_LOCATIONS - .map(untildify) - .map(async (condaPath: string) => this.fileSystem.fileExists(condaPath).then(exists => exists ? condaPath : ''))); + const condaFiles = await this.fileSystem.search(untildify(CondaLocationsGlob)) + .catch(() => []); const validCondaFiles = condaFiles.filter(condaPath => condaPath.length > 0); return validCondaFiles.length === 0 ? 'conda' : validCondaFiles[0]; diff --git a/src/test/common/platform/filesystem.test.ts b/src/test/common/platform/filesystem.test.ts index 0e77a631f2c4..b434bd5d9c96 100644 --- a/src/test/common/platform/filesystem.test.ts +++ b/src/test/common/platform/filesystem.test.ts @@ -77,4 +77,12 @@ suite('FileSystem', () => { const fileContents = await fileSystem.readFile(fileToAppendTo); expect(fileContents).to.be.equal(dataToAppend); }); + test('Test searching for files', async () => { + const files = await fileSystem.search(path.join(__dirname, '*.js')); + expect(files).to.be.array(); + expect(files.length).to.be.at.least(1); + const expectedFileName = __filename.replace(/\\/g, '/'); + const fileName = files[0].replace(/\\/g, '/'); + expect(fileName).to.equal(expectedFileName); + }); }); diff --git a/src/test/interpreters/condaService.test.ts b/src/test/interpreters/condaService.test.ts index d16f7512ea3c..fae42a258bc9 100644 --- a/src/test/interpreters/condaService.test.ts +++ b/src/test/interpreters/condaService.test.ts @@ -9,7 +9,7 @@ import { Architecture, IFileSystem, IPlatformService } from '../../client/common import { IProcessService, IProcessServiceFactory } from '../../client/common/process/types'; import { ILogger, IPersistentStateFactory } from '../../client/common/types'; import { IInterpreterLocatorService, InterpreterType, PythonInterpreter } from '../../client/interpreter/contracts'; -import { CondaService, KNOWN_CONDA_LOCATIONS } from '../../client/interpreter/locators/services/condaService'; +import { CondaService } from '../../client/interpreter/locators/services/condaService'; import { IServiceContainer } from '../../client/ioc/types'; import { MockState } from './mocks'; @@ -356,21 +356,25 @@ suite('Interpreters Conda Service', () => { processService.verify(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once()); }); - KNOWN_CONDA_LOCATIONS.forEach(knownLocation => { - test(`Must return conda path from known location '${knownLocation}' (non windows)`, async () => { - const expectedCondaLocation = untildify(knownLocation); - platformService.setup(p => p.isWindows).returns(() => false); - processService.setup(p => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())).returns(() => Promise.reject(new Error('Not Found'))); - fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isAny())).returns((file: string) => Promise.resolve(file === expectedCondaLocation)); + ['~/anaconda/bin/conda', '~/miniconda/bin/conda', '~/anaconda2/bin/conda', + '~/miniconda2/bin/conda', '~/anaconda3/bin/conda', '~/miniconda3/bin/conda'] + .forEach(knownLocation => { + test(`Must return conda path from known location '${knownLocation}' (non windows)`, async () => { + const expectedCondaLocation = untildify(knownLocation); + platformService.setup(p => p.isWindows).returns(() => false); + processService.setup(p => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())).returns(() => Promise.reject(new Error('Not Found'))); + fileSystem.setup(fs => fs.search(TypeMoq.It.isAny())).returns(() => Promise.resolve([expectedCondaLocation])); + fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(expectedCondaLocation))).returns(() => Promise.resolve(true)); - const condaExe = await condaService.getCondaFile(); - assert.equal(condaExe, expectedCondaLocation, 'Failed to identify'); + const condaExe = await condaService.getCondaFile(); + assert.equal(condaExe, expectedCondaLocation, 'Failed to identify'); + }); }); - }); test('Must return \'conda\' if conda could not be found in known locations', async () => { platformService.setup(p => p.isWindows).returns(() => false); processService.setup(p => p.exec(TypeMoq.It.isValue('conda'), TypeMoq.It.isValue(['--version']), TypeMoq.It.isAny())).returns(() => Promise.reject(new Error('Not Found'))); + fileSystem.setup(fs => fs.search(TypeMoq.It.isAny())).returns(() => Promise.resolve([])); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isAny())).returns((file: string) => Promise.resolve(false)); const condaExe = await condaService.getCondaFile(); From 1e52efa97d30d46268e0f115ce441ce0c778cebb Mon Sep 17 00:00:00 2001 From: Derek Keeler Date: Fri, 8 Jun 2018 10:23:46 -0700 Subject: [PATCH 322/433] Test existance and value of `MOCHA_REPORTER_JUNIT` during test runs (#1912) Resolves issue #1911 --- src/test/constants.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/test/constants.ts b/src/test/constants.ts index 733fad899506..aaa771464d72 100644 --- a/src/test/constants.ts +++ b/src/test/constants.ts @@ -10,9 +10,15 @@ export const IS_VSTS = process.env.TF_BUILD !== undefined; export const IS_CI_SERVER = IS_TRAVIS || IS_APPVEYOR || IS_VSTS; // allow the CI server to specify JUnit output... -export const MOCHA_REPORTER_JUNIT: boolean = IS_CI_SERVER && process.env.MOCHA_REPORTER_JUNIT !== undefined; -export const MOCHA_CI_REPORTFILE: string = MOCHA_REPORTER_JUNIT && process.env.MOCHA_CI_REPORTFILE !== undefined ? process.env.MOCHA_CI_REPORTFILE.toString() : './junit-out.xml'; -export const MOCHA_CI_PROPERTIES: string = MOCHA_REPORTER_JUNIT && process.env.MOCHA_CI_PROPERTIES !== undefined ? process.env.MOCHA_CI_PROPERTIES.toString() : ''; +let reportJunit: boolean = false; +if (IS_CI_SERVER && process.env.MOCHA_REPORTER_JUNIT !== undefined) { + reportJunit = process.env.MOCHA_REPORTER_JUNIT.toLowerCase() === 'true'; +} +export const MOCHA_REPORTER_JUNIT: boolean = reportJunit; +export const MOCHA_CI_REPORTFILE: string = MOCHA_REPORTER_JUNIT && process.env.MOCHA_CI_REPORTFILE !== undefined ? + process.env.MOCHA_CI_REPORTFILE : './junit-out.xml'; +export const MOCHA_CI_PROPERTIES: string = MOCHA_REPORTER_JUNIT && process.env.MOCHA_CI_PROPERTIES !== undefined ? + process.env.MOCHA_CI_PROPERTIES : ''; export const TEST_TIMEOUT = 25000; export const IS_MULTI_ROOT_TEST = isMultitrootTest(); From f99060486b9ac693045ebd9b33da1780f635f028 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Fri, 8 Jun 2018 11:09:08 -0700 Subject: [PATCH 323/433] Add command for loading of LS extensions (#1914) * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * #1096 The if statement is automatically formatted incorrectly * Merge fix * Add more tests * More tests * Typo * Test * Also better handle multiline arguments * Add a couple missing periods [skip ci] * Undo changes * Test fixes * Increase timeout * Remove double event listening * Remove test * Revert "Remove test" This reverts commit e240c3fd117c38b9e6fdcbdd1ba2715789fefe48. * Revert "Remove double event listening" This reverts commit af573be27372a79d5589e2134002cc753bb54f2a. * Merge fix * #1257 On type formatting errors for args and kwargs * Handle f-strings * Stop importing from test code * #1308 Single line statements leading to an indentation on the next line * #726 editing python after inline if statement invalid indent * Undo change * Move constant * Harden LS startup error checks * #1364 Intellisense doesn't work after specific const string * Telemetry for the analysis enging * PR feedback * Fix typo * Test baseline update * Improve function argument detection * Specify markdown * Remove Pythia * Load extension command --- src/client/activation/analysis.ts | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/client/activation/analysis.ts b/src/client/activation/analysis.ts index 522722b39a69..7957bdc55172 100644 --- a/src/client/activation/analysis.ts +++ b/src/client/activation/analysis.ts @@ -5,8 +5,9 @@ import { inject, injectable } from 'inversify'; import * as path from 'path'; import { ExtensionContext, OutputChannel } from 'vscode'; import { Disposable, LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient'; -import { IApplicationShell } from '../common/application/types'; +import { IApplicationShell, ICommandManager } from '../common/application/types'; import { isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; +import { createDeferred, Deferred } from '../common/helpers'; import { IFileSystem, IPlatformService } from '../common/platform/types'; import { StopWatch } from '../common/stopWatch'; import { IConfigurationService, IExtensionContext, IOutputChannel } from '../common/types'; @@ -28,6 +29,7 @@ const PYTHON = 'python'; const dotNetCommand = 'dotnet'; const languageClientName = 'Python Tools'; const analysisEngineFolder = 'analysis'; +const loadExtensionCommand = 'python._loadLanguageServerExtension'; @injectable() export class AnalysisExtensionActivator implements IExtensionActivator { @@ -38,7 +40,9 @@ export class AnalysisExtensionActivator implements IExtensionActivator { private readonly sw = new StopWatch(); private readonly platformData: PlatformData; private readonly interpreterService: IInterpreterService; + private readonly startupCompleted: Deferred; private readonly disposables: Disposable[] = []; + private languageClient: LanguageClient | undefined; private readonly context: ExtensionContext; private interpreterHash: string = ''; @@ -51,6 +55,17 @@ export class AnalysisExtensionActivator implements IExtensionActivator { this.fs = this.services.get(IFileSystem); this.platformData = new PlatformData(services.get(IPlatformService), this.fs); this.interpreterService = this.services.get(IInterpreterService); + + this.startupCompleted = createDeferred(); + const commandManager = this.services.get(ICommandManager); + this.disposables.push(commandManager.registerCommand(loadExtensionCommand, + async (args) => { + if (this.languageClient) { + await this.startupCompleted.promise; + this.languageClient.sendRequest('python/loadExtension', args); + } + } + )); } public async activate(): Promise { @@ -119,9 +134,15 @@ export class AnalysisExtensionActivator implements IExtensionActivator { } private async startLanguageClient(context: ExtensionContext): Promise { + this.languageClient!.onReady() + .then(() => { + this.startupCompleted.resolve(); + }) + .catch(error => this.startupCompleted.reject(error)); + context.subscriptions.push(this.languageClient!.start()); if (isTestExecution()) { - await this.languageClient!.onReady(); + await this.startupCompleted.promise; } } From 6edc92c884fc2c419dd59a939d79794affacc0cd Mon Sep 17 00:00:00 2001 From: Mario Rubio Date: Fri, 8 Jun 2018 21:01:49 +0200 Subject: [PATCH 324/433] Added Spanish translation (#1902) --- README.md | 1 + news/1 Enhancements/1902.md | 2 ++ package.nls.es.json | 52 +++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 news/1 Enhancements/1902.md create mode 100644 package.nls.es.json diff --git a/README.md b/README.md index 18bb6feb2a63..9f49b1f5f457 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ contributors (if you would like to contribute a translation, see the [pull request which added Italian](https://github.com/Microsoft/vscode-python/pull/1152)): * `en` +* `es` * `it` * `ja` * `ko-kr` diff --git a/news/1 Enhancements/1902.md b/news/1 Enhancements/1902.md new file mode 100644 index 000000000000..328e7dd1c5df --- /dev/null +++ b/news/1 Enhancements/1902.md @@ -0,0 +1,2 @@ +Added Spanish translation +(thanks [Mario Rubio](https://github.com/mario-mra/)) \ No newline at end of file diff --git a/package.nls.es.json b/package.nls.es.json new file mode 100644 index 000000000000..3d2df644941f --- /dev/null +++ b/package.nls.es.json @@ -0,0 +1,52 @@ +{ + "python.command.python.sortImports.title": "Ordenar importaciones", + "python.command.python.startREPL.title": "Nuevo REPL", + "python.command.python.createTerminal.title": "Nueva terminal", + "python.command.python.buildWorkspaceSymbols.title": "Compilar símbolos del area de trabajo", + "python.command.python.runtests.title": "Ejecutar todas las pruebas unitarias", + "python.command.python.debugtests.title": "Depurar todas la pruebas unitarias", + "python.command.python.execInTerminal.title": "Ejecutar archivo Python en la terminal", + "python.command.python.setInterpreter.title": "Seleccionar intérprete", + "python.command.python.updateSparkLibrary.title": "Actualizar las librerías PySpark del area de trabajo", + "python.command.python.refactorExtractVariable.title": "Extraer variable", + "python.command.python.refactorExtractMethod.title": "Extraer método", + "python.command.python.viewTestOutput.title": "Mostrar resultados de la prueba unitaria", + "python.command.python.selectAndRunTestMethod.title": "Método de ejecución de pruebas unitarias ...", + "python.command.python.selectAndDebugTestMethod.title": "Método de depuración de pruebas unitarias ...", + "python.command.python.selectAndRunTestFile.title": "Ejecutar archivo de prueba unitaria ...", + "python.command.python.runCurrentTestFile.title": "Ejecutar archivo de prueba unitaria actual", + "python.command.python.runFailedTests.title": "Ejecutar pruebas unitarias fallidas", + "python.command.python.discoverTests.title": "Encontrar pruebas unitarias", + "python.command.python.execSelectionInTerminal.title": "Ejecutar linea/selección en la terminal", + "python.command.python.execSelectionInDjangoShell.title": "Ejecutar linea/selección en el interprete de Django", + "python.command.python.goToPythonObject.title": "Ir al objeto de Python", + "python.command.python.setLinter.title": "Selecionar Linter", + "python.command.python.enableLinting.title": "Habilitar Linting", + "python.command.python.runLinting.title": "Ejecutar Linting", + "python.snippet.launch.standard.label": "Python: Archivo actual", + "python.snippet.launch.standard.description": "Depurar una aplicación Python con salida estandar", + "python.snippet.launch.pyspark.label": "Python: PySpark", + "python.snippet.launch.pyspark.description": "Depurar una aplicación de PySpark", + "python.snippet.launch.module.label": "Python: Módulo", + "python.snippet.launch.module.description": "Depurar un módulo de Python", + "python.snippet.launch.terminal.label": "Python: Terminal (integrada)", + "python.snippet.launch.terminal.description": "Depurar una aplicación Python usando la terminal integrada", + "python.snippet.launch.externalTerminal.label": "Python: Terminal (externa)", + "python.snippet.launch.externalTerminal.description": "Depurar una aplicación Python usando una terminal externa", + "python.snippet.launch.django.label": "Python: Django", + "python.snippet.launch.django.description": "Depurar una aplicación de Django", + "python.snippet.launch.flask.label": "Python: Flask (Versión 0.11.x o posterior)", + "python.snippet.launch.flask.description": "Depurar una aplicación de Flask", + "python.snippet.launch.flaskOld.label": "Python: Flask (Versión 0.10.x o anterior)", + "python.snippet.launch.flaskOld.description": "Depurar una aplicación de Flask de estilo antiguo", + "python.snippet.launch.gevent.label": "Python: Gevent", + "python.snippet.launch.gevent.description": "Depurar una aplicación de Gevent", + "python.snippet.launch.pyramid.label": "Python: Pyramid", + "python.snippet.launch.pyramid.description": "Depurar una aplicación de Pyramid", + "python.snippet.launch.watson.label": "Python: Watson", + "python.snippet.launch.watson.description": "Depurar una aplicación de Watson", + "python.snippet.launch.attach.label": "Python: Adjuntar", + "python.snippet.launch.attach.description": "Depuración remota usando depurador adjunto", + "python.snippet.launch.scrapy.label": "Python: Scrapy", + "python.snippet.launch.scrapy.description": "Scrapy usando la terminal integrada" +} From 72870822c9ea89bce8d5e66466b7f11a5612e97d Mon Sep 17 00:00:00 2001 From: testvinder Date: Fri, 8 Jun 2018 21:08:02 +0200 Subject: [PATCH 325/433] Mention Black as option for code formatter (#1910) --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9f49b1f5f457..aa7bf7dec017 100644 --- a/README.md +++ b/README.md @@ -70,9 +70,9 @@ contributors (if you would like to contribute a translation, see the + Ability to include custom module paths (e.g. include paths for libraries like Google App Engine, etc.; use the setting `python.autoComplete.extraPaths = []`) * Code formatting + Auto formatting of code upon saving changes (default to 'Off') - + Use either [yapf](https://pypi.io/project/yapf/) or [autopep8](https://pypi.io/project/autopep8/) for code formatting (defaults to autopep8) + + Use either [yapf](https://pypi.org/project/yapf/), [autopep8](https://pypi.org/project/autopep8/), or [Black](https://pypi.org/project/black/) for code formatting (defaults to autopep8) * Linting - + Support for multiple linters with custom settings (default is [Pylint](https://pypi.org/project/pylint/), but [Prospector](https://pypi.org/project/prospector/), [Flake8](https://pypi.io/project/flake8/), [pylama](https://github.com/klen/pylama), [pydocstyle](https://pypi.org/project/pydocstyle/), and [mypy](https://pypi.org/project/mypy/) are also supported) + + Support for multiple linters with custom settings (default is [Pylint](https://pypi.org/project/pylint/), but [Prospector](https://pypi.org/project/prospector/), [Flake8](https://pypi.org/project/flake8/), [pylama](https://github.com/klen/pylama), [pydocstyle](https://pypi.org/project/pydocstyle/), and [mypy](https://pypi.org/project/mypy/) are also supported) * Debugging + Watch window + Evaluate expressions @@ -88,7 +88,7 @@ contributors (if you would like to contribute a translation, see the + Debugging in the integrated or external terminal window + Debugging as sudo * Unit testing - + Support for [unittest](https://docs.python.org/3/library/unittest.html#module-unittest), [pytest](https://pypi.io/project/pytest/), and [nose](https://pypi.io/project/nose/) + + Support for [unittest](https://docs.python.org/3/library/unittest.html#module-unittest), [pytest](https://pypi.org/project/pytest/), and [nose](https://pypi.org/project/nose/) + Ability to run all failed tests, individual tests + Debugging unit tests * Snippets From 8ae9fff118693f6ebb77728ba9d9fe1934aa9ce2 Mon Sep 17 00:00:00 2001 From: Anderson Carlos Woss Date: Mon, 11 Jun 2018 15:58:38 -0300 Subject: [PATCH 326/433] Add PT-BR translation (#1900) --- README.md | 1 + package.nls.pt-br.json | 52 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 package.nls.pt-br.json diff --git a/README.md b/README.md index aa7bf7dec017..0c2e514f05f3 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ contributors (if you would like to contribute a translation, see the * `it` * `ja` * `ko-kr` +* `pt-br` * `ru` * `zh-cn` * `zh-tw` diff --git a/package.nls.pt-br.json b/package.nls.pt-br.json new file mode 100644 index 000000000000..5b6e4dc2b539 --- /dev/null +++ b/package.nls.pt-br.json @@ -0,0 +1,52 @@ +{ + "python.command.python.sortImports.title": "Ordenar Importações", + "python.command.python.startREPL.title": "Iniciar REPL", + "python.command.python.createTerminal.title": "Criar Terminal", + "python.command.python.buildWorkspaceSymbols.title": "Construir Símbolos da Área de Trabalho", + "python.command.python.runtests.title": "Executar Todos os Testes Unitários", + "python.command.python.debugtests.title": "Depurar Todos os Testes Unitários", + "python.command.python.execInTerminal.title": "Executar Arquivo no Terminal", + "python.command.python.setInterpreter.title": "Selecionar Interpretador", + "python.command.python.updateSparkLibrary.title": "Atualizar Área de Trabalho da Biblioteca PySpark", + "python.command.python.refactorExtractVariable.title": "Extrair Variável", + "python.command.python.refactorExtractMethod.title": "Extrair Método", + "python.command.python.viewTestOutput.title": "Exibir Resultados dos Testes Unitários", + "python.command.python.selectAndRunTestMethod.title": "Executar Testes Unitários do Método ...", + "python.command.python.selectAndDebugTestMethod.title": "Depurar Testes Unitários do Método ...", + "python.command.python.selectAndRunTestFile.title": "Executar Arquivo de Testes Unitários ...", + "python.command.python.runCurrentTestFile.title": "Executar o Arquivo de Testes Unitários Atual", + "python.command.python.runFailedTests.title": "Executar Testes Unitários com Falhas", + "python.command.python.discoverTests.title": "Descobrir Testes Unitários", + "python.command.python.execSelectionInTerminal.title": "Executar Seleção/Linha no Terminal", + "python.command.python.execSelectionInDjangoShell.title": "Executar Seleção/Linha no Django Shell", + "python.command.python.goToPythonObject.title": "Ir para Objeto Python", + "python.command.python.setLinter.title": "Selecionar Linter", + "python.command.python.enableLinting.title": "Habilitar Linting", + "python.command.python.runLinting.title": "Executar Linting", + "python.snippet.launch.standard.label": "Python: Arquivo Atual", + "python.snippet.launch.standard.description": "Depurar um Programa Python com a saída padrão", + "python.snippet.launch.pyspark.label": "Python: PySpark", + "python.snippet.launch.pyspark.description": "Depurar PySpark", + "python.snippet.launch.module.label": "Python: Módulo", + "python.snippet.launch.module.description": "Depurar um Módulo Python", + "python.snippet.launch.terminal.label": "Python: Terminal (integrado)", + "python.snippet.launch.terminal.description": "Depurar um Programa Python com Terminal/Console Integrado", + "python.snippet.launch.externalTerminal.label": "Python: Terminal (externo)", + "python.snippet.launch.externalTerminal.description": "Depurar um Programa Python com Terminal/Console Externo", + "python.snippet.launch.django.label": "Python: Django", + "python.snippet.launch.django.description": "Depurar uma Aplicação Django", + "python.snippet.launch.flask.label": "Python: Flask (0.11.x ou superior)", + "python.snippet.launch.flask.description": "Depurar uma Aplicação Flask", + "python.snippet.launch.flaskOld.label": "Python: Flask (0.10.x ou inferior)", + "python.snippet.launch.flaskOld.description": "Depurar uma Aplicação Flask no Estilo Antigo", + "python.snippet.launch.gevent.label": "Python: Gevent", + "python.snippet.launch.gevent.description": "Depurar uma Aplicação Gevent", + "python.snippet.launch.pyramid.label": "Python: Aplicação Pyramid", + "python.snippet.launch.pyramid.description": "Depurar uma Aplicação Pyramid", + "python.snippet.launch.watson.label": "Python: Aplicação Watson", + "python.snippet.launch.watson.description": "Depurar uma Aplicação Watson", + "python.snippet.launch.attach.label": "Python: Anexar", + "python.snippet.launch.attach.description": "Anexar depurador para depuração remota", + "python.snippet.launch.scrapy.label": "Python: Scrapy", + "python.snippet.launch.scrapy.description": "Scrapy com Terminal/Console Integrado" +} From 5ee4046478ebb7e16bc357f55c95c526c2debac9 Mon Sep 17 00:00:00 2001 From: Nathan Gaberel Date: Mon, 11 Jun 2018 22:31:42 +0100 Subject: [PATCH 327/433] Add isort CodeAction (sort imports on save) (#1926) Fixes #156 --- news/1 Enhancements/156.md | 1 + src/client/extension.ts | 3 ++ src/client/providers/codeActionsProvider.ts | 21 +++++++++ .../providers/codeActionsProvider.test.ts | 44 +++++++++++++++++++ 4 files changed, 69 insertions(+) create mode 100644 news/1 Enhancements/156.md create mode 100644 src/client/providers/codeActionsProvider.ts create mode 100644 src/test/providers/codeActionsProvider.test.ts diff --git a/news/1 Enhancements/156.md b/news/1 Enhancements/156.md new file mode 100644 index 000000000000..da404f67686e --- /dev/null +++ b/news/1 Enhancements/156.md @@ -0,0 +1 @@ +Add support for the `editor.codeActionsOnSave.source.organizeImports` setting (thanks [Nathan Gaberel](https://github.com/n6g7)). diff --git a/src/client/extension.ts b/src/client/extension.ts index 2228cca81495..3e59f8ae408f 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -40,6 +40,7 @@ import { IServiceContainer, IServiceManager } from './ioc/types'; import { LinterCommands } from './linters/linterCommands'; import { registerTypes as lintersRegisterTypes } from './linters/serviceRegistry'; import { ILintingEngine } from './linters/types'; +import { PythonCodeActionProvider } from './providers/codeActionsProvider'; import { PythonFormattingEditProvider } from './providers/formatProvider'; import { LinterProvider } from './providers/linterProvider'; import { PythonRenameProvider } from './providers/renameProvider'; @@ -145,6 +146,8 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(new TerminalProvider(serviceContainer)); context.subscriptions.push(new WorkspaceSymbols(serviceContainer)); + context.subscriptions.push(languages.registerCodeActionsProvider(PYTHON, new PythonCodeActionProvider())); + type ConfigurationProvider = BaseConfigurationProvider; serviceContainer.getAll(IDebugConfigurationProvider).forEach(debugConfig => { context.subscriptions.push(debug.registerDebugConfigurationProvider(debugConfig.debugType, debugConfig)); diff --git a/src/client/providers/codeActionsProvider.ts b/src/client/providers/codeActionsProvider.ts new file mode 100644 index 000000000000..5bc2975f5670 --- /dev/null +++ b/src/client/providers/codeActionsProvider.ts @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as vscode from 'vscode'; + +export class PythonCodeActionProvider implements vscode.CodeActionProvider { + public provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken): vscode.ProviderResult { + const sortImports = new vscode.CodeAction( + 'Sort imports on save', + vscode.CodeActionKind.SourceOrganizeImports + ); + sortImports.command = { + title: 'Sort imports', + command: 'python.sortImports' + }; + + return [sortImports]; + } +} diff --git a/src/test/providers/codeActionsProvider.test.ts b/src/test/providers/codeActionsProvider.test.ts new file mode 100644 index 000000000000..8147063d649e --- /dev/null +++ b/src/test/providers/codeActionsProvider.test.ts @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { expect } from 'chai'; +import * as TypeMoq from 'typemoq'; +import { CancellationToken, CodeActionContext, CodeActionKind, Range, TextDocument } from 'vscode'; +import { PythonCodeActionProvider } from '../../client/providers/codeActionsProvider'; + +suite('CodeAction Provider', () => { + let codeActionsProvider: PythonCodeActionProvider; + let document: TypeMoq.IMock; + let range: TypeMoq.IMock; + let context: TypeMoq.IMock; + let token: TypeMoq.IMock; + + setup(() => { + codeActionsProvider = new PythonCodeActionProvider(); + document = TypeMoq.Mock.ofType(); + range = TypeMoq.Mock.ofType(); + context = TypeMoq.Mock.ofType(); + token = TypeMoq.Mock.ofType(); + }); + + test('Ensure it always returns a source.organizeImports CodeAction', async () => { + const codeActions = await codeActionsProvider.provideCodeActions( + document.object, + range.object, + context.object, + token.object + ); + + if (!codeActions) { + throw Error(`codeActionsProvider.provideCodeActions did not return an array (it returned ${codeActions})`); + } + + const organizeImportsCodeAction = codeActions.filter( + codeAction => codeAction.kind === CodeActionKind.SourceOrganizeImports + ); + expect(organizeImportsCodeAction).to.have.length(1); + expect(organizeImportsCodeAction[0].kind).to.eq(CodeActionKind.SourceOrganizeImports); + }); +}); From 88828074e0566838858346dc4a5127db7c2e3585 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 11 Jun 2018 14:56:39 -0700 Subject: [PATCH 328/433] Configure the No Response bot --- .github/no-response.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/no-response.yml diff --git a/.github/no-response.yml b/.github/no-response.yml new file mode 100644 index 000000000000..75f82d73cb54 --- /dev/null +++ b/.github/no-response.yml @@ -0,0 +1,2 @@ +daysUntilClose: 28 +responseRequiredLabel: "needs more info" From 1db2d64efbfc14ee8b0283004a3c4c9d099d56ad Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 11 Jun 2018 14:58:27 -0700 Subject: [PATCH 329/433] Settings for the Lock Threads bot --- .github/lock.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .github/lock.yml diff --git a/.github/lock.yml b/.github/lock.yml new file mode 100644 index 000000000000..8947d433b798 --- /dev/null +++ b/.github/lock.yml @@ -0,0 +1,3 @@ +daysUntilLock:28 +lockComment: false +only: issues From 971cb670796f16a4b239b2d95a2e9c49a0c2e32a Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 11 Jun 2018 15:02:09 -0700 Subject: [PATCH 330/433] Fix unhandled rejected promises and fix tests (#1921) Fixes #1918 Fixes #1919 --- news/2 Fixes/1919.md | 1 + news/3 Code Health/1918.md | 1 + .../debugger/configProvider/provider.test.ts | 13 +++++-- src/test/index.ts | 12 ++++++ .../install/channelManager.messages.test.ts | 37 ++++++++++--------- .../unittests/common/debugLauncher.test.ts | 6 +-- 6 files changed, 46 insertions(+), 24 deletions(-) create mode 100644 news/2 Fixes/1919.md create mode 100644 news/3 Code Health/1918.md diff --git a/news/2 Fixes/1919.md b/news/2 Fixes/1919.md new file mode 100644 index 000000000000..bff5edbd4083 --- /dev/null +++ b/news/2 Fixes/1919.md @@ -0,0 +1 @@ +Fix unhandled rejected promises in unit tests. diff --git a/news/3 Code Health/1918.md b/news/3 Code Health/1918.md new file mode 100644 index 000000000000..e49860ba62f6 --- /dev/null +++ b/news/3 Code Health/1918.md @@ -0,0 +1 @@ +Log unhandled rejected promises when running unit tests. diff --git a/src/test/debugger/configProvider/provider.test.ts b/src/test/debugger/configProvider/provider.test.ts index 187b78e87a58..888fc1b2a9da 100644 --- a/src/test/debugger/configProvider/provider.test.ts +++ b/src/test/debugger/configProvider/provider.test.ts @@ -344,10 +344,15 @@ import { IServiceContainer } from '../../../client/ioc/types'; setupIoc(pythonPath, isWindows, isMac, isLinux); setupActiveEditor(pythonFile, PYTHON_LANGUAGE); - const execOutput = pyramidExists ? Promise.resolve({ stdout: pyramidFilePath }) : Promise.reject('No Module'); - pythonExecutionService.setup(e => e.exec(TypeMoq.It.isValue(args), TypeMoq.It.isAny())) - .returns(() => execOutput) - .verifiable(TypeMoq.Times.exactly(addPyramidDebugOption ? 1 : 0)); + if (pyramidExists) { + pythonExecutionService.setup(e => e.exec(TypeMoq.It.isValue(args), TypeMoq.It.isAny())) + .returns(() => Promise.resolve({ stdout: pyramidFilePath })) + .verifiable(TypeMoq.Times.exactly(addPyramidDebugOption ? 1 : 0)); + } else { + pythonExecutionService.setup(e => e.exec(TypeMoq.It.isValue(args), TypeMoq.It.isAny())) + .returns(() => Promise.reject('No Module Available')) + .verifiable(TypeMoq.Times.exactly(addPyramidDebugOption ? 1 : 0)); + } fileSystem.setup(f => f.fileExists(TypeMoq.It.isValue(pserveFilePath))) .returns(() => Promise.resolve(pyramidExists)) .verifiable(TypeMoq.Times.exactly(pyramidExists && addPyramidDebugOption ? 1 : 0)); diff --git a/src/test/index.ts b/src/test/index.ts index da4866260751..5a2ebe364582 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -46,5 +46,17 @@ if (MOCHA_REPORTER_JUNIT) { }; } +process.on('unhandledRejection', (ex: string | Error, a) => { + const message = [`${ex}`]; + if (typeof ex !== 'string' && ex && ex.message) { + message.push(ex.name); + message.push(ex.message); + if (ex.stack) { + message.push(ex.stack); + } + } + console.error(`Unhandled Promise Rejection with the message ${message.join(', ')}`); +}); + testRunner.configure(options, { coverageConfig: '../coverconfig.json' }); module.exports = testRunner; diff --git a/src/test/install/channelManager.messages.test.ts b/src/test/install/channelManager.messages.test.ts index 7d332917a0f1..0cafd2f64b0c 100644 --- a/src/test/install/channelManager.messages.test.ts +++ b/src/test/install/channelManager.messages.test.ts @@ -6,6 +6,7 @@ import { Container } from 'inversify'; import * as TypeMoq from 'typemoq'; import { IApplicationShell } from '../../client/common/application/types'; import { InstallationChannelManager } from '../../client/common/installer/channelManager'; +import { IModuleInstaller } from '../../client/common/installer/types'; import { Architecture, IPlatformService } from '../../client/common/platform/types'; import { Product } from '../../client/common/types'; import { IInterpreterService, InterpreterType, PythonInterpreter } from '../../client/interpreter/contracts'; @@ -46,13 +47,15 @@ suite('Installation - channel messages', () => { interpreters = TypeMoq.Mock.ofType(); serviceManager.addSingletonInstance(IInterpreterService, interpreters.object); + + const moduleInstaller = TypeMoq.Mock.ofType(); + serviceManager.addSingletonInstance(IModuleInstaller, moduleInstaller.object); }); test('No installers message: Unknown/Windows', async () => { platform.setup(x => x.isWindows).returns(() => true); await testInstallerMissingMessage(InterpreterType.Unknown, - async (channels: InstallationChannelManager, message: string, url: string) => { - await channels.showNoInstallersMessage(); + async (message: string, url: string) => { verifyMessage(message, ['Pip'], ['Conda']); verifyUrl(url, ['Windows', 'Pip']); }); @@ -61,8 +64,7 @@ suite('Installation - channel messages', () => { test('No installers message: Conda/Windows', async () => { platform.setup(x => x.isWindows).returns(() => true); await testInstallerMissingMessage(InterpreterType.Conda, - async (channels: InstallationChannelManager, message: string, url: string) => { - await channels.showNoInstallersMessage(); + async (message: string, url: string) => { verifyMessage(message, ['Pip', 'Conda'], []); verifyUrl(url, ['Windows', 'Pip', 'Conda']); }); @@ -72,8 +74,7 @@ suite('Installation - channel messages', () => { platform.setup(x => x.isWindows).returns(() => false); platform.setup(x => x.isMac).returns(() => true); await testInstallerMissingMessage(InterpreterType.Unknown, - async (channels: InstallationChannelManager, message: string, url: string) => { - await channels.showNoInstallersMessage(); + async (message: string, url: string) => { verifyMessage(message, ['Pip'], ['Conda']); verifyUrl(url, ['Mac', 'Pip']); }); @@ -83,8 +84,7 @@ suite('Installation - channel messages', () => { platform.setup(x => x.isWindows).returns(() => false); platform.setup(x => x.isMac).returns(() => true); await testInstallerMissingMessage(InterpreterType.Conda, - async (channels: InstallationChannelManager, message: string, url: string) => { - await channels.showNoInstallersMessage(); + async (message: string, url: string) => { verifyMessage(message, ['Pip', 'Conda'], []); verifyUrl(url, ['Mac', 'Pip', 'Conda']); }); @@ -95,8 +95,7 @@ suite('Installation - channel messages', () => { platform.setup(x => x.isMac).returns(() => false); platform.setup(x => x.isLinux).returns(() => true); await testInstallerMissingMessage(InterpreterType.Unknown, - async (channels: InstallationChannelManager, message: string, url: string) => { - await channels.showNoInstallersMessage(); + async (message: string, url: string) => { verifyMessage(message, ['Pip'], ['Conda']); verifyUrl(url, ['Linux', 'Pip']); }); @@ -107,8 +106,7 @@ suite('Installation - channel messages', () => { platform.setup(x => x.isMac).returns(() => false); platform.setup(x => x.isLinux).returns(() => true); await testInstallerMissingMessage(InterpreterType.Conda, - async (channels: InstallationChannelManager, message: string, url: string) => { - await channels.showNoInstallersMessage(); + async (message: string, url: string) => { verifyMessage(message, ['Pip', 'Conda'], []); verifyUrl(url, ['Linux', 'Pip', 'Conda']); }); @@ -117,11 +115,10 @@ suite('Installation - channel messages', () => { test('No channels message', async () => { platform.setup(x => x.isWindows).returns(() => true); await testInstallerMissingMessage(InterpreterType.Unknown, - async (channels: InstallationChannelManager, message: string, url: string) => { - await channels.getInstallationChannel(Product.pylint); + async (message: string, url: string) => { verifyMessage(message, ['Pip'], ['Conda']); verifyUrl(url, ['Windows', 'Pip']); - }); + }, 'getInstallationChannel'); }); function verifyMessage(message: string, present: string[], missing: string[]) { @@ -142,7 +139,8 @@ suite('Installation - channel messages', () => { async function testInstallerMissingMessage( interpreterType: InterpreterType, - verify: (c: InstallationChannelManager, m: string, u: string) => void): Promise { + verify: (m: string, u: string) => Promise, + methodType: 'showNoInstallersMessage' | 'getInstallationChannel' = 'showNoInstallersMessage'): Promise { const activeInterpreter: PythonInterpreter = { ...info, @@ -167,6 +165,11 @@ suite('Installation - channel messages', () => { appShell.setup(x => x.openUrl(TypeMoq.It.isAnyString())).callback((s: string) => { url = s; }); - verify(channels, message, url); + if (methodType === 'showNoInstallersMessage') { + await channels.showNoInstallersMessage(); + } else { + await channels.getInstallationChannel(Product.pylint); + } + await verify(message, url); } }); diff --git a/src/test/unittests/common/debugLauncher.test.ts b/src/test/unittests/common/debugLauncher.test.ts index c4d5a3520684..f480e0b3ee80 100644 --- a/src/test/unittests/common/debugLauncher.test.ts +++ b/src/test/unittests/common/debugLauncher.test.ts @@ -86,7 +86,7 @@ suite('Unit Tests - Debug Launcher', () => { const testProviders: TestProvider[] = ['nosetest', 'pytest', 'unittest']; testProviders.forEach(testProvider => { [true, false].forEach(useExperimentalDebugger => { - const testTitleSuffix = `(Test Framework '${testProvider}', and use experimental debugger = '${useExperimentalDebugger}'`; + const testTitleSuffix = `(Test Framework '${testProvider}', and use experimental debugger = '${useExperimentalDebugger}')`; const testLaunchScript = getTestLauncherScript(testProvider, useExperimentalDebugger); const debuggerType = useExperimentalDebugger ? 'pythonExperimental' : 'python'; @@ -131,7 +131,7 @@ suite('Unit Tests - Debug Launcher', () => { const cancellationToken = new CancellationTokenSource(); cancellationToken.cancel(); const token = cancellationToken.token; - expect(debugLauncher.launchDebugger({ cwd: '', args: [], token, testProvider })).to.be.eventually.equal(undefined, 'not undefined'); + await expect(debugLauncher.launchDebugger({ cwd: '', args: [], token, testProvider })).to.be.eventually.equal(undefined, 'not undefined'); debugService.verifyAll(); }); test(`Must throw an exception if there are no workspaces ${testTitleSuffix}`, async () => { @@ -142,7 +142,7 @@ suite('Unit Tests - Debug Launcher', () => { .returns(() => Promise.resolve(undefined as any)) .verifiable(TypeMoq.Times.never()); - expect(debugLauncher.launchDebugger({ cwd: '', args: [], testProvider })).to.eventually.throw('Please open a workspace'); + await expect(debugLauncher.launchDebugger({ cwd: '', args: [], testProvider })).to.eventually.rejectedWith('Please open a workspace'); debugService.verifyAll(); }); }); From 59531591a294915af550d395e63bd2409368f75c Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 11 Jun 2018 15:50:03 -0700 Subject: [PATCH 331/433] Beta release of 2018.6.0 (#1932) --- CHANGELOG.md | 128 ++++++++++++++++++++++++++ README.md | 2 +- ThirdPartyNotices-Distribution.txt | 139 ++++++++++++++++++++--------- news/1 Enhancements/1037.md | 4 +- news/1 Enhancements/1902.md | 4 +- news/2 Fixes/1638.md | 3 +- news/3 Code Health/1815.md | 2 +- news/3 Code Health/1842.md | 2 +- news/__main__.py | 3 + package-lock.json | 10 ++- package.json | 2 +- 11 files changed, 243 insertions(+), 56 deletions(-) create mode 100644 news/__main__.py diff --git a/CHANGELOG.md b/CHANGELOG.md index da27f22d7478..f160bf492d86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,133 @@ # Changelog +## 2018.6.0-beta 11 June 2018) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: +- [isort 4.3.4](https://pypi.org/project/isort/4.3.4/) +- [jedi 0.12.0](https://pypi.org/project/jedi/0.12.0/) + and [parso 0.2.1](https://pypi.org/project/parso/0.2.1/) +- [ptvsd 3.0.0](https://pypi.org/project/ptvsd/3.0.0/) and [4.1.11a5](https://pypi.org/project/ptvsd/4.1.11a5/) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +### Enhancements + +1. Add setting for auto run test discovery on save, `python.unitTest.autoTestDiscoverOnSaveEnabled`. + (thanks [Lingyu Li](http://github.com/lingyv-li/)) + ([#1037](https://github.com/Microsoft/vscode-python/issues/1037)) +1. Add `gevent` launch configuration option to enable debugging of gevent monkey patched code. + ([#127](https://github.com/Microsoft/vscode-python/issues/127)) +1. Added Spanish translation. + (thanks [Mario Rubio](https://github.com/mario-mra/)) + ([#1902](https://github.com/Microsoft/vscode-python/issues/1902)) + +### Fixes + +1. Ensure navigation to definitons follows imports and is transparent to decoration. + (thanks [Peter Law](https://github.com/PeterJCLaw)) + ([#1638](https://github.com/Microsoft/vscode-python/issues/1638)) +1. Fix for intellisense failing when using the new `Outline` feature. + ([#1721](https://github.com/Microsoft/vscode-python/issues/1721)) +1. When debugging unit tests, use the `env` file configured in `settings.json` under `python.envFile`. + ([#1759](https://github.com/Microsoft/vscode-python/issues/1759)) +1. Fix to display all interpreters in the interpreter list when a workspace contains a `Pipfile`. + ([#1800](https://github.com/Microsoft/vscode-python/issues/1800)) +1. Use file system API to perform file path comparisons when performing code navigation. + (thanks to [bstaint](https://github.com/bstaint) for the initial patch) + ([#1811](https://github.com/Microsoft/vscode-python/issues/1811)) +1. Automatically add path mappings for remote debugging when attaching to the localhost. + ([#1829](https://github.com/Microsoft/vscode-python/issues/1829)) +1. Fix debugger issue that causes the debugger to hang and silently exit stepping over a line of code instantiating an ITK vector object. + ([#459](https://github.com/Microsoft/vscode-python/issues/459)) + +### Code Health + +1. Add telemetry to capture type of python interpreter used in workspace. + ([#1237](https://github.com/Microsoft/vscode-python/issues/1237)) +1. Use [dotenv](https://www.npmjs.com/package/dotenv) package to parse [environment variables definition files](https://code.visualstudio.com/docs/python/environments#_environment-variable-definitions-file). + ([#1376](https://github.com/Microsoft/vscode-python/issues/1376)) +1. Move from yarn to npm. + ([#1402](https://github.com/Microsoft/vscode-python/issues/1402)) +1. Capture telemetry for the usage of the `Create Terminal` command along with other instances when a terminal is created implicitly. + ([#1542](https://github.com/Microsoft/vscode-python/issues/1542)) +1. Add telemetry to capture availability of Python 3, version of Python used in workspace and the number of workspace folders. + ([#1545](https://github.com/Microsoft/vscode-python/issues/1545)) +1. Capture telemetry for the usage of the feature that formats a line as you type (`editor.formatOnType`). + ([#1766](https://github.com/Microsoft/vscode-python/issues/1766)) +1. Capture telemetry for the new debugger. + ([#1767](https://github.com/Microsoft/vscode-python/issues/1767)) +1. Capture telemetry for usage of the setting `python.autocomplete.addBrackets` + ([#1770](https://github.com/Microsoft/vscode-python/issues/1770)) +1. Speed up githook by skipping commits not containing any `.ts` files. + ([#1803](https://github.com/Microsoft/vscode-python/issues/1803)) +1. Update typescript package to 2.9.1. + ([#1815](https://github.com/Microsoft/vscode-python/issues/1815)) +1. Log Conda not existing message as an information instead of an error. + ([#1817](https://github.com/Microsoft/vscode-python/issues/1817)) +1. Make use of `ILogger` to log messages instead of using `console.error`. + ([#1821](https://github.com/Microsoft/vscode-python/issues/1821)) +1. Update `parso` package to 0.2.1. + ([#1833](https://github.com/Microsoft/vscode-python/issues/1833)) +1. Update `isort` package to 4.3.4. + ([#1842](https://github.com/Microsoft/vscode-python/issues/1842)) +1. Add better exception handling when parsing responses received from the Jedi language service. + ([#1867](https://github.com/Microsoft/vscode-python/issues/1867)) +1. Resolve warnings in CI Tests and fix some broken CI Tests. + ([#1885](https://github.com/Microsoft/vscode-python/issues/1885)) +1. Reduce sample count used to capture performance metrics in order to reduce time taken to complete the tests. + ([#1887](https://github.com/Microsoft/vscode-python/issues/1887)) +1. Ensure workspace information is passed into installer when determining whether a product/tool is installed. + ([#1893](https://github.com/Microsoft/vscode-python/issues/1893)) +1. Add JUnit file output to enable CI integration with VSTS. + ([#1897](https://github.com/Microsoft/vscode-python/issues/1897)) +1. Use a glob pattern to look for `conda` executables. + ([#256](https://github.com/Microsoft/vscode-python/issues/256)) +1. Create tests to measure activation times for the extension. + ([#932](https://github.com/Microsoft/vscode-python/issues/932)) + + + ## 2018.5.0 (05 Jun 2018) Thanks to the following projects which we fully rely on to provide some of diff --git a/README.md b/README.md index 0c2e514f05f3..637469599877 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ contributors (if you would like to contribute a translation, see the + Auto formatting of code upon saving changes (default to 'Off') + Use either [yapf](https://pypi.org/project/yapf/), [autopep8](https://pypi.org/project/autopep8/), or [Black](https://pypi.org/project/black/) for code formatting (defaults to autopep8) * Linting - + Support for multiple linters with custom settings (default is [Pylint](https://pypi.org/project/pylint/), but [Prospector](https://pypi.org/project/prospector/), [Flake8](https://pypi.org/project/flake8/), [pylama](https://github.com/klen/pylama), [pydocstyle](https://pypi.org/project/pydocstyle/), and [mypy](https://pypi.org/project/mypy/) are also supported) + + Support for multiple linters with custom settings (default is [Pylint](https://pypi.org/project/pylint/), but [Prospector](https://pypi.org/project/prospector/), [Flake8](https://pypi.org/project/flake8/), [pylama](https://pypi.org/project/pylama/), [pydocstyle](https://pypi.org/project/pydocstyle/), and [mypy](https://pypi.org/project/mypy/) are also supported) * Debugging + Watch window + Evaluate expressions diff --git a/ThirdPartyNotices-Distribution.txt b/ThirdPartyNotices-Distribution.txt index a13b7f31cdf0..8570d92e913c 100644 --- a/ThirdPartyNotices-Distribution.txt +++ b/ThirdPartyNotices-Distribution.txt @@ -6,59 +6,61 @@ Microsoft Python extension for Visual Studio Code incorporates components from t 1. Arch (https://github.com/feross/arch) 2. diff-match-patch (https://github.com/ForbesLindesay-Unmaintained/diff-match-patch) -3. Files from the Python Project (https://www.python.org/) -4. fuzzy (https://github.com/mattyork/fuzzy) -5. Get-port (https://github.com/sindresorhus/get-port) -6. Go for Visual Studio Code (https://github.com/Microsoft/vscode-go) -7. Google Diff Match and Patch (https://github.com/GerHobbelt/google-diff-match-patch) -8. Iconv-lite (https://github.com/ashtuchkin/iconv-lite) -9. Inversify (https://github.com/inversify/InversifyJS) -10. isort (https://github.com/timothycrosley/isort) -11. jedi (https://github.com/davidhalter/jedi) -12. line-by-line (https://github.com/Osterjour/line-by-line) -13. lodash (https://github.com/lodash/lodash) +3. dotenv (https://github.com/motdotla/dotenv) +4. Files from the Python Project (https://www.python.org/) +5. fuzzy (https://github.com/mattyork/fuzzy) +6. Get-port (https://github.com/sindresorhus/get-port) +7. Glob (https://github.com/isaacs/node-glob) +8. Go for Visual Studio Code (https://github.com/Microsoft/vscode-go) +9. Google Diff Match and Patch (https://github.com/GerHobbelt/google-diff-match-patch) +10. Iconv-lite (https://github.com/ashtuchkin/iconv-lite) +11. Inversify (https://github.com/inversify/InversifyJS) +12. isort (https://github.com/timothycrosley/isort) +13. jedi (https://github.com/davidhalter/jedi) +14. line-by-line (https://github.com/Osterjour/line-by-line) +15. lodash (https://github.com/lodash/lodash) Includes:Sizzle CSS Selector Engine Includes:Webpack -14. MD5 (https://github.com/pvorb/node-md5) -15. minimatch (https://github.com/isaacs/minimatch) -16. named-js-regexp (https://github.com/edvinv/named-js-regexp) -17. node-fs-extra (https://github.com/jprichardson/node-fs-extra) -18. node-semver (https://github.com/npm/node-semver) -19. node-stream-zip (https://github.com/antelle/node-stream-zip) -20. node-tmp (https://github.com/raszi/node-tmp) -21. node-tree-kill (https://github.com/pkrumins/node-tree-kill) -22. node-winreg (https://github.com/fresc81/node-winreg) -23. node-xml2js (https://github.com/Leonidas-from-XIV/node-xml2js) -24. omnisharp-vscode (https://github.com/OmniSharp/omnisharp-vscode) -25. opn (https://github.com/sindresorhus/opn) -26. pidusage (https://github.com/soyuka/pidusage) -27. PTVS (https://github.com/Microsoft/PTVS) -28. PTVSD (https://github.com/Microsoft/PTVSD) -29. PyDev.Debugger (https://github.com/fabioz/PyDev.Debugger) +16. MD5 (https://github.com/pvorb/node-md5) +17. minimatch (https://github.com/isaacs/minimatch) +18. named-js-regexp (https://github.com/edvinv/named-js-regexp) +19. node-fs-extra (https://github.com/jprichardson/node-fs-extra) +20. node-semver (https://github.com/npm/node-semver) +21. node-stream-zip (https://github.com/antelle/node-stream-zip) +22. node-tmp (https://github.com/raszi/node-tmp) +23. node-tree-kill (https://github.com/pkrumins/node-tree-kill) +24. node-winreg (https://github.com/fresc81/node-winreg) +25. node-xml2js (https://github.com/Leonidas-from-XIV/node-xml2js) +26. omnisharp-vscode (https://github.com/OmniSharp/omnisharp-vscode) +27. opn (https://github.com/sindresorhus/opn) +28. pidusage (https://github.com/soyuka/pidusage) +29. PTVS (https://github.com/Microsoft/PTVS) +30. PTVSD (https://github.com/Microsoft/PTVSD) +31. PyDev.Debugger (https://github.com/fabioz/PyDev.Debugger) Includes:Files copyright Yuli Fitterman Includes:IPython Includes:py2app Includes:Python (various files) -30. Python documentation (https://docs.python.org/) -31. python-functools32 (https://github.com/MiCHiLU/python-functools32/blob/master/functools32/functools32.py) -32. pythonVSCode (https://github.com/DonJayamanne/pythonVSCode) -33. Reflect-metadata (https://github.com/rbuckton/reflect-metadata) -34. request (https://github.com/request/request) -35. request-progress (https://github.com/IndigoUnited/node-request-progress) -36. RxJS (https://github.com/ReactiveX/RxJS) +32. Python documentation (https://docs.python.org/) +33. python-functools32 (https://github.com/MiCHiLU/python-functools32/blob/master/functools32/functools32.py) +34. pythonVSCode (https://github.com/DonJayamanne/pythonVSCode) +35. Reflect-metadata (https://github.com/rbuckton/reflect-metadata) +36. request (https://github.com/request/request) +37. request-progress (https://github.com/IndigoUnited/node-request-progress) +38. RxJS (https://github.com/ReactiveX/RxJS) Includes:Contributor Covenant v1.1.0, v1.4 Includes:File from Angular.io Includes:File from setImmediate -37. Sphinx (http://sphinx-doc.org/) -38. sudo-prompt (https://github.com/jorangreef/sudo-prompt) -39. uint64be (https://github.com/mafintosh/uint64be) -40. untangle (https://github.com/stchris/untangle) -41. untildify (https://github.com/sindresorhus/untildify) -42. vscode-debugadapter (https://github.com/Microsoft/vscode-debugadapter-node/tree/master/adapter) -43. vscode-debugprotocol (https://github.com/Microsoft/vscode-debugadapter-node/tree/master/protocol) -44. vscode-extension-telemetry (https://github.com/Microsoft/vscode-extension-telemetry) -45. vscode-languageclient (https://github.com/Microsoft/vscode-languageserver-node) -46. vscode-languageserver (https://github.com/Microsoft/vscode-languageserver-node/) +39. Sphinx (http://sphinx-doc.org/) +40. sudo-prompt (https://github.com/jorangreef/sudo-prompt) +41. uint64be (https://github.com/mafintosh/uint64be) +42. untangle (https://github.com/stchris/untangle) +43. untildify (https://github.com/sindresorhus/untildify) +44. vscode-debugadapter (https://github.com/Microsoft/vscode-debugadapter-node/tree/master/adapter) +45. vscode-debugprotocol (https://github.com/Microsoft/vscode-debugadapter-node/tree/master/protocol) +46. vscode-extension-telemetry (https://github.com/Microsoft/vscode-extension-telemetry) +47. vscode-languageclient (https://github.com/Microsoft/vscode-languageserver-node) +48. vscode-languageserver (https://github.com/Microsoft/vscode-languageserver-node/) %% Arch NOTICES AND INFORMATION BEGIN HERE @@ -105,6 +107,35 @@ limitations under the License. ========================================= END OF diff-match-patch NOTICES AND INFORMATION +%% dotenv NOTICES AND INFORMATION BEGIN HERE +========================================= +Copyright (c) 2015, Scott Motte +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +========================================= +END OF dotenv NOTICES AND INFORMATION + %% Files from the Python Project NOTICES AND INFORMATION BEGIN HERE ========================================= PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 @@ -344,6 +375,26 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI ========================================= END OF Get-port NOTICES AND INFORMATION +%% Glob NOTICES AND INFORMATION BEGIN HERE +========================================= +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +========================================= +END OF Glob NOTICES AND INFORMATION + %% Go for Visual Studio Code NOTICES AND INFORMATION BEGIN HERE ========================================= diff --git a/news/1 Enhancements/1037.md b/news/1 Enhancements/1037.md index 08e41c35ff60..f096c595b71f 100644 --- a/news/1 Enhancements/1037.md +++ b/news/1 Enhancements/1037.md @@ -1,2 +1,2 @@ -Add setting for auto run test discover on save -(thanks [Lingyu Li](http://github.com/lingyv-li/)) \ No newline at end of file +Add setting for auto run test discover on save, `python.unitTest.autoTestDiscoverOnSaveEnabled`. +(thanks [Lingyu Li](http://github.com/lingyv-li/)) diff --git a/news/1 Enhancements/1902.md b/news/1 Enhancements/1902.md index 328e7dd1c5df..c9272a9ddebd 100644 --- a/news/1 Enhancements/1902.md +++ b/news/1 Enhancements/1902.md @@ -1,2 +1,2 @@ -Added Spanish translation -(thanks [Mario Rubio](https://github.com/mario-mra/)) \ No newline at end of file +Added Spanish translation. +(thanks [Mario Rubio](https://github.com/mario-mra/)) diff --git a/news/2 Fixes/1638.md b/news/2 Fixes/1638.md index 9a493c62233f..82123fa1102c 100644 --- a/news/2 Fixes/1638.md +++ b/news/2 Fixes/1638.md @@ -1 +1,2 @@ -Ensure navigation to definitons follows imports and is transparent to decoration ([#1638](https://github.com/Microsoft/vscode-python/issues/1638); thanks [Peter Law](https://github.com/PeterJCLaw)) +Ensure navigation to definitons follows imports and is transparent to decoration. +(thanks [Peter Law](https://github.com/PeterJCLaw)) diff --git a/news/3 Code Health/1815.md b/news/3 Code Health/1815.md index 9ce860c5f6d6..cd0a4f5d40e7 100644 --- a/news/3 Code Health/1815.md +++ b/news/3 Code Health/1815.md @@ -1 +1 @@ -Update typescript package to 2.9.1 +Update typescript package to 2.9.1. diff --git a/news/3 Code Health/1842.md b/news/3 Code Health/1842.md index 1219cc348f7d..f0ab0021f46d 100644 --- a/news/3 Code Health/1842.md +++ b/news/3 Code Health/1842.md @@ -1 +1 @@ -Update `isort` package to 4.3.4 +Update `isort` package to 4.3.4. diff --git a/news/__main__.py b/news/__main__.py new file mode 100644 index 000000000000..e12476c4ecca --- /dev/null +++ b/news/__main__.py @@ -0,0 +1,3 @@ +import runpy + +runpy.run_module('announce', run_name='__main__', alter_sys=True) diff --git a/package-lock.json b/package-lock.json index d8683fad54c8..dd72857a11a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "python", - "version": "2018.6.0-alpha", + "version": "2018.6.0-beta", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2691,12 +2691,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2716,7 +2718,8 @@ "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", @@ -2864,6 +2867,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } diff --git a/package.json b/package.json index e870c4de7ae8..bfdba28bc78d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2018.6.0-alpha", + "version": "2018.6.0-beta", "publisher": "ms-python", "author": { "name": "Microsoft Corporation" From c9803a7ba8d4d72472b72b43292e33ecb42b14b5 Mon Sep 17 00:00:00 2001 From: Derek Keeler Date: Mon, 11 Jun 2018 17:08:54 -0700 Subject: [PATCH 332/433] Correct npm run vscode:publish problem by handling PATHEXT properly (#1930) Added dev dependency on cross-spawn to get the functionality back --- gulpfile.js | 3 ++- package-lock.json | 40 ++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index c887f26df873..f953df251b53 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -13,6 +13,7 @@ const tslint = require('tslint'); const relative = require('relative'); const ts = require('gulp-typescript'); const cp = require('child_process'); +const spawn = require('cross-spawn'); const colors = require('colors/safe'); const gitmodified = require('gulp-gitmodified'); const path = require('path'); @@ -128,7 +129,7 @@ function hasNativeDependencies() { if (!Array.isArray(nativeDependencies) || nativeDependencies.length === 0) { return false; } - const dependencies = JSON.parse(cp.spawnSync('npm', ['ls', '--json', '--prod']).stdout.toString()); + const dependencies = JSON.parse(spawn.sync('npm', ['ls', '--json', '--prod']).stdout.toString()); const jsonProperties = Object.keys(flat.flatten(dependencies)); nativeDependencies = _.flatMap(nativeDependencies, item => path.dirname(item.substring(item.indexOf('node_modules') + 'node_modules'.length)).split(path.sep)) .filter(item => item.length > 0) diff --git a/package-lock.json b/package-lock.json index dd72857a11a7..c5210b5d241a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1460,6 +1460,19 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, "crypt": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", @@ -6687,6 +6700,12 @@ "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "dev": true }, + "nice-try": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.4.tgz", + "integrity": "sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==", + "dev": true + }, "nise": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/nise/-/nise-1.3.3.tgz", @@ -7143,6 +7162,12 @@ "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", "dev": true }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, "path-parse": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", @@ -7856,6 +7881,21 @@ } } }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, "shortid": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.8.tgz", diff --git a/package.json b/package.json index bfdba28bc78d..58851c4dcdc6 100644 --- a/package.json +++ b/package.json @@ -1954,6 +1954,7 @@ "chai-as-promised": "^7.1.1", "codecov": "^3.0.0", "colors": "^1.2.1", + "cross-spawn": "^6.0.5", "debounce": "^1.1.0", "decache": "^4.4.0", "del": "^3.0.0", From 2d193da0d20261e37b7b967d5b6120f62b3c637c Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 11 Jun 2018 19:15:04 -0700 Subject: [PATCH 333/433] Add ability to run tests without having to launch VS Code (#1923) Fixes #1922 --- news/3 Code Health/1922.md | 1 + src/test/autocomplete/base.test.ts | 6 +- src/test/autocomplete/pep484.test.ts | 4 +- src/test/autocomplete/pep526.test.ts | 4 +- src/test/common.ts | 18 +- src/test/constants.ts | 2 +- src/test/definitions/hover.jedi.test.ts | 4 +- src/test/definitions/hover.ptvs.test.ts | 4 +- src/test/definitions/parallel.jedi.test.ts | 4 +- src/test/definitions/parallel.ptvs.test.ts | 4 +- src/test/mocks/vsc/README.md | 6 + src/test/mocks/vsc/arrays.ts | 403 ++++ src/test/mocks/vsc/extHostedTypes.ts | 1922 +++++++++++++++++ src/test/mocks/vsc/htmlContent.ts | 97 + src/test/mocks/vsc/index.ts | 158 ++ src/test/mocks/vsc/position.ts | 157 ++ src/test/mocks/vsc/range.ts | 384 ++++ src/test/mocks/vsc/selection.ts | 211 ++ src/test/mocks/vsc/strings.ts | 40 + src/test/mocks/vsc/telemetryReporter.ts | 16 + src/test/mocks/vsc/uri.ts | 474 ++++ ....test.ts => completionSource.unit.test.ts} | 0 .../{repl.test.ts => repl.unit.test.ts} | 0 ...er.test.ts => symbolProvider.unit.test.ts} | 0 ...terminal.test.ts => terminal.unit.test.ts} | 0 src/test/signature/signature.jedi.test.ts | 4 +- src/test/signature/signature.ptvs.test.ts | 4 +- ...t.ts => codeExecutionManager.unit.test.ts} | 0 ...t.ts => djangoShellCodeExect.unit.test.ts} | 0 .../{helper.test.ts => helper.unit.test.ts} | 0 src/test/vscode-mock.ts | 84 +- 31 files changed, 3929 insertions(+), 82 deletions(-) create mode 100644 news/3 Code Health/1922.md create mode 100644 src/test/mocks/vsc/README.md create mode 100644 src/test/mocks/vsc/arrays.ts create mode 100644 src/test/mocks/vsc/extHostedTypes.ts create mode 100644 src/test/mocks/vsc/htmlContent.ts create mode 100644 src/test/mocks/vsc/index.ts create mode 100644 src/test/mocks/vsc/position.ts create mode 100644 src/test/mocks/vsc/range.ts create mode 100644 src/test/mocks/vsc/selection.ts create mode 100644 src/test/mocks/vsc/strings.ts create mode 100644 src/test/mocks/vsc/telemetryReporter.ts create mode 100644 src/test/mocks/vsc/uri.ts rename src/test/providers/{completionSource.test.ts => completionSource.unit.test.ts} (100%) rename src/test/providers/{repl.test.ts => repl.unit.test.ts} (100%) rename src/test/providers/{symbolProvider.test.ts => symbolProvider.unit.test.ts} (100%) rename src/test/providers/{terminal.test.ts => terminal.unit.test.ts} (100%) rename src/test/terminals/codeExecution/{codeExecutionManager.test.ts => codeExecutionManager.unit.test.ts} (100%) rename src/test/terminals/codeExecution/{djangoShellCodeExect.test.ts => djangoShellCodeExect.unit.test.ts} (100%) rename src/test/terminals/codeExecution/{helper.test.ts => helper.unit.test.ts} (100%) diff --git a/news/3 Code Health/1922.md b/news/3 Code Health/1922.md new file mode 100644 index 000000000000..ce836f31d3b9 --- /dev/null +++ b/news/3 Code Health/1922.md @@ -0,0 +1 @@ +Add ability to run tests without having to launch VS Code. diff --git a/src/test/autocomplete/base.test.ts b/src/test/autocomplete/base.test.ts index f3289e359dfd..5e582b00bfb8 100644 --- a/src/test/autocomplete/base.test.ts +++ b/src/test/autocomplete/base.test.ts @@ -8,7 +8,7 @@ import * as path from 'path'; import * as vscode from 'vscode'; import { IConfigurationService } from '../../client/common/types'; import { rootWorkspaceUri } from '../common'; -import { closeActiveWindows, initialize, initializeTest, IS_ANALYSIS_ENGINE_TEST } from '../initialize'; +import { closeActiveWindows, initialize, initializeTest, IsAnalysisEngineTest } from '../initialize'; import { UnitTestIocContainer } from '../unittests/serviceRegistry'; const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); @@ -104,7 +104,7 @@ suite('Autocomplete', function () { // https://github.com/DonJayamanne/pythonVSCode/issues/630 test('For "abc.decorators"', async () => { // Disabled for MS Python Code Analysis, see https://github.com/Microsoft/PTVS/issues/3857 - if (IS_ANALYSIS_ENGINE_TEST) { + if (IsAnalysisEngineTest()) { return; } const textDocument = await vscode.workspace.openTextDocument(fileDecorator); @@ -205,7 +205,7 @@ suite('Autocomplete', function () { test('Suppress in strings/comments', async () => { // Excluded from MS Python Code Analysis b/c skipping of strings and comments // is not yet there. See https://github.com/Microsoft/PTVS/issues/3798 - if (IS_ANALYSIS_ENGINE_TEST) { + if (IsAnalysisEngineTest()) { return; } const positions = [ diff --git a/src/test/autocomplete/pep484.test.ts b/src/test/autocomplete/pep484.test.ts index bac83c7afefa..288300a101bf 100644 --- a/src/test/autocomplete/pep484.test.ts +++ b/src/test/autocomplete/pep484.test.ts @@ -2,7 +2,7 @@ import * as assert from 'assert'; import * as path from 'path'; import * as vscode from 'vscode'; import { rootWorkspaceUri } from '../common'; -import { IS_ANALYSIS_ENGINE_TEST } from '../constants'; +import { IsAnalysisEngineTest } from '../constants'; import { closeActiveWindows, initialize, initializeTest } from '../initialize'; import { UnitTestIocContainer } from '../unittests/serviceRegistry'; @@ -14,7 +14,7 @@ suite('Autocomplete PEP 484', () => { let ioc: UnitTestIocContainer; suiteSetup(async function () { // https://github.com/Microsoft/PTVS/issues/3917 - if (IS_ANALYSIS_ENGINE_TEST) { + if (IsAnalysisEngineTest()) { // tslint:disable-next-line:no-invalid-this this.skip(); } diff --git a/src/test/autocomplete/pep526.test.ts b/src/test/autocomplete/pep526.test.ts index 24aadb2eb04d..01cc82f932df 100644 --- a/src/test/autocomplete/pep526.test.ts +++ b/src/test/autocomplete/pep526.test.ts @@ -2,7 +2,7 @@ import * as assert from 'assert'; import * as path from 'path'; import * as vscode from 'vscode'; import { rootWorkspaceUri } from '../common'; -import { closeActiveWindows, initialize, initializeTest, IS_ANALYSIS_ENGINE_TEST } from '../initialize'; +import { closeActiveWindows, initialize, initializeTest, IsAnalysisEngineTest } from '../initialize'; import { UnitTestIocContainer } from '../unittests/serviceRegistry'; const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); @@ -14,7 +14,7 @@ suite('Autocomplete PEP 526', () => { let ioc: UnitTestIocContainer; suiteSetup(async function () { // https://github.com/Microsoft/PTVS/issues/3917 - if (IS_ANALYSIS_ENGINE_TEST) { + if (IsAnalysisEngineTest()) { // tslint:disable-next-line:no-invalid-this this.skip(); } diff --git a/src/test/common.ts b/src/test/common.ts index 15b600e7d43b..a8abb1271567 100644 --- a/src/test/common.ts +++ b/src/test/common.ts @@ -7,6 +7,8 @@ import { IS_MULTI_ROOT_TEST } from './initialize'; export * from './core'; +// tslint:disable:no-non-null-assertion no-unsafe-any await-promise no-any no-use-before-declare no-string-based-set-timeout no-unsafe-any no-any no-invalid-this + const fileInNonRootWorkspace = path.join(__dirname, '..', '..', 'src', 'test', 'pythonFiles', 'dummy.py'); export const rootWorkspaceUri = getWorkspaceRoot(); @@ -31,7 +33,6 @@ export async function updateSetting(setting: PythonSettingKeys, value: {} | unde PythonSettings.dispose(); return; } - // tslint:disable-next-line:await-promise await settings.update(setting, value, configTarget); await sleep(2000); PythonSettings.dispose(); @@ -48,25 +49,19 @@ function getWorkspaceRoot() { return workspaceFolder ? workspaceFolder.uri : workspace.workspaceFolders[0].uri; } -// tslint:disable-next-line:no-any export function retryAsync(wrapped: Function, retryCount: number = 2) { - // tslint:disable-next-line:no-any return async (...args: any[]) => { return new Promise((resolve, reject) => { - // tslint:disable-next-line:no-any const reasons: any[] = []; const makeCall = () => { - // tslint:disable-next-line:no-unsafe-any no-any no-invalid-this wrapped.call(this as Function, ...args) - // tslint:disable-next-line:no-unsafe-any no-any .then(resolve, (reason: any) => { reasons.push(reason); if (reasons.length >= retryCount) { reject(reasons); } else { // If failed once, lets wait for some time before trying again. - // tslint:disable-next-line:no-string-based-set-timeout setTimeout(makeCall, 500); } }); @@ -91,11 +86,8 @@ async function setPythonPathInWorkspace(resource: string | Uri | undefined, conf } } async function restoreGlobalPythonPathSetting(): Promise { - // tslint:disable-next-line:no-any const pythonConfig = workspace.getConfiguration('python', null as any as Uri); - // tslint:disable-next-line:no-non-null-assertion const currentGlobalPythonPathSetting = pythonConfig.inspect('pythonPath')!.globalValue; - // tslint:disable-next-line:no-use-before-declare if (globalPythonPathSetting !== currentGlobalPythonPathSetting) { await pythonConfig.update('pythonPath', undefined, true); } @@ -115,16 +107,14 @@ export async function deleteFile(file: string) { } } -// tslint:disable-next-line:no-non-null-assertion -const globalPythonPathSetting = workspace.getConfiguration('python').inspect('pythonPath')!.globalValue; +// In some tests we will be mocking VS Code API (mocked classes) +const globalPythonPathSetting = workspace.getConfiguration('python') ? workspace.getConfiguration('python').inspect('pythonPath')!.globalValue : 'python'; export const clearPythonPathInWorkspaceFolder = async (resource: string | Uri) => retryAsync(setPythonPathInWorkspace)(resource, ConfigurationTarget.WorkspaceFolder); export const setPythonPathInWorkspaceRoot = async (pythonPath: string) => retryAsync(setPythonPathInWorkspace)(undefined, ConfigurationTarget.Workspace, pythonPath); export const resetGlobalPythonPathSetting = async () => retryAsync(restoreGlobalPythonPathSetting)(); function getPythonPath(): string { - // tslint:disable-next-line:no-unsafe-any if (process.env.TRAVIS_PYTHON_PATH && fs.existsSync(process.env.TRAVIS_PYTHON_PATH)) { - // tslint:disable-next-line:no-unsafe-any return process.env.TRAVIS_PYTHON_PATH; } return 'python'; diff --git a/src/test/constants.ts b/src/test/constants.ts index aaa771464d72..d66b46013b8a 100644 --- a/src/test/constants.ts +++ b/src/test/constants.ts @@ -30,5 +30,5 @@ function isMultitrootTest() { return Array.isArray(workspace.workspaceFolders) && workspace.workspaceFolders.length > 1; } -export const IS_ANALYSIS_ENGINE_TEST = +export const IsAnalysisEngineTest = () => !IS_TRAVIS && (process.env.VSC_PYTHON_ANALYSIS === '1' || !PythonSettings.getInstance().jediEnabled); diff --git a/src/test/definitions/hover.jedi.test.ts b/src/test/definitions/hover.jedi.test.ts index 5d1ea8b386cd..969d7c7f780b 100644 --- a/src/test/definitions/hover.jedi.test.ts +++ b/src/test/definitions/hover.jedi.test.ts @@ -2,7 +2,7 @@ import * as assert from 'assert'; import { EOL } from 'os'; import * as path from 'path'; import * as vscode from 'vscode'; -import { IS_ANALYSIS_ENGINE_TEST } from '../constants'; +import { IsAnalysisEngineTest } from '../constants'; import { closeActiveWindows, initialize, initializeTest } from '../initialize'; import { normalizeMarkedString } from '../textUtils'; @@ -18,7 +18,7 @@ const fileStringFormat = path.join(hoverPath, 'stringFormat.py'); // tslint:disable-next-line:max-func-body-length suite('Hover Definition (Jedi)', () => { suiteSetup(async function () { - if (IS_ANALYSIS_ENGINE_TEST) { + if (IsAnalysisEngineTest()) { // tslint:disable-next-line:no-invalid-this this.skip(); } diff --git a/src/test/definitions/hover.ptvs.test.ts b/src/test/definitions/hover.ptvs.test.ts index cb297e0e9374..f00411fedfad 100644 --- a/src/test/definitions/hover.ptvs.test.ts +++ b/src/test/definitions/hover.ptvs.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import * as path from 'path'; import * as vscode from 'vscode'; import '../../client/common/extensions'; -import { IS_ANALYSIS_ENGINE_TEST } from '../constants'; +import { IsAnalysisEngineTest } from '../constants'; import { closeActiveWindows, initialize, initializeTest } from '../initialize'; import { normalizeMarkedString } from '../textUtils'; @@ -23,7 +23,7 @@ let textDocument: vscode.TextDocument; // tslint:disable-next-line:max-func-body-length suite('Hover Definition (Analysis Engine)', () => { suiteSetup(async function () { - if (!IS_ANALYSIS_ENGINE_TEST) { + if (!IsAnalysisEngineTest()) { // tslint:disable-next-line:no-invalid-this this.skip(); } diff --git a/src/test/definitions/parallel.jedi.test.ts b/src/test/definitions/parallel.jedi.test.ts index 627352c60947..09fb921ecbb3 100644 --- a/src/test/definitions/parallel.jedi.test.ts +++ b/src/test/definitions/parallel.jedi.test.ts @@ -3,7 +3,7 @@ import { EOL } from 'os'; import * as path from 'path'; import * as vscode from 'vscode'; import { IS_WINDOWS } from '../../client/common/platform/constants'; -import { IS_ANALYSIS_ENGINE_TEST } from '../constants'; +import { IsAnalysisEngineTest } from '../constants'; import { closeActiveWindows, initialize } from '../initialize'; import { normalizeMarkedString } from '../textUtils'; @@ -12,7 +12,7 @@ const fileOne = path.join(autoCompPath, 'one.py'); suite('Code, Hover Definition and Intellisense (Jedi)', () => { suiteSetup(async function () { - if (IS_ANALYSIS_ENGINE_TEST) { + if (IsAnalysisEngineTest()) { // tslint:disable-next-line:no-invalid-this this.skip(); } diff --git a/src/test/definitions/parallel.ptvs.test.ts b/src/test/definitions/parallel.ptvs.test.ts index 6740350d374d..339585130cea 100644 --- a/src/test/definitions/parallel.ptvs.test.ts +++ b/src/test/definitions/parallel.ptvs.test.ts @@ -6,7 +6,7 @@ import { EOL } from 'os'; import * as path from 'path'; import * as vscode from 'vscode'; import { IS_WINDOWS } from '../../client/common/platform/constants'; -import { IS_ANALYSIS_ENGINE_TEST } from '../constants'; +import { IsAnalysisEngineTest } from '../constants'; import { closeActiveWindows, initialize } from '../initialize'; import { normalizeMarkedString } from '../textUtils'; @@ -19,7 +19,7 @@ suite('Code, Hover Definition and Intellisense (MS Python Code Analysis)', () => // tslint:disable-next-line:no-invalid-this this.skip(); - if (!IS_ANALYSIS_ENGINE_TEST) { + if (!IsAnalysisEngineTest()) { // tslint:disable-next-line:no-invalid-this this.skip(); } diff --git a/src/test/mocks/vsc/README.md b/src/test/mocks/vsc/README.md new file mode 100644 index 000000000000..39fbe1508bbd --- /dev/null +++ b/src/test/mocks/vsc/README.md @@ -0,0 +1,6 @@ +# This folder contains classes exposed by VS Code required in running the unit tests. +* These classes are only used when running unit tests that are not hosted by VS Code. +* So even if these classes were buggy, it doesn't matter, running the tests under VS Code host will ensure the right classes are available. +* The purpose of these classes are to avoid having to use VS Code as the hosting environment for the tests, making it faster to run the tests and not have to rely on VS Code host to run the tests. +* Everyting in here must either be within a namespace prefixed with `vscMock` or exported types must be prefixed with `vscMock`. +This is to prevent developers from accidentally importing them into their Code. Even if they did, the extension would fail to load and tests would fail. diff --git a/src/test/mocks/vsc/arrays.ts b/src/test/mocks/vsc/arrays.ts new file mode 100644 index 000000000000..bae8cc34b8b2 --- /dev/null +++ b/src/test/mocks/vsc/arrays.ts @@ -0,0 +1,403 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +// tslint:disable:all + +export namespace vscMockArrays { + /** + * Returns the last element of an array. + * @param array The array. + * @param n Which element from the end (default is zero). + */ + export function tail(array: T[], n: number = 0): T { + return array[array.length - (1 + n)]; + } + + export function equals(one: T[], other: T[], itemEquals: (a: T, b: T) => boolean = (a, b) => a === b): boolean { + if (one.length !== other.length) { + return false; + } + + for (let i = 0, len = one.length; i < len; i++) { + if (!itemEquals(one[i], other[i])) { + return false; + } + } + + return true; + } + + export function binarySearch(array: T[], key: T, comparator: (op1: T, op2: T) => number): number { + let low = 0, + high = array.length - 1; + + while (low <= high) { + let mid = ((low + high) / 2) | 0; + let comp = comparator(array[mid], key); + if (comp < 0) { + low = mid + 1; + } else if (comp > 0) { + high = mid - 1; + } else { + return mid; + } + } + return -(low + 1); + } + + /** + * Takes a sorted array and a function p. The array is sorted in such a way that all elements where p(x) is false + * are located before all elements where p(x) is true. + * @returns the least x for which p(x) is true or array.length if no element fullfills the given function. + */ + export function findFirst(array: T[], p: (x: T) => boolean): number { + let low = 0, high = array.length; + if (high === 0) { + return 0; // no children + } + while (low < high) { + let mid = Math.floor((low + high) / 2); + if (p(array[mid])) { + high = mid; + } else { + low = mid + 1; + } + } + return low; + } + + /** + * Like `Array#sort` but always stable. Usually runs a little slower `than Array#sort` + * so only use this when actually needing stable sort. + */ + export function mergeSort(data: T[], compare: (a: T, b: T) => number): T[] { + _divideAndMerge(data, compare); + return data; + } + + function _divideAndMerge(data: T[], compare: (a: T, b: T) => number): void { + if (data.length <= 1) { + // sorted + return; + } + const p = (data.length / 2) | 0; + const left = data.slice(0, p); + const right = data.slice(p); + + _divideAndMerge(left, compare); + _divideAndMerge(right, compare); + + let leftIdx = 0; + let rightIdx = 0; + let i = 0; + while (leftIdx < left.length && rightIdx < right.length) { + let ret = compare(left[leftIdx], right[rightIdx]); + if (ret <= 0) { + // smaller_equal -> take left to preserve order + data[i++] = left[leftIdx++]; + } else { + // greater -> take right + data[i++] = right[rightIdx++]; + } + } + while (leftIdx < left.length) { + data[i++] = left[leftIdx++]; + } + while (rightIdx < right.length) { + data[i++] = right[rightIdx++]; + } + } + + export function groupBy(data: T[], compare: (a: T, b: T) => number): T[][] { + const result: T[][] = []; + let currentGroup: T[]; + for (const element of mergeSort(data.slice(0), compare)) { + if (!currentGroup || compare(currentGroup[0], element) !== 0) { + currentGroup = [element]; + result.push(currentGroup); + } else { + currentGroup.push(element); + } + } + return result; + } + + type IMutableSplice = Array & any & { + deleteCount: number; + } + type ISplice = Array & any; + + /** + * Diffs two *sorted* arrays and computes the splices which apply the diff. + */ + export function sortedDiff(before: T[], after: T[], compare: (a: T, b: T) => number): ISplice[] { + const result: IMutableSplice[] = []; + + function pushSplice(start: number, deleteCount: number, toInsert: T[]): void { + if (deleteCount === 0 && toInsert.length === 0) { + return; + } + + const latest = result[result.length - 1]; + + if (latest && latest.start + latest.deleteCount === start) { + latest.deleteCount += deleteCount; + latest.toInsert.push(...toInsert); + } else { + result.push({ start, deleteCount, toInsert }); + } + } + + let beforeIdx = 0; + let afterIdx = 0; + + while (true) { + if (beforeIdx === before.length) { + pushSplice(beforeIdx, 0, after.slice(afterIdx)); + break; + } + if (afterIdx === after.length) { + pushSplice(beforeIdx, before.length - beforeIdx, []); + break; + } + + const beforeElement = before[beforeIdx]; + const afterElement = after[afterIdx]; + const n = compare(beforeElement, afterElement); + if (n === 0) { + // equal + beforeIdx += 1; + afterIdx += 1; + } else if (n < 0) { + // beforeElement is smaller -> before element removed + pushSplice(beforeIdx, 1, []); + beforeIdx += 1; + } else if (n > 0) { + // beforeElement is greater -> after element added + pushSplice(beforeIdx, 0, [afterElement]); + afterIdx += 1; + } + } + + return result; + } + + /** + * Takes two *sorted* arrays and computes their delta (removed, added elements). + * Finishes in `Math.min(before.length, after.length)` steps. + * @param before + * @param after + * @param compare + */ + export function delta(before: T[], after: T[], compare: (a: T, b: T) => number): { removed: T[], added: T[] } { + const splices = sortedDiff(before, after, compare); + const removed: T[] = []; + const added: T[] = []; + + for (const splice of splices) { + removed.push(...before.slice(splice.start, splice.start + splice.deleteCount)); + added.push(...splice.toInsert); + } + + return { removed, added }; + } + + /** + * Returns the top N elements from the array. + * + * Faster than sorting the entire array when the array is a lot larger than N. + * + * @param array The unsorted array. + * @param compare A sort function for the elements. + * @param n The number of elements to return. + * @return The first n elemnts from array when sorted with compare. + */ + export function top(array: T[], compare: (a: T, b: T) => number, n: number): T[] { + if (n === 0) { + return []; + } + const result = array.slice(0, n).sort(compare); + topStep(array, compare, result, n, array.length); + return result; + } + + function topStep(array: T[], compare: (a: T, b: T) => number, result: T[], i: number, m: number): void { + for (const n = result.length; i < m; i++) { + const element = array[i]; + if (compare(element, result[n - 1]) < 0) { + result.pop(); + const j = findFirst(result, e => compare(element, e) < 0); + result.splice(j, 0, element); + } + } + } + + /** + * @returns a new array with all undefined or null values removed. The original array is not modified at all. + */ + export function coalesce(array: T[]): T[] { + if (!array) { + return array; + } + + return array.filter(e => !!e); + } + + /** + * Moves the element in the array for the provided positions. + */ + export function move(array: any[], from: number, to: number): void { + array.splice(to, 0, array.splice(from, 1)[0]); + } + + /** + * @returns {{false}} if the provided object is an array + * and not empty. + */ + export function isFalsyOrEmpty(obj: any): boolean { + return !Array.isArray(obj) || (>obj).length === 0; + } + + /** + * Removes duplicates from the given array. The optional keyFn allows to specify + * how elements are checked for equalness by returning a unique string for each. + */ + export function distinct(array: T[], keyFn?: (t: T) => string): T[] { + if (!keyFn) { + return array.filter((element, position) => { + return array.indexOf(element) === position; + }); + } + + const seen: { [key: string]: boolean; } = Object.create(null); + return array.filter((elem) => { + const key = keyFn(elem); + if (seen[key]) { + return false; + } + + seen[key] = true; + + return true; + }); + } + + export function uniqueFilter(keyFn: (t: T) => string): (t: T) => boolean { + const seen: { [key: string]: boolean; } = Object.create(null); + + return element => { + const key = keyFn(element); + + if (seen[key]) { + return false; + } + + seen[key] = true; + return true; + }; + } + + export function firstIndex(array: T[], fn: (item: T) => boolean): number { + for (let i = 0; i < array.length; i++) { + const element = array[i]; + + if (fn(element)) { + return i; + } + } + + return -1; + } + + export function first(array: T[], fn: (item: T) => boolean, notFoundValue: T = null): T { + const index = firstIndex(array, fn); + return index < 0 ? notFoundValue : array[index]; + } + + export function commonPrefixLength(one: T[], other: T[], equals: (a: T, b: T) => boolean = (a, b) => a === b): number { + let result = 0; + + for (let i = 0, len = Math.min(one.length, other.length); i < len && equals(one[i], other[i]); i++) { + result++; + } + + return result; + } + + export function flatten(arr: T[][]): T[] { + return [].concat(...arr); + } + + export function range(to: number): number[]; + export function range(from: number, to: number): number[]; + export function range(arg: number, to?: number): number[] { + let from = typeof to === 'number' ? arg : 0; + + if (typeof to === 'number') { + from = arg; + } else { + from = 0; + to = arg; + } + + const result: number[] = []; + + if (from <= to) { + for (let i = from; i < to; i++) { + result.push(i); + } + } else { + for (let i = from; i > to; i--) { + result.push(i); + } + } + + return result; + } + + export function fill(num: number, valueFn: () => T, arr: T[] = []): T[] { + for (let i = 0; i < num; i++) { + arr[i] = valueFn(); + } + + return arr; + } + + export function index(array: T[], indexer: (t: T) => string): { [key: string]: T; }; + export function index(array: T[], indexer: (t: T) => string, merger?: (t: T, r: R) => R): { [key: string]: R; }; + export function index(array: T[], indexer: (t: T) => string, merger: (t: T, r: R) => R = t => t as any): { [key: string]: R; } { + return array.reduce((r, t) => { + const key = indexer(t); + r[key] = merger(t, r[key]); + return r; + }, Object.create(null)); + } + + /** + * Inserts an element into an array. Returns a function which, when + * called, will remove that element from the array. + */ + export function insert(array: T[], element: T): () => void { + array.push(element); + + return () => { + const index = array.indexOf(element); + if (index > -1) { + array.splice(index, 1); + } + }; + } + + /** + * Insert `insertArr` inside `target` at `insertIndex`. + * Please don't touch unless you understand https://jsperf.com/inserting-an-array-within-an-array + */ + export function arrayInsert(target: T[], insertIndex: number, insertArr: T[]): T[] { + const before = target.slice(0, insertIndex); + const after = target.slice(insertIndex); + return before.concat(insertArr, after); + } +} diff --git a/src/test/mocks/vsc/extHostedTypes.ts b/src/test/mocks/vsc/extHostedTypes.ts new file mode 100644 index 000000000000..0d5f6bc2f2ae --- /dev/null +++ b/src/test/mocks/vsc/extHostedTypes.ts @@ -0,0 +1,1922 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +// import * as crypto from 'crypto'; + +// tslint:disable:all + +import { relative } from 'path'; +import * as vscode from 'vscode'; +import { vscMockHtmlContent } from './htmlContent'; +import { vscMockStrings } from './strings'; +import { vscUri } from './uri'; + +export namespace vscMockExtHostedTypes { + + export interface IRelativePattern { + base: string; + pattern: string; + pathToRelative(from: string, to: string): string; + } + + // tslint:disable:all + const illegalArgument = (msg = 'Illegal Argument') => new Error(msg); + + export class Disposable { + + static from(...disposables: { dispose(): any }[]): Disposable { + return new Disposable(function () { + if (disposables) { + for (let disposable of disposables) { + if (disposable && typeof disposable.dispose === 'function') { + disposable.dispose(); + } + } + disposables = undefined; + } + }); + } + + private _callOnDispose: Function; + + constructor(callOnDispose: Function) { + this._callOnDispose = callOnDispose; + } + + dispose(): any { + if (typeof this._callOnDispose === 'function') { + this._callOnDispose(); + this._callOnDispose = undefined; + } + } + } + + export class Position { + + static Min(...positions: Position[]): Position { + let result = positions.pop(); + for (let p of positions) { + if (p.isBefore(result)) { + result = p; + } + } + return result; + } + + static Max(...positions: Position[]): Position { + let result = positions.pop(); + for (let p of positions) { + if (p.isAfter(result)) { + result = p; + } + } + return result; + } + + static isPosition(other: any): other is Position { + if (!other) { + return false; + } + if (other instanceof Position) { + return true; + } + let { line, character } = other; + if (typeof line === 'number' && typeof character === 'number') { + return true; + } + return false; + } + + private _line: number; + private _character: number; + + get line(): number { + return this._line; + } + + get character(): number { + return this._character; + } + + constructor(line: number, character: number) { + if (line < 0) { + throw illegalArgument('line must be non-negative'); + } + if (character < 0) { + throw illegalArgument('character must be non-negative'); + } + this._line = line; + this._character = character; + } + + isBefore(other: Position): boolean { + if (this._line < other._line) { + return true; + } + if (other._line < this._line) { + return false; + } + return this._character < other._character; + } + + isBeforeOrEqual(other: Position): boolean { + if (this._line < other._line) { + return true; + } + if (other._line < this._line) { + return false; + } + return this._character <= other._character; + } + + isAfter(other: Position): boolean { + return !this.isBeforeOrEqual(other); + } + + isAfterOrEqual(other: Position): boolean { + return !this.isBefore(other); + } + + isEqual(other: Position): boolean { + return this._line === other._line && this._character === other._character; + } + + compareTo(other: Position): number { + if (this._line < other._line) { + return -1; + } else if (this._line > other.line) { + return 1; + } else { + // equal line + if (this._character < other._character) { + return -1; + } else if (this._character > other._character) { + return 1; + } else { + // equal line and character + return 0; + } + } + } + + translate(change: { lineDelta?: number; characterDelta?: number; }): Position; + translate(lineDelta?: number, characterDelta?: number): Position; + translate(lineDeltaOrChange: number | { lineDelta?: number; characterDelta?: number; }, characterDelta: number = 0): Position { + + if (lineDeltaOrChange === null || characterDelta === null) { + throw illegalArgument(); + } + + let lineDelta: number; + if (typeof lineDeltaOrChange === 'undefined') { + lineDelta = 0; + } else if (typeof lineDeltaOrChange === 'number') { + lineDelta = lineDeltaOrChange; + } else { + lineDelta = typeof lineDeltaOrChange.lineDelta === 'number' ? lineDeltaOrChange.lineDelta : 0; + characterDelta = typeof lineDeltaOrChange.characterDelta === 'number' ? lineDeltaOrChange.characterDelta : 0; + } + + if (lineDelta === 0 && characterDelta === 0) { + return this; + } + return new Position(this.line + lineDelta, this.character + characterDelta); + } + + with(change: { line?: number; character?: number; }): Position; + with(line?: number, character?: number): Position; + with(lineOrChange: number | { line?: number; character?: number; }, character: number = this.character): Position { + + if (lineOrChange === null || character === null) { + throw illegalArgument(); + } + + let line: number; + if (typeof lineOrChange === 'undefined') { + line = this.line; + + } else if (typeof lineOrChange === 'number') { + line = lineOrChange; + + } else { + line = typeof lineOrChange.line === 'number' ? lineOrChange.line : this.line; + character = typeof lineOrChange.character === 'number' ? lineOrChange.character : this.character; + } + + if (line === this.line && character === this.character) { + return this; + } + return new Position(line, character); + } + + toJSON(): any { + return { line: this.line, character: this.character }; + } + } + + export class Range { + + static isRange(thing: any): thing is vscode.Range { + if (thing instanceof Range) { + return true; + } + if (!thing) { + return false; + } + return Position.isPosition((thing).start) + && Position.isPosition((thing.end)); + } + + protected _start: Position; + protected _end: Position; + + get start(): Position { + return this._start; + } + + get end(): Position { + return this._end; + } + + constructor(start: Position, end: Position); + constructor(startLine: number, startColumn: number, endLine: number, endColumn: number); + constructor(startLineOrStart: number | Position, startColumnOrEnd: number | Position, endLine?: number, endColumn?: number) { + let start: Position; + let end: Position; + + if (typeof startLineOrStart === 'number' && typeof startColumnOrEnd === 'number' && typeof endLine === 'number' && typeof endColumn === 'number') { + start = new Position(startLineOrStart, startColumnOrEnd); + end = new Position(endLine, endColumn); + } else if (startLineOrStart instanceof Position && startColumnOrEnd instanceof Position) { + start = startLineOrStart; + end = startColumnOrEnd; + } + + if (!start || !end) { + throw new Error('Invalid arguments'); + } + + if (start.isBefore(end)) { + this._start = start; + this._end = end; + } else { + this._start = end; + this._end = start; + } + } + + contains(positionOrRange: Position | Range): boolean { + if (positionOrRange instanceof Range) { + return this.contains(positionOrRange._start) + && this.contains(positionOrRange._end); + + } else if (positionOrRange instanceof Position) { + if (positionOrRange.isBefore(this._start)) { + return false; + } + if (this._end.isBefore(positionOrRange)) { + return false; + } + return true; + } + return false; + } + + isEqual(other: Range): boolean { + return this._start.isEqual(other._start) && this._end.isEqual(other._end); + } + + intersection(other: Range): Range { + let start = Position.Max(other.start, this._start); + let end = Position.Min(other.end, this._end); + if (start.isAfter(end)) { + // this happens when there is no overlap: + // |-----| + // |----| + return undefined; + } + return new Range(start, end); + } + + union(other: Range): Range { + if (this.contains(other)) { + return this; + } else if (other.contains(this)) { + return other; + } + let start = Position.Min(other.start, this._start); + let end = Position.Max(other.end, this.end); + return new Range(start, end); + } + + get isEmpty(): boolean { + return this._start.isEqual(this._end); + } + + get isSingleLine(): boolean { + return this._start.line === this._end.line; + } + + with(change: { start?: Position, end?: Position }): Range; + with(start?: Position, end?: Position): Range; + with(startOrChange: Position | { start?: Position, end?: Position }, end: Position = this.end): Range { + + if (startOrChange === null || end === null) { + throw illegalArgument(); + } + + let start: Position; + if (!startOrChange) { + start = this.start; + + } else if (Position.isPosition(startOrChange)) { + start = startOrChange; + + } else { + start = startOrChange.start || this.start; + end = startOrChange.end || this.end; + } + + if (start.isEqual(this._start) && end.isEqual(this.end)) { + return this; + } + return new Range(start, end); + } + + toJSON(): any { + return [this.start, this.end]; + } + } + + export class Selection extends Range { + + static isSelection(thing: any): thing is Selection { + if (thing instanceof Selection) { + return true; + } + if (!thing) { + return false; + } + return Range.isRange(thing) + && Position.isPosition((thing).anchor) + && Position.isPosition((thing).active) + && typeof (thing).isReversed === 'boolean'; + } + + private _anchor: Position; + + public get anchor(): Position { + return this._anchor; + } + + private _active: Position; + + public get active(): Position { + return this._active; + } + + constructor(anchor: Position, active: Position); + constructor(anchorLine: number, anchorColumn: number, activeLine: number, activeColumn: number); + constructor(anchorLineOrAnchor: number | Position, anchorColumnOrActive: number | Position, activeLine?: number, activeColumn?: number) { + let anchor: Position; + let active: Position; + + if (typeof anchorLineOrAnchor === 'number' && typeof anchorColumnOrActive === 'number' && typeof activeLine === 'number' && typeof activeColumn === 'number') { + anchor = new Position(anchorLineOrAnchor, anchorColumnOrActive); + active = new Position(activeLine, activeColumn); + } else if (anchorLineOrAnchor instanceof Position && anchorColumnOrActive instanceof Position) { + anchor = anchorLineOrAnchor; + active = anchorColumnOrActive; + } + + if (!anchor || !active) { + throw new Error('Invalid arguments'); + } + + super(anchor, active); + + this._anchor = anchor; + this._active = active; + } + + get isReversed(): boolean { + return this._anchor === this._end; + } + + toJSON() { + return { + start: this.start, + end: this.end, + active: this.active, + anchor: this.anchor + }; + } + } + + export enum EndOfLine { + LF = 1, + CRLF = 2 + } + + export class TextEdit { + + static isTextEdit(thing: any): thing is TextEdit { + if (thing instanceof TextEdit) { + return true; + } + if (!thing) { + return false; + } + return Range.isRange((thing)) + && typeof (thing).newText === 'string'; + } + + static replace(range: Range, newText: string): TextEdit { + return new TextEdit(range, newText); + } + + static insert(position: Position, newText: string): TextEdit { + return TextEdit.replace(new Range(position, position), newText); + } + + static delete(range: Range): TextEdit { + return TextEdit.replace(range, ''); + } + + static setEndOfLine(eol: EndOfLine): TextEdit { + let ret = new TextEdit(undefined, undefined); + ret.newEol = eol; + return ret; + } + + protected _range: Range; + protected _newText: string; + protected _newEol: EndOfLine; + + get range(): Range { + return this._range; + } + + set range(value: Range) { + if (value && !Range.isRange(value)) { + throw illegalArgument('range'); + } + this._range = value; + } + + get newText(): string { + return this._newText || ''; + } + + set newText(value: string) { + if (value && typeof value !== 'string') { + throw illegalArgument('newText'); + } + this._newText = value; + } + + get newEol(): EndOfLine { + return this._newEol; + } + + set newEol(value: EndOfLine) { + if (value && typeof value !== 'number') { + throw illegalArgument('newEol'); + } + this._newEol = value; + } + + constructor(range: Range, newText: string) { + this.range = range; + this.newText = newText; + } + + toJSON(): any { + return { + range: this.range, + newText: this.newText, + newEol: this._newEol + }; + } + } + + export class WorkspaceEdit implements vscode.WorkspaceEdit { + + private _seqPool: number = 0; + + private _resourceEdits: { seq: number, from: vscUri.URI, to: vscUri.URI }[] = []; + private _textEdits = new Map(); + + // createResource(uri: vscode.Uri): void { + // this.renameResource(undefined, uri); + // } + + // deleteResource(uri: vscode.Uri): void { + // this.renameResource(uri, undefined); + // } + + // renameResource(from: vscode.Uri, to: vscode.Uri): void { + // this._resourceEdits.push({ seq: this._seqPool++, from, to }); + // } + + // resourceEdits(): [vscode.Uri, vscode.Uri][] { + // return this._resourceEdits.map(({ from, to }) => (<[vscode.Uri, vscode.Uri]>[from, to])); + // } + + replace(uri: vscUri.URI, range: Range, newText: string): void { + let edit = new TextEdit(range, newText); + let array = this.get(uri); + if (array) { + array.push(edit); + } else { + array = [edit]; + } + this.set(uri, array); + } + + insert(resource: vscUri.URI, position: Position, newText: string): void { + this.replace(resource, new Range(position, position), newText); + } + + delete(resource: vscUri.URI, range: Range): void { + this.replace(resource, range, ''); + } + + has(uri: vscUri.URI): boolean { + return this._textEdits.has(uri.toString()); + } + + set(uri: vscUri.URI, edits: TextEdit[]): void { + let data = this._textEdits.get(uri.toString()); + if (!data) { + data = { seq: this._seqPool++, uri, edits: [] }; + this._textEdits.set(uri.toString(), data); + } + if (!edits) { + data.edits = undefined; + } else { + data.edits = edits.slice(0); + } + } + + get(uri: vscUri.URI): TextEdit[] { + if (!this._textEdits.has(uri.toString())) { + return undefined; + } + const { edits } = this._textEdits.get(uri.toString()); + return edits ? edits.slice() : undefined; + } + + entries(): [vscUri.URI, TextEdit[]][] { + const res: [vscUri.URI, TextEdit[]][] = []; + this._textEdits.forEach(value => res.push([value.uri, value.edits])); + return res.slice(); + } + + allEntries(): ([vscUri.URI, TextEdit[]] | [vscUri.URI, vscUri.URI])[] { + return this.entries(); + // // use the 'seq' the we have assigned when inserting + // // the operation and use that order in the resulting + // // array + // const res: ([vscUri.URI, TextEdit[]] | [vscUri.URI,vscUri.URI])[] = []; + // this._textEdits.forEach(value => { + // const { seq, uri, edits } = value; + // res[seq] = [uri, edits]; + // }); + // this._resourceEdits.forEach(value => { + // const { seq, from, to } = value; + // res[seq] = [from, to]; + // }); + // return res; + } + + get size(): number { + return this._textEdits.size + this._resourceEdits.length; + } + + toJSON(): any { + return this.entries(); + } + } + + export class SnippetString { + + static isSnippetString(thing: any): thing is SnippetString { + if (thing instanceof SnippetString) { + return true; + } + if (!thing) { + return false; + } + return typeof (thing).value === 'string'; + } + + private static _escape(value: string): string { + return value.replace(/\$|}|\\/g, '\\$&'); + } + + private _tabstop: number = 1; + + value: string; + + constructor(value?: string) { + this.value = value || ''; + } + + appendText(string: string): SnippetString { + this.value += SnippetString._escape(string); + return this; + } + + appendTabstop(number: number = this._tabstop++): SnippetString { + this.value += '$'; + this.value += number; + return this; + } + + appendPlaceholder(value: string | ((snippet: SnippetString) => any), number: number = this._tabstop++): SnippetString { + + if (typeof value === 'function') { + const nested = new SnippetString(); + nested._tabstop = this._tabstop; + value(nested); + this._tabstop = nested._tabstop; + value = nested.value; + } else { + value = SnippetString._escape(value); + } + + this.value += '${'; + this.value += number; + this.value += ':'; + this.value += value; + this.value += '}'; + + return this; + } + + appendVariable(name: string, defaultValue?: string | ((snippet: SnippetString) => any)): SnippetString { + + if (typeof defaultValue === 'function') { + const nested = new SnippetString(); + nested._tabstop = this._tabstop; + defaultValue(nested); + this._tabstop = nested._tabstop; + defaultValue = nested.value; + + } else if (typeof defaultValue === 'string') { + defaultValue = defaultValue.replace(/\$|}/g, '\\$&'); + } + + this.value += '${'; + this.value += name; + if (defaultValue) { + this.value += ':'; + this.value += defaultValue; + } + this.value += '}'; + + + return this; + } + } + + export enum DiagnosticTag { + Unnecessary = 1, + } + + export enum DiagnosticSeverity { + Hint = 3, + Information = 2, + Warning = 1, + Error = 0 + } + + export class Location { + + static isLocation(thing: any): thing is Location { + if (thing instanceof Location) { + return true; + } + if (!thing) { + return false; + } + return Range.isRange((thing).range) + && vscUri.URI.isUri((thing).uri); + } + + uri: vscUri.URI; + range: Range; + + constructor(uri: vscUri.URI, rangeOrPosition: Range | Position) { + this.uri = uri; + + if (!rangeOrPosition) { + //that's OK + } else if (rangeOrPosition instanceof Range) { + this.range = rangeOrPosition; + } else if (rangeOrPosition instanceof Position) { + this.range = new Range(rangeOrPosition, rangeOrPosition); + } else { + throw new Error('Illegal argument'); + } + } + + toJSON(): any { + return { + uri: this.uri, + range: this.range + }; + } + } + + export class DiagnosticRelatedInformation { + + static is(thing: any): thing is DiagnosticRelatedInformation { + if (!thing) { + return false; + } + return typeof (thing).message === 'string' + && (thing).location + && Range.isRange((thing).location.range) + && vscUri.URI.isUri((thing).location.uri); + } + + location: Location; + message: string; + + constructor(location: Location, message: string) { + this.location = location; + this.message = message; + } + } + + export class Diagnostic { + + range: Range; + message: string; + source: string; + code: string | number; + severity: DiagnosticSeverity; + relatedInformation: DiagnosticRelatedInformation[]; + customTags?: DiagnosticTag[]; + + constructor(range: Range, message: string, severity: DiagnosticSeverity = DiagnosticSeverity.Error) { + this.range = range; + this.message = message; + this.severity = severity; + } + + toJSON(): any { + return { + severity: DiagnosticSeverity[this.severity], + message: this.message, + range: this.range, + source: this.source, + code: this.code, + }; + } + } + + export class Hover { + + public contents: vscode.MarkdownString[] | vscode.MarkedString[]; + public range: Range; + + constructor( + contents: vscode.MarkdownString | vscode.MarkedString | vscode.MarkdownString[] | vscode.MarkedString[], + range?: Range + ) { + if (!contents) { + throw new Error('Illegal argument, contents must be defined'); + } + if (Array.isArray(contents)) { + this.contents = contents; + } else if (vscMockHtmlContent.isMarkdownString(contents)) { + this.contents = [contents]; + } else { + this.contents = [contents]; + } + this.range = range; + } + } + + export enum DocumentHighlightKind { + Text = 0, + Read = 1, + Write = 2 + } + + export class DocumentHighlight { + + range: Range; + kind: DocumentHighlightKind; + + constructor(range: Range, kind: DocumentHighlightKind = DocumentHighlightKind.Text) { + this.range = range; + this.kind = kind; + } + + toJSON(): any { + return { + range: this.range, + kind: DocumentHighlightKind[this.kind] + }; + } + } + + export enum SymbolKind { + File = 0, + Module = 1, + Namespace = 2, + Package = 3, + Class = 4, + Method = 5, + Property = 6, + Field = 7, + Constructor = 8, + Enum = 9, + Interface = 10, + Function = 11, + Variable = 12, + Constant = 13, + String = 14, + Number = 15, + Boolean = 16, + Array = 17, + Object = 18, + Key = 19, + Null = 20, + EnumMember = 21, + Struct = 22, + Event = 23, + Operator = 24, + TypeParameter = 25 + } + + export class SymbolInformation { + + name: string; + location: Location; + kind: SymbolKind; + containerName: string; + + constructor(name: string, kind: SymbolKind, containerName: string, location: Location); + constructor(name: string, kind: SymbolKind, range: Range, uri?: vscUri.URI, containerName?: string); + constructor(name: string, kind: SymbolKind, rangeOrContainer: string | Range, locationOrUri?: Location | vscUri.URI, containerName?: string) { + this.name = name; + this.kind = kind; + this.containerName = containerName; + + if (typeof rangeOrContainer === 'string') { + this.containerName = rangeOrContainer; + } + + if (locationOrUri instanceof Location) { + this.location = locationOrUri; + } else if (rangeOrContainer instanceof Range) { + this.location = new Location(locationOrUri, rangeOrContainer); + } + } + + toJSON(): any { + return { + name: this.name, + kind: SymbolKind[this.kind], + location: this.location, + containerName: this.containerName + }; + } + } + + export class SymbolInformation2 extends SymbolInformation { + definingRange: Range; + children: SymbolInformation2[]; + constructor(name: string, kind: SymbolKind, containerName: string, location: Location) { + super(name, kind, containerName, location); + + this.children = []; + this.definingRange = location.range; + } + + } + + export enum CodeActionTrigger { + Automatic = 1, + Manual = 2, + } + + export class CodeAction { + title: string; + + command?: vscode.Command; + + edit?: WorkspaceEdit; + + dianostics?: Diagnostic[]; + + kind?: CodeActionKind; + + constructor(title: string, kind?: CodeActionKind) { + this.title = title; + this.kind = kind; + } + } + + + export class CodeActionKind { + private static readonly sep = '.'; + + public static readonly Empty = new CodeActionKind(''); + public static readonly QuickFix = CodeActionKind.Empty.append('quickfix'); + public static readonly Refactor = CodeActionKind.Empty.append('refactor'); + public static readonly RefactorExtract = CodeActionKind.Refactor.append('extract'); + public static readonly RefactorInline = CodeActionKind.Refactor.append('inline'); + public static readonly RefactorRewrite = CodeActionKind.Refactor.append('rewrite'); + public static readonly Source = CodeActionKind.Empty.append('source'); + public static readonly SourceOrganizeImports = CodeActionKind.Source.append('organizeImports'); + + constructor( + public readonly value: string + ) { } + + public append(parts: string): CodeActionKind { + return new CodeActionKind(this.value ? this.value + CodeActionKind.sep + parts : parts); + } + + public contains(other: CodeActionKind): boolean { + return this.value === other.value || vscMockStrings.startsWith(other.value, this.value + CodeActionKind.sep); + } + } + + + export class CodeLens { + + range: Range; + + command: vscode.Command; + + constructor(range: Range, command?: vscode.Command) { + this.range = range; + this.command = command; + } + + get isResolved(): boolean { + return !!this.command; + } + } + + export class MarkdownString { + + value: string; + isTrusted?: boolean; + + constructor(value?: string) { + this.value = value || ''; + } + + appendText(value: string): MarkdownString { + // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash + this.value += value.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&'); + return this; + } + + appendMarkdown(value: string): MarkdownString { + this.value += value; + return this; + } + + appendCodeblock(code: string, language: string = ''): MarkdownString { + this.value += '\n```'; + this.value += language; + this.value += '\n'; + this.value += code; + this.value += '\n```\n'; + return this; + } + } + + export class ParameterInformation { + + label: string; + documentation?: string | MarkdownString; + + constructor(label: string, documentation?: string | MarkdownString) { + this.label = label; + this.documentation = documentation; + } + } + + export class SignatureInformation { + + label: string; + documentation?: string | MarkdownString; + parameters: ParameterInformation[]; + + constructor(label: string, documentation?: string | MarkdownString) { + this.label = label; + this.documentation = documentation; + this.parameters = []; + } + } + + export class SignatureHelp { + + signatures: SignatureInformation[]; + activeSignature: number; + activeParameter: number; + + constructor() { + this.signatures = []; + } + } + + export enum CompletionTriggerKind { + Invoke = 0, + TriggerCharacter = 1, + TriggerForIncompleteCompletions = 2 + } + + export interface CompletionContext { + triggerKind: CompletionTriggerKind; + triggerCharacter: string; + } + + export enum CompletionItemKind { + Text = 0, + Method = 1, + Function = 2, + Constructor = 3, + Field = 4, + Variable = 5, + Class = 6, + Interface = 7, + Module = 8, + Property = 9, + Unit = 10, + Value = 11, + Enum = 12, + Keyword = 13, + Snippet = 14, + Color = 15, + File = 16, + Reference = 17, + Folder = 18, + EnumMember = 19, + Constant = 20, + Struct = 21, + Event = 22, + Operator = 23, + TypeParameter = 24 + } + + export class CompletionItem { + + label: string; + kind: CompletionItemKind; + detail: string; + documentation: string | MarkdownString; + sortText: string; + filterText: string; + insertText: string | SnippetString; + range: Range; + textEdit: TextEdit; + additionalTextEdits: TextEdit[]; + command: vscode.Command; + + constructor(label: string, kind?: CompletionItemKind) { + this.label = label; + this.kind = kind; + } + + toJSON(): any { + return { + label: this.label, + kind: CompletionItemKind[this.kind], + detail: this.detail, + documentation: this.documentation, + sortText: this.sortText, + filterText: this.filterText, + insertText: this.insertText, + textEdit: this.textEdit + }; + } + } + + export class CompletionList { + + isIncomplete?: boolean; + + items: vscode.CompletionItem[]; + + constructor(items: vscode.CompletionItem[] = [], isIncomplete: boolean = false) { + this.items = items; + this.isIncomplete = isIncomplete; + } + } + + export enum ViewColumn { + Active = -1, + One = 1, + Two = 2, + Three = 3 + } + + export enum StatusBarAlignment { + Left = 1, + Right = 2 + } + + export enum TextEditorLineNumbersStyle { + Off = 0, + On = 1, + Relative = 2 + } + + export enum TextDocumentSaveReason { + Manual = 1, + AfterDelay = 2, + FocusOut = 3 + } + + export enum TextEditorRevealType { + Default = 0, + InCenter = 1, + InCenterIfOutsideViewport = 2, + AtTop = 3 + } + + export enum TextEditorSelectionChangeKind { + Keyboard = 1, + Mouse = 2, + Command = 3 + } + + /** + * These values match very carefully the values of `TrackedRangeStickiness` + */ + export enum DecorationRangeBehavior { + /** + * TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges + */ + OpenOpen = 0, + /** + * TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges + */ + ClosedClosed = 1, + /** + * TrackedRangeStickiness.GrowsOnlyWhenTypingBefore + */ + OpenClosed = 2, + /** + * TrackedRangeStickiness.GrowsOnlyWhenTypingAfter + */ + ClosedOpen = 3 + } + + export namespace TextEditorSelectionChangeKind { + export function fromValue(s: string) { + switch (s) { + case 'keyboard': return TextEditorSelectionChangeKind.Keyboard; + case 'mouse': return TextEditorSelectionChangeKind.Mouse; + case 'api': return TextEditorSelectionChangeKind.Command; + } + return undefined; + } + } + + export class DocumentLink { + + range: Range; + + target: vscUri.URI; + + constructor(range: Range, target: vscUri.URI) { + if (target && !(target instanceof vscUri.URI)) { + throw illegalArgument('target'); + } + if (!Range.isRange(range) || range.isEmpty) { + throw illegalArgument('range'); + } + this.range = range; + this.target = target; + } + } + + export class Color { + readonly red: number; + readonly green: number; + readonly blue: number; + readonly alpha: number; + + constructor(red: number, green: number, blue: number, alpha: number) { + this.red = red; + this.green = green; + this.blue = blue; + this.alpha = alpha; + } + } + + export type IColorFormat = string | { opaque: string, transparent: string }; + + export class ColorInformation { + range: Range; + + color: Color; + + constructor(range: Range, color: Color) { + if (color && !(color instanceof Color)) { + throw illegalArgument('color'); + } + if (!Range.isRange(range) || range.isEmpty) { + throw illegalArgument('range'); + } + this.range = range; + this.color = color; + } + } + + export class ColorPresentation { + label: string; + textEdit?: TextEdit; + additionalTextEdits?: TextEdit[]; + + constructor(label: string) { + if (!label || typeof label !== 'string') { + throw illegalArgument('label'); + } + this.label = label; + } + } + + export enum ColorFormat { + RGB = 0, + HEX = 1, + HSL = 2 + } + + export enum SourceControlInputBoxValidationType { + Error = 0, + Warning = 1, + Information = 2 + } + + export enum TaskRevealKind { + Always = 1, + + Silent = 2, + + Never = 3 + } + + export enum TaskPanelKind { + Shared = 1, + + Dedicated = 2, + + New = 3 + } + + export class TaskGroup implements vscode.TaskGroup { + + private _id: string; + + public static Clean: TaskGroup = new TaskGroup('clean', 'Clean'); + + public static Build: TaskGroup = new TaskGroup('build', 'Build'); + + public static Rebuild: TaskGroup = new TaskGroup('rebuild', 'Rebuild'); + + public static Test: TaskGroup = new TaskGroup('test', 'Test'); + + public static from(value: string) { + switch (value) { + case 'clean': + return TaskGroup.Clean; + case 'build': + return TaskGroup.Build; + case 'rebuild': + return TaskGroup.Rebuild; + case 'test': + return TaskGroup.Test; + default: + return undefined; + } + } + + constructor(id: string, _label: string) { + if (typeof id !== 'string') { + throw illegalArgument('name'); + } + if (typeof _label !== 'string') { + throw illegalArgument('name'); + } + this._id = id; + } + + get id(): string { + return this._id; + } + } + + export class ProcessExecution implements vscode.ProcessExecution { + + private _process: string; + private _args: string[]; + private _options: vscode.ProcessExecutionOptions; + + constructor(process: string, options?: vscode.ProcessExecutionOptions); + constructor(process: string, args: string[], options?: vscode.ProcessExecutionOptions); + constructor(process: string, varg1?: string[] | vscode.ProcessExecutionOptions, varg2?: vscode.ProcessExecutionOptions) { + if (typeof process !== 'string') { + throw illegalArgument('process'); + } + this._process = process; + if (varg1 !== void 0) { + if (Array.isArray(varg1)) { + this._args = varg1; + this._options = varg2; + } else { + this._options = varg1; + } + } + if (this._args === void 0) { + this._args = []; + } + } + + + get process(): string { + return this._process; + } + + set process(value: string) { + if (typeof value !== 'string') { + throw illegalArgument('process'); + } + this._process = value; + } + + get args(): string[] { + return this._args; + } + + set args(value: string[]) { + if (!Array.isArray(value)) { + value = []; + } + this._args = value; + } + + get options(): vscode.ProcessExecutionOptions { + return this._options; + } + + set options(value: vscode.ProcessExecutionOptions) { + this._options = value; + } + + public computeId(): string { + // const hash = crypto.createHash('md5'); + // hash.update('process'); + // if (this._process !== void 0) { + // hash.update(this._process); + // } + // if (this._args && this._args.length > 0) { + // for (let arg of this._args) { + // hash.update(arg); + // } + // } + // return hash.digest('hex'); + throw new Error('Not supported'); + } + } + + export class ShellExecution implements vscode.ShellExecution { + + private _commandLine: string; + private _command: string | vscode.ShellQuotedString; + private _args: (string | vscode.ShellQuotedString)[]; + private _options: vscode.ShellExecutionOptions; + + constructor(commandLine: string, options?: vscode.ShellExecutionOptions); + constructor(command: string | vscode.ShellQuotedString, args: (string | vscode.ShellQuotedString)[], options?: vscode.ShellExecutionOptions); + constructor(arg0: string | vscode.ShellQuotedString, arg1?: vscode.ShellExecutionOptions | (string | vscode.ShellQuotedString)[], arg2?: vscode.ShellExecutionOptions) { + if (Array.isArray(arg1)) { + if (!arg0) { + throw illegalArgument('command can\'t be undefined or null'); + } + if (typeof arg0 !== 'string' && typeof arg0.value !== 'string') { + throw illegalArgument('command'); + } + this._command = arg0; + this._args = arg1 as (string | vscode.ShellQuotedString)[]; + this._options = arg2; + } else { + if (typeof arg0 !== 'string') { + throw illegalArgument('commandLine'); + } + this._commandLine = arg0; + this._options = arg1; + } + } + + get commandLine(): string { + return this._commandLine; + } + + set commandLine(value: string) { + if (typeof value !== 'string') { + throw illegalArgument('commandLine'); + } + this._commandLine = value; + } + + get command(): string | vscode.ShellQuotedString { + return this._command; + } + + set command(value: string | vscode.ShellQuotedString) { + if (typeof value !== 'string' && typeof value.value !== 'string') { + throw illegalArgument('command'); + } + this._command = value; + } + + get args(): (string | vscode.ShellQuotedString)[] { + return this._args; + } + + set args(value: (string | vscode.ShellQuotedString)[]) { + this._args = value || []; + } + + get options(): vscode.ShellExecutionOptions { + return this._options; + } + + set options(value: vscode.ShellExecutionOptions) { + this._options = value; + } + + public computeId(): string { + // const hash = crypto.createHash('md5'); + // hash.update('shell'); + // if (this._commandLine !== void 0) { + // hash.update(this._commandLine); + // } + // if (this._command !== void 0) { + // hash.update(typeof this._command === 'string' ? this._command : this._command.value); + // } + // if (this._args && this._args.length > 0) { + // for (let arg of this._args) { + // hash.update(typeof arg === 'string' ? arg : arg.value); + // } + // } + // return hash.digest('hex'); + throw new Error('Not spported'); + } + } + + export enum ShellQuoting { + Escape = 1, + Strong = 2, + Weak = 3 + } + + export enum TaskScope { + Global = 1, + Workspace = 2 + } + + export class Task implements vscode.Task { + + private __id: string; + + private _definition: vscode.TaskDefinition; + private _scope: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder; + private _name: string; + private _execution: ProcessExecution | ShellExecution; + private _problemMatchers: string[]; + private _hasDefinedMatchers: boolean; + private _isBackground: boolean; + private _source: string; + private _group: TaskGroup; + private _presentationOptions: vscode.TaskPresentationOptions; + + constructor(definition: vscode.TaskDefinition, name: string, source: string, execution?: ProcessExecution | ShellExecution, problemMatchers?: string | string[]); + constructor(definition: vscode.TaskDefinition, scope: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder, name: string, source: string, execution?: ProcessExecution | ShellExecution, problemMatchers?: string | string[]); + constructor(definition: vscode.TaskDefinition, arg2: string | (vscode.TaskScope.Global | vscode.TaskScope.Workspace) | vscode.WorkspaceFolder, arg3: any, arg4?: any, arg5?: any, arg6?: any) { + this.definition = definition; + let problemMatchers: string | string[]; + if (typeof arg2 === 'string') { + this.name = arg2; + this.source = arg3; + this.execution = arg4; + problemMatchers = arg5; + } else if (arg2 === TaskScope.Global || arg2 === TaskScope.Workspace) { + this.target = arg2; + this.name = arg3; + this.source = arg4; + this.execution = arg5; + problemMatchers = arg6; + } else { + this.target = arg2; + this.name = arg3; + this.source = arg4; + this.execution = arg5; + problemMatchers = arg6; + } + if (typeof problemMatchers === 'string') { + this._problemMatchers = [problemMatchers]; + this._hasDefinedMatchers = true; + } else if (Array.isArray(problemMatchers)) { + this._problemMatchers = problemMatchers; + this._hasDefinedMatchers = true; + } else { + this._problemMatchers = []; + this._hasDefinedMatchers = false; + } + this._isBackground = false; + } + + get _id(): string { + return this.__id; + } + + set _id(value: string) { + this.__id = value; + } + + private clear(): void { + if (this.__id === void 0) { + return; + } + this.__id = undefined; + this._scope = undefined; + this._definition = undefined; + if (this._execution instanceof ProcessExecution) { + this._definition = { + type: 'process', + id: this._execution.computeId() + }; + } else if (this._execution instanceof ShellExecution) { + this._definition = { + type: 'shell', + id: this._execution.computeId() + }; + } + } + + get definition(): vscode.TaskDefinition { + return this._definition; + } + + set definition(value: vscode.TaskDefinition) { + if (value === void 0 || value === null) { + throw illegalArgument('Kind can\'t be undefined or null'); + } + this.clear(); + this._definition = value; + } + + get scope(): vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder { + return this._scope; + } + + set target(value: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder) { + this.clear(); + this._scope = value; + } + + get name(): string { + return this._name; + } + + set name(value: string) { + if (typeof value !== 'string') { + throw illegalArgument('name'); + } + this.clear(); + this._name = value; + } + + get execution(): ProcessExecution | ShellExecution { + return this._execution; + } + + set execution(value: ProcessExecution | ShellExecution) { + if (value === null) { + value = undefined; + } + this.clear(); + this._execution = value; + } + + get problemMatchers(): string[] { + return this._problemMatchers; + } + + set problemMatchers(value: string[]) { + if (!Array.isArray(value)) { + this._problemMatchers = []; + this._hasDefinedMatchers = false; + return; + } + this.clear(); + this._problemMatchers = value; + this._hasDefinedMatchers = true; + } + + get hasDefinedMatchers(): boolean { + return this._hasDefinedMatchers; + } + + get isBackground(): boolean { + return this._isBackground; + } + + set isBackground(value: boolean) { + if (value !== true && value !== false) { + value = false; + } + this.clear(); + this._isBackground = value; + } + + get source(): string { + return this._source; + } + + set source(value: string) { + if (typeof value !== 'string' || value.length === 0) { + throw illegalArgument('source must be a string of length > 0'); + } + this.clear(); + this._source = value; + } + + get group(): TaskGroup { + return this._group; + } + + set group(value: TaskGroup) { + if (value === void 0 || value === null) { + this._group = undefined; + return; + } + this.clear(); + this._group = value; + } + + get presentationOptions(): vscode.TaskPresentationOptions { + return this._presentationOptions; + } + + set presentationOptions(value: vscode.TaskPresentationOptions) { + if (value === null) { + value = undefined; + } + this.clear(); + this._presentationOptions = value; + } + } + + + export enum ProgressLocation { + SourceControl = 1, + Window = 10, + Notification = 15 + } + + export class TreeItem { + + label?: string; + resourceUri?: vscUri.URI; + iconPath?: string | vscUri.URI | { light: string | vscUri.URI; dark: string | vscUri.URI }; + command?: vscode.Command; + contextValue?: string; + tooltip?: string; + + constructor(label: string, collapsibleState?: vscode.TreeItemCollapsibleState) + constructor(resourceUri: vscUri.URI, collapsibleState?: vscode.TreeItemCollapsibleState) + constructor(arg1: string | vscUri.URI, public collapsibleState: vscode.TreeItemCollapsibleState = TreeItemCollapsibleState.None) { + if (arg1 instanceof vscUri.URI) { + this.resourceUri = arg1; + } else { + this.label = arg1; + } + } + + } + + export enum TreeItemCollapsibleState { + None = 0, + Collapsed = 1, + Expanded = 2 + } + + export class ThemeIcon { + static readonly File = new ThemeIcon('file'); + + static readonly Folder = new ThemeIcon('folder'); + + readonly id: string; + + private constructor(id: string) { + this.id = id; + } + } + + export class ThemeColor { + id: string; + constructor(id: string) { + this.id = id; + } + } + + export enum ConfigurationTarget { + Global = 1, + + Workspace = 2, + + WorkspaceFolder = 3 + } + + export class RelativePattern implements IRelativePattern { + base: string; + pattern: string; + + constructor(base: vscode.WorkspaceFolder | string, pattern: string) { + if (typeof base !== 'string') { + if (!base || !vscUri.URI.isUri(base.uri)) { + throw illegalArgument('base'); + } + } + + if (typeof pattern !== 'string') { + throw illegalArgument('pattern'); + } + + this.base = typeof base === 'string' ? base : base.uri.fsPath; + this.pattern = pattern; + } + + public pathToRelative(from: string, to: string): string { + return relative(from, to); + } + } + + export class Breakpoint { + + readonly enabled: boolean; + readonly condition?: string; + readonly hitCondition?: string; + readonly logMessage?: string; + + protected constructor(enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string) { + this.enabled = typeof enabled === 'boolean' ? enabled : true; + if (typeof condition === 'string') { + this.condition = condition; + } + if (typeof hitCondition === 'string') { + this.hitCondition = hitCondition; + } + if (typeof logMessage === 'string') { + this.logMessage = logMessage; + } + } + } + + export class SourceBreakpoint extends Breakpoint { + readonly location: Location; + + constructor(location: Location, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string) { + super(enabled, condition, hitCondition, logMessage); + if (location === null) { + throw illegalArgument('location'); + } + this.location = location; + } + } + + export class FunctionBreakpoint extends Breakpoint { + readonly functionName: string; + + constructor(functionName: string, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string) { + super(enabled, condition, hitCondition, logMessage); + if (!functionName) { + throw illegalArgument('functionName'); + } + this.functionName = functionName; + } + } + + export class DebugAdapterExecutable { + readonly command: string; + readonly args: string[]; + + constructor(command: string, args?: string[]) { + this.command = command; + this.args = args; + } + } + + export enum LogLevel { + Trace = 1, + Debug = 2, + Info = 3, + Warning = 4, + Error = 5, + Critical = 6, + Off = 7 + } + + //#region file api + + export enum FileChangeType { + Changed = 1, + Created = 2, + Deleted = 3, + } + + export class FileSystemError extends Error { + + static FileExists(messageOrUri?: string | vscUri.URI): FileSystemError { + return new FileSystemError(messageOrUri, 'EntryExists', FileSystemError.FileExists); + } + static FileNotFound(messageOrUri?: string | vscUri.URI): FileSystemError { + return new FileSystemError(messageOrUri, 'EntryNotFound', FileSystemError.FileNotFound); + } + static FileNotADirectory(messageOrUri?: string | vscUri.URI): FileSystemError { + return new FileSystemError(messageOrUri, 'EntryNotADirectory', FileSystemError.FileNotADirectory); + } + static FileIsADirectory(messageOrUri?: string | vscUri.URI): FileSystemError { + return new FileSystemError(messageOrUri, 'EntryIsADirectory', FileSystemError.FileIsADirectory); + } + static NoPermissions(messageOrUri?: string | vscUri.URI): FileSystemError { + return new FileSystemError(messageOrUri, 'NoPermissions', FileSystemError.NoPermissions); + } + static Unavailable(messageOrUri?: string | vscUri.URI): FileSystemError { + return new FileSystemError(messageOrUri, 'Unavailable', FileSystemError.Unavailable); + } + + constructor(uriOrMessage?: string | vscUri.URI, code?: string, terminator?: Function) { + super(vscUri.URI.isUri(uriOrMessage) ? uriOrMessage.toString(true) : uriOrMessage); + this.name = code ? `${code} (FileSystemError)` : `FileSystemError`; + + // workaround when extending builtin objects and when compiling to ES5, see: + // https://github.com/Microsoft/TypeScript-wiki/blob/master/Breaking-Changes.md#extending-built-ins-like-error-array-and-map-may-no-longer-work + if (typeof (Object).setPrototypeOf === 'function') { + (Object).setPrototypeOf(this, FileSystemError.prototype); + } + + if (typeof Error.captureStackTrace === 'function' && typeof terminator === 'function') { + // nice stack traces + Error.captureStackTrace(this, terminator); + } + } + } + + //#endregion + + //#region folding api + + export class FoldingRange { + + start: number; + + end: number; + + kind?: FoldingRangeKind; + + constructor(start: number, end: number, kind?: FoldingRangeKind) { + this.start = start; + this.end = end; + this.kind = kind; + } + } + + export enum FoldingRangeKind { + Comment = 1, + Imports = 2, + Region = 3 + } + + //#endregion + + + export enum CommentThreadCollapsibleState { + /** + * Determines an item is collapsed + */ + Collapsed = 0, + /** + * Determines an item is expanded + */ + Expanded = 1 + } +} diff --git a/src/test/mocks/vsc/htmlContent.ts b/src/test/mocks/vsc/htmlContent.ts new file mode 100644 index 000000000000..5be4dd47fc92 --- /dev/null +++ b/src/test/mocks/vsc/htmlContent.ts @@ -0,0 +1,97 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import { vscMockArrays } from './arrays'; +// tslint:disable:all + +export namespace vscMockHtmlContent { + export interface IMarkdownString { + value: string; + isTrusted?: boolean; + } + + export class MarkdownString implements IMarkdownString { + + value: string; + isTrusted?: boolean; + + constructor(value: string = '') { + this.value = value; + } + + appendText(value: string): MarkdownString { + // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash + this.value += value.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&'); + return this; + } + + appendMarkdown(value: string): MarkdownString { + this.value += value; + return this; + } + + appendCodeblock(langId: string, code: string): MarkdownString { + this.value += '\n```'; + this.value += langId; + this.value += '\n'; + this.value += code; + this.value += '\n```\n'; + return this; + } + } + + export function isEmptyMarkdownString(oneOrMany: IMarkdownString | IMarkdownString[]): boolean { + if (isMarkdownString(oneOrMany)) { + return !oneOrMany.value; + } else if (Array.isArray(oneOrMany)) { + return oneOrMany.every(isEmptyMarkdownString); + } else { + return true; + } + } + + export function isMarkdownString(thing: any): thing is IMarkdownString { + if (thing instanceof MarkdownString) { + return true; + } else if (thing && typeof thing === 'object') { + return typeof (thing).value === 'string' + && (typeof (thing).isTrusted === 'boolean' || (thing).isTrusted === void 0); + } + return false; + } + + export function markedStringsEquals(a: IMarkdownString | IMarkdownString[], b: IMarkdownString | IMarkdownString[]): boolean { + if (!a && !b) { + return true; + } else if (!a || !b) { + return false; + } else if (Array.isArray(a) && Array.isArray(b)) { + return vscMockArrays.equals(a, b, markdownStringEqual); + } else if (isMarkdownString(a) && isMarkdownString(b)) { + return markdownStringEqual(a, b); + } else { + return false; + } + } + + function markdownStringEqual(a: IMarkdownString, b: IMarkdownString): boolean { + if (a === b) { + return true; + } else if (!a || !b) { + return false; + } else { + return a.value === b.value && a.isTrusted === b.isTrusted; + } + } + + export function removeMarkdownEscapes(text: string): string { + if (!text) { + return text; + } + return text.replace(/\\([\\`*_{}[\]()#+\-.!])/g, '$1'); + } +} diff --git a/src/test/mocks/vsc/index.ts b/src/test/mocks/vsc/index.ts new file mode 100644 index 000000000000..77eaad5a0791 --- /dev/null +++ b/src/test/mocks/vsc/index.ts @@ -0,0 +1,158 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:no-invalid-this no-require-imports no-var-requires no-any max-classes-per-file + +import { EventEmitter as NodeEventEmitter } from 'events'; +import * as vscode from 'vscode'; +// export * from './range'; +// export * from './position'; +// export * from './selection'; +export * from './extHostedTypes'; + +export namespace vscMock { + // This is one of the very few classes that we need in our unit tests. + // It is constructed in a number of places, and this is required for verification. + // Using mocked objects for verfications does not work in typemoq. + export class Uri implements vscode.Uri { + private constructor(public readonly scheme: string, public readonly authority: string, + public readonly path: string, public readonly query: string, + public readonly fragment: string, public readonly fsPath) { + + } + public static file(path: string): Uri { + return new Uri('file', '', path, '', '', path); + } + public static parse(value: string): Uri { + return new Uri('http', '', value, '', '', value); + } + public with(change: { scheme?: string; authority?: string; path?: string; query?: string; fragment?: string }): vscode.Uri { + throw new Error('Not implemented'); + } + public toString(skipEncoding?: boolean): string { + return this.fsPath; + } + public toJSON(): any { + return this.fsPath; + } + } + + export class Disposable { + constructor(private callOnDispose: Function) { + } + public dispose(): any { + if (this.callOnDispose) { + this.callOnDispose(); + } + } + } + + export class EventEmitter implements vscode.EventEmitter { + + public event: vscode.Event; + public emitter: NodeEventEmitter; + constructor() { + this.event = this.add; + this.emitter = new NodeEventEmitter(); + } + public fire(data?: T): void { + this.emitter.emit('evt', data); + } + public dispose(): void { + this.emitter.removeAllListeners(); + } + + protected add(listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]): Disposable { + this.emitter.addListener('evt', listener); + return { + dispose: () => { + this.emitter.removeListener('evt', listener); + } + } as any as Disposable; + } + } + + export class CancellationToken extends EventEmitter implements vscode.CancellationToken { + public isCancellationRequested: boolean; + public onCancellationRequested: vscode.Event; + constructor() { + super(); + this.onCancellationRequested = this.add; + } + public cancel() { + this.isCancellationRequested = true; + this.fire(); + } + } + + export class CancellationTokenSource { + public token: CancellationToken; + constructor() { + this.token = new CancellationToken(); + } + public cancel(): void { + this.token.cancel(); + } + public dispose(): void { + this.token.dispose(); + } + } + + export enum CompletionItemKind { + Text = 0, + Method = 1, + Function = 2, + Constructor = 3, + Field = 4, + Variable = 5, + Class = 6, + Interface = 7, + Module = 8, + Property = 9, + Unit = 10, + Value = 11, + Enum = 12, + Keyword = 13, + Snippet = 14, + Color = 15, + Reference = 17, + File = 16, + Folder = 18, + EnumMember = 19, + Constant = 20, + Struct = 21, + Event = 22, + Operator = 23, + TypeParameter = 24 + } + export enum SymbolKind { + File = 0, + Module = 1, + Namespace = 2, + Package = 3, + Class = 4, + Method = 5, + Property = 6, + Field = 7, + Constructor = 8, + Enum = 9, + Interface = 10, + Function = 11, + Variable = 12, + Constant = 13, + String = 14, + Number = 15, + Boolean = 16, + Array = 17, + Object = 18, + Key = 19, + Null = 20, + EnumMember = 21, + Struct = 22, + Event = 23, + Operator = 24, + TypeParameter = 25 + } +} diff --git a/src/test/mocks/vsc/position.ts b/src/test/mocks/vsc/position.ts new file mode 100644 index 000000000000..f901c5f7d9ce --- /dev/null +++ b/src/test/mocks/vsc/position.ts @@ -0,0 +1,157 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the MIT License. See License.txt in the project root for license information. +*--------------------------------------------------------------------------------------------*/ +'use strict'; + +// tslint:disable:all +export namespace vscMockPosition { + /** + * A position in the editor. This interface is suitable for serialization. + */ + export interface IPosition { + /** + * line number (starts at 1) + */ + readonly lineNumber: number; + /** + * column (the first character in a line is between column 1 and column 2) + */ + readonly column: number; + } + + /** + * A position in the editor. + */ + export class Position { + /** + * line number (starts at 1) + */ + public readonly lineNumber: number; + /** + * column (the first character in a line is between column 1 and column 2) + */ + public readonly column: number; + + constructor(lineNumber: number, column: number) { + this.lineNumber = lineNumber; + this.column = column; + } + + /** + * Test if this position equals other position + */ + public equals(other: IPosition): boolean { + return Position.equals(this, other); + } + + /** + * Test if position `a` equals position `b` + */ + public static equals(a: IPosition, b: IPosition): boolean { + if (!a && !b) { + return true; + } + return ( + !!a && + !!b && + a.lineNumber === b.lineNumber && + a.column === b.column + ); + } + + /** + * Test if this position is before other position. + * If the two positions are equal, the result will be false. + */ + public isBefore(other: IPosition): boolean { + return Position.isBefore(this, other); + } + + /** + * Test if position `a` is before position `b`. + * If the two positions are equal, the result will be false. + */ + public static isBefore(a: IPosition, b: IPosition): boolean { + if (a.lineNumber < b.lineNumber) { + return true; + } + if (b.lineNumber < a.lineNumber) { + return false; + } + return a.column < b.column; + } + + /** + * Test if this position is before other position. + * If the two positions are equal, the result will be true. + */ + public isBeforeOrEqual(other: IPosition): boolean { + return Position.isBeforeOrEqual(this, other); + } + + /** + * Test if position `a` is before position `b`. + * If the two positions are equal, the result will be true. + */ + public static isBeforeOrEqual(a: IPosition, b: IPosition): boolean { + if (a.lineNumber < b.lineNumber) { + return true; + } + if (b.lineNumber < a.lineNumber) { + return false; + } + return a.column <= b.column; + } + + /** + * A function that compares positions, useful for sorting + */ + public static compare(a: IPosition, b: IPosition): number { + let aLineNumber = a.lineNumber | 0; + let bLineNumber = b.lineNumber | 0; + + if (aLineNumber === bLineNumber) { + let aColumn = a.column | 0; + let bColumn = b.column | 0; + return aColumn - bColumn; + } + + return aLineNumber - bLineNumber; + } + + /** + * Clone this position. + */ + public clone(): Position { + return new Position(this.lineNumber, this.column); + } + + /** + * Convert to a human-readable representation. + */ + public toString(): string { + return '(' + this.lineNumber + ',' + this.column + ')'; + } + + // --- + + /** + * Create a `Position` from an `IPosition`. + */ + public static lift(pos: IPosition): Position { + return new Position(pos.lineNumber, pos.column); + } + + /** + * Test if `obj` is an `IPosition`. + */ + public static isIPosition(obj: any): obj is IPosition { + return ( + obj + && (typeof obj.lineNumber === 'number') + && (typeof obj.column === 'number') + ); + } + } +} diff --git a/src/test/mocks/vsc/range.ts b/src/test/mocks/vsc/range.ts new file mode 100644 index 000000000000..16d40ff0bb12 --- /dev/null +++ b/src/test/mocks/vsc/range.ts @@ -0,0 +1,384 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; +// tslint:disable:all +import { vscMockPosition } from './position'; + +export namespace vscMockRange { + /** + * A range in the editor. This interface is suitable for serialization. + */ + export interface IRange { + /** + * Line number on which the range starts (starts at 1). + */ + readonly startLineNumber: number; + /** + * Column on which the range starts in line `startLineNumber` (starts at 1). + */ + readonly startColumn: number; + /** + * Line number on which the range ends. + */ + readonly endLineNumber: number; + /** + * Column on which the range ends in line `endLineNumber`. + */ + readonly endColumn: number; + } + + /** + * A range in the editor. (startLineNumber,startColumn) is <= (endLineNumber,endColumn) + */ + export class Range { + + /** + * Line number on which the range starts (starts at 1). + */ + public readonly startLineNumber: number; + /** + * Column on which the range starts in line `startLineNumber` (starts at 1). + */ + public readonly startColumn: number; + /** + * Line number on which the range ends. + */ + public readonly endLineNumber: number; + /** + * Column on which the range ends in line `endLineNumber`. + */ + public readonly endColumn: number; + + constructor(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number) { + if ((startLineNumber > endLineNumber) || (startLineNumber === endLineNumber && startColumn > endColumn)) { + this.startLineNumber = endLineNumber; + this.startColumn = endColumn; + this.endLineNumber = startLineNumber; + this.endColumn = startColumn; + } else { + this.startLineNumber = startLineNumber; + this.startColumn = startColumn; + this.endLineNumber = endLineNumber; + this.endColumn = endColumn; + } + } + + /** + * Test if this range is empty. + */ + public isEmpty(): boolean { + return Range.isEmpty(this); + } + + /** + * Test if `range` is empty. + */ + public static isEmpty(range: IRange): boolean { + return (range.startLineNumber === range.endLineNumber && range.startColumn === range.endColumn); + } + + /** + * Test if position is in this range. If the position is at the edges, will return true. + */ + public containsPosition(position: vscMockPosition.IPosition): boolean { + return Range.containsPosition(this, position); + } + + /** + * Test if `position` is in `range`. If the position is at the edges, will return true. + */ + public static containsPosition(range: IRange, position: vscMockPosition.IPosition): boolean { + if (position.lineNumber < range.startLineNumber || position.lineNumber > range.endLineNumber) { + return false; + } + if (position.lineNumber === range.startLineNumber && position.column < range.startColumn) { + return false; + } + if (position.lineNumber === range.endLineNumber && position.column > range.endColumn) { + return false; + } + return true; + } + + /** + * Test if range is in this range. If the range is equal to this range, will return true. + */ + public containsRange(range: IRange): boolean { + return Range.containsRange(this, range); + } + + /** + * Test if `otherRange` is in `range`. If the ranges are equal, will return true. + */ + public static containsRange(range: IRange, otherRange: IRange): boolean { + if (otherRange.startLineNumber < range.startLineNumber || otherRange.endLineNumber < range.startLineNumber) { + return false; + } + if (otherRange.startLineNumber > range.endLineNumber || otherRange.endLineNumber > range.endLineNumber) { + return false; + } + if (otherRange.startLineNumber === range.startLineNumber && otherRange.startColumn < range.startColumn) { + return false; + } + if (otherRange.endLineNumber === range.endLineNumber && otherRange.endColumn > range.endColumn) { + return false; + } + return true; + } + + /** + * A reunion of the two ranges. + * The smallest position will be used as the start point, and the largest one as the end point. + */ + public plusRange(range: IRange): Range { + return Range.plusRange(this, range); + } + + /** + * A reunion of the two ranges. + * The smallest position will be used as the start point, and the largest one as the end point. + */ + public static plusRange(a: IRange, b: IRange): Range { + var startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number; + if (b.startLineNumber < a.startLineNumber) { + startLineNumber = b.startLineNumber; + startColumn = b.startColumn; + } else if (b.startLineNumber === a.startLineNumber) { + startLineNumber = b.startLineNumber; + startColumn = Math.min(b.startColumn, a.startColumn); + } else { + startLineNumber = a.startLineNumber; + startColumn = a.startColumn; + } + + if (b.endLineNumber > a.endLineNumber) { + endLineNumber = b.endLineNumber; + endColumn = b.endColumn; + } else if (b.endLineNumber === a.endLineNumber) { + endLineNumber = b.endLineNumber; + endColumn = Math.max(b.endColumn, a.endColumn); + } else { + endLineNumber = a.endLineNumber; + endColumn = a.endColumn; + } + + return new Range(startLineNumber, startColumn, endLineNumber, endColumn); + } + + /** + * A intersection of the two ranges. + */ + public intersectRanges(range: IRange): Range { + return Range.intersectRanges(this, range); + } + + /** + * A intersection of the two ranges. + */ + public static intersectRanges(a: IRange, b: IRange): Range { + var resultStartLineNumber = a.startLineNumber, + resultStartColumn = a.startColumn, + resultEndLineNumber = a.endLineNumber, + resultEndColumn = a.endColumn, + otherStartLineNumber = b.startLineNumber, + otherStartColumn = b.startColumn, + otherEndLineNumber = b.endLineNumber, + otherEndColumn = b.endColumn; + + if (resultStartLineNumber < otherStartLineNumber) { + resultStartLineNumber = otherStartLineNumber; + resultStartColumn = otherStartColumn; + } else if (resultStartLineNumber === otherStartLineNumber) { + resultStartColumn = Math.max(resultStartColumn, otherStartColumn); + } + + if (resultEndLineNumber > otherEndLineNumber) { + resultEndLineNumber = otherEndLineNumber; + resultEndColumn = otherEndColumn; + } else if (resultEndLineNumber === otherEndLineNumber) { + resultEndColumn = Math.min(resultEndColumn, otherEndColumn); + } + + // Check if selection is now empty + if (resultStartLineNumber > resultEndLineNumber) { + return null; + } + if (resultStartLineNumber === resultEndLineNumber && resultStartColumn > resultEndColumn) { + return null; + } + return new Range(resultStartLineNumber, resultStartColumn, resultEndLineNumber, resultEndColumn); + } + + /** + * Test if this range equals other. + */ + public equalsRange(other: IRange): boolean { + return Range.equalsRange(this, other); + } + + /** + * Test if range `a` equals `b`. + */ + public static equalsRange(a: IRange, b: IRange): boolean { + return ( + !!a && + !!b && + a.startLineNumber === b.startLineNumber && + a.startColumn === b.startColumn && + a.endLineNumber === b.endLineNumber && + a.endColumn === b.endColumn + ); + } + + /** + * Return the end position (which will be after or equal to the start position) + */ + public getEndPosition(): vscMockPosition.Position { + return new vscMockPosition.Position(this.endLineNumber, this.endColumn); + } + + /** + * Return the start position (which will be before or equal to the end position) + */ + public getStartPosition(): vscMockPosition.Position { + return new vscMockPosition.Position(this.startLineNumber, this.startColumn); + } + + /** + * Transform to a user presentable string representation. + */ + public toString(): string { + return '[' + this.startLineNumber + ',' + this.startColumn + ' -> ' + this.endLineNumber + ',' + this.endColumn + ']'; + } + + /** + * Create a new range using this range's start position, and using endLineNumber and endColumn as the end position. + */ + public setEndPosition(endLineNumber: number, endColumn: number): Range { + return new Range(this.startLineNumber, this.startColumn, endLineNumber, endColumn); + } + + /** + * Create a new range using this range's end position, and using startLineNumber and startColumn as the start position. + */ + public setStartPosition(startLineNumber: number, startColumn: number): Range { + return new Range(startLineNumber, startColumn, this.endLineNumber, this.endColumn); + } + + /** + * Create a new empty range using this range's start position. + */ + public collapseToStart(): Range { + return Range.collapseToStart(this); + } + + /** + * Create a new empty range using this range's start position. + */ + public static collapseToStart(range: IRange): Range { + return new Range(range.startLineNumber, range.startColumn, range.startLineNumber, range.startColumn); + } + + // --- + + public static fromPositions(start: vscMockPosition.IPosition, end: vscMockPosition.IPosition = start): Range { + return new Range(start.lineNumber, start.column, end.lineNumber, end.column); + } + + /** + * Create a `Range` from an `IRange`. + */ + public static lift(range: IRange): Range { + if (!range) { + return null; + } + return new Range(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn); + } + + /** + * Test if `obj` is an `IRange`. + */ + public static isIRange(obj: any): obj is IRange { + return ( + obj + && (typeof obj.startLineNumber === 'number') + && (typeof obj.startColumn === 'number') + && (typeof obj.endLineNumber === 'number') + && (typeof obj.endColumn === 'number') + ); + } + + /** + * Test if the two ranges are touching in any way. + */ + public static areIntersectingOrTouching(a: IRange, b: IRange): boolean { + // Check if `a` is before `b` + if (a.endLineNumber < b.startLineNumber || (a.endLineNumber === b.startLineNumber && a.endColumn < b.startColumn)) { + return false; + } + + // Check if `b` is before `a` + if (b.endLineNumber < a.startLineNumber || (b.endLineNumber === a.startLineNumber && b.endColumn < a.startColumn)) { + return false; + } + + // These ranges must intersect + return true; + } + + /** + * A function that compares ranges, useful for sorting ranges + * It will first compare ranges on the startPosition and then on the endPosition + */ + public static compareRangesUsingStarts(a: IRange, b: IRange): number { + let aStartLineNumber = a.startLineNumber | 0; + let bStartLineNumber = b.startLineNumber | 0; + + if (aStartLineNumber === bStartLineNumber) { + let aStartColumn = a.startColumn | 0; + let bStartColumn = b.startColumn | 0; + + if (aStartColumn === bStartColumn) { + let aEndLineNumber = a.endLineNumber | 0; + let bEndLineNumber = b.endLineNumber | 0; + + if (aEndLineNumber === bEndLineNumber) { + let aEndColumn = a.endColumn | 0; + let bEndColumn = b.endColumn | 0; + return aEndColumn - bEndColumn; + } + return aEndLineNumber - bEndLineNumber; + } + return aStartColumn - bStartColumn; + } + return aStartLineNumber - bStartLineNumber; + } + + /** + * A function that compares ranges, useful for sorting ranges + * It will first compare ranges on the endPosition and then on the startPosition + */ + public static compareRangesUsingEnds(a: IRange, b: IRange): number { + if (a.endLineNumber === b.endLineNumber) { + if (a.endColumn === b.endColumn) { + if (a.startLineNumber === b.startLineNumber) { + return a.startColumn - b.startColumn; + } + return a.startLineNumber - b.startLineNumber; + } + return a.endColumn - b.endColumn; + } + return a.endLineNumber - b.endLineNumber; + } + + /** + * Test if the range spans multiple lines. + */ + public static spansMultipleLines(range: IRange): boolean { + return range.endLineNumber > range.startLineNumber; + } + } +} diff --git a/src/test/mocks/vsc/selection.ts b/src/test/mocks/vsc/selection.ts new file mode 100644 index 000000000000..5c750fed8847 --- /dev/null +++ b/src/test/mocks/vsc/selection.ts @@ -0,0 +1,211 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; +// tslint:disable:all +import { vscMockPosition } from './position'; +import { vscMockRange } from './range'; +export namespace vscMockSelection { + /** + * A selection in the editor. + * The selection is a range that has an orientation. + */ + export interface ISelection { + /** + * The line number on which the selection has started. + */ + readonly selectionStartLineNumber: number; + /** + * The column on `selectionStartLineNumber` where the selection has started. + */ + readonly selectionStartColumn: number; + /** + * The line number on which the selection has ended. + */ + readonly positionLineNumber: number; + /** + * The column on `positionLineNumber` where the selection has ended. + */ + readonly positionColumn: number; + } + + /** + * The direction of a selection. + */ + export enum SelectionDirection { + /** + * The selection starts above where it ends. + */ + LTR, + /** + * The selection starts below where it ends. + */ + RTL + } + + /** + * A selection in the editor. + * The selection is a range that has an orientation. + */ + export class Selection extends vscMockRange.Range { + /** + * The line number on which the selection has started. + */ + public readonly selectionStartLineNumber: number; + /** + * The column on `selectionStartLineNumber` where the selection has started. + */ + public readonly selectionStartColumn: number; + /** + * The line number on which the selection has ended. + */ + public readonly positionLineNumber: number; + /** + * The column on `positionLineNumber` where the selection has ended. + */ + public readonly positionColumn: number; + + constructor(selectionStartLineNumber: number, selectionStartColumn: number, positionLineNumber: number, positionColumn: number) { + super(selectionStartLineNumber, selectionStartColumn, positionLineNumber, positionColumn); + this.selectionStartLineNumber = selectionStartLineNumber; + this.selectionStartColumn = selectionStartColumn; + this.positionLineNumber = positionLineNumber; + this.positionColumn = positionColumn; + } + + /** + * Clone this selection. + */ + public clone(): Selection { + return new Selection(this.selectionStartLineNumber, this.selectionStartColumn, this.positionLineNumber, this.positionColumn); + } + + /** + * Transform to a human-readable representation. + */ + public toString(): string { + return '[' + this.selectionStartLineNumber + ',' + this.selectionStartColumn + ' -> ' + this.positionLineNumber + ',' + this.positionColumn + ']'; + } + + /** + * Test if equals other selection. + */ + public equalsSelection(other: ISelection): boolean { + return ( + Selection.selectionsEqual(this, other) + ); + } + + /** + * Test if the two selections are equal. + */ + public static selectionsEqual(a: ISelection, b: ISelection): boolean { + return ( + a.selectionStartLineNumber === b.selectionStartLineNumber && + a.selectionStartColumn === b.selectionStartColumn && + a.positionLineNumber === b.positionLineNumber && + a.positionColumn === b.positionColumn + ); + } + + /** + * Get directions (LTR or RTL). + */ + public getDirection(): SelectionDirection { + if (this.selectionStartLineNumber === this.startLineNumber && this.selectionStartColumn === this.startColumn) { + return SelectionDirection.LTR; + } + return SelectionDirection.RTL; + } + + /** + * Create a new selection with a different `positionLineNumber` and `positionColumn`. + */ + public setEndPosition(endLineNumber: number, endColumn: number): Selection { + if (this.getDirection() === SelectionDirection.LTR) { + return new Selection(this.startLineNumber, this.startColumn, endLineNumber, endColumn); + } + return new Selection(endLineNumber, endColumn, this.startLineNumber, this.startColumn); + } + + /** + * Get the position at `positionLineNumber` and `positionColumn`. + */ + public getPosition(): vscMockPosition.Position { + return new vscMockPosition.Position(this.positionLineNumber, this.positionColumn); + } + + /** + * Create a new selection with a different `selectionStartLineNumber` and `selectionStartColumn`. + */ + public setStartPosition(startLineNumber: number, startColumn: number): Selection { + if (this.getDirection() === SelectionDirection.LTR) { + return new Selection(startLineNumber, startColumn, this.endLineNumber, this.endColumn); + } + return new Selection(this.endLineNumber, this.endColumn, startLineNumber, startColumn); + } + + // ---- + + /** + * Create a `Selection` from one or two positions + */ + public static fromPositions(start: vscMockPosition.IPosition, end: vscMockPosition.IPosition = start): Selection { + return new Selection(start.lineNumber, start.column, end.lineNumber, end.column); + } + + /** + * Create a `Selection` from an `ISelection`. + */ + public static liftSelection(sel: ISelection): Selection { + return new Selection(sel.selectionStartLineNumber, sel.selectionStartColumn, sel.positionLineNumber, sel.positionColumn); + } + + /** + * `a` equals `b`. + */ + public static selectionsArrEqual(a: ISelection[], b: ISelection[]): boolean { + if (a && !b || !a && b) { + return false; + } + if (!a && !b) { + return true; + } + if (a.length !== b.length) { + return false; + } + for (var i = 0, len = a.length; i < len; i++) { + if (!this.selectionsEqual(a[i], b[i])) { + return false; + } + } + return true; + } + + /** + * Test if `obj` is an `ISelection`. + */ + public static isISelection(obj: any): obj is ISelection { + return ( + obj + && (typeof obj.selectionStartLineNumber === 'number') + && (typeof obj.selectionStartColumn === 'number') + && (typeof obj.positionLineNumber === 'number') + && (typeof obj.positionColumn === 'number') + ); + } + + /** + * Create with a direction. + */ + public static createWithDirection(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, direction: SelectionDirection): Selection { + + if (direction === SelectionDirection.LTR) { + return new Selection(startLineNumber, startColumn, endLineNumber, endColumn); + } + + return new Selection(endLineNumber, endColumn, startLineNumber, startColumn); + } + } +} diff --git a/src/test/mocks/vsc/strings.ts b/src/test/mocks/vsc/strings.ts new file mode 100644 index 000000000000..a1feac2d2668 --- /dev/null +++ b/src/test/mocks/vsc/strings.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +// tslint:disable:all + +export namespace vscMockStrings { + /** + * Determines if haystack starts with needle. + */ + export function startsWith(haystack: string, needle: string): boolean { + if (haystack.length < needle.length) { + return false; + } + + for (let i = 0; i < needle.length; i++) { + if (haystack[i] !== needle[i]) { + return false; + } + } + + return true; + } + + /** + * Determines if haystack ends with needle. + */ + export function endsWith(haystack: string, needle: string): boolean { + let diff = haystack.length - needle.length; + if (diff > 0) { + return haystack.indexOf(needle, diff) === diff; + } else if (diff === 0) { + return haystack === needle; + } else { + return false; + } + } +} diff --git a/src/test/mocks/vsc/telemetryReporter.ts b/src/test/mocks/vsc/telemetryReporter.ts new file mode 100644 index 000000000000..9b5ef94178cf --- /dev/null +++ b/src/test/mocks/vsc/telemetryReporter.ts @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:all +import * as telemetry from 'vscode-extension-telemetry'; +export class vscMockTelemetryReporter implements telemetry.default { + constructor() { + // + } + + public sendTelemetryEvent(): void { + // + } +} diff --git a/src/test/mocks/vsc/uri.ts b/src/test/mocks/vsc/uri.ts new file mode 100644 index 000000000000..e91531924823 --- /dev/null +++ b/src/test/mocks/vsc/uri.ts @@ -0,0 +1,474 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +export namespace vscUri { + const platform = { + isWindows: /^win/.test(process.platform) + }; + + // tslint:disable:all + + function _encode(ch: string): string { + return '%' + ch.charCodeAt(0).toString(16).toUpperCase(); + } + + // see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent + function encodeURIComponent2(str: string): string { + return encodeURIComponent(str).replace(/[!'()*]/g, _encode); + } + + function encodeNoop(str: string): string { + return str.replace(/[#?]/, _encode); + } + + + const _schemePattern = /^\w[\w\d+.-]*$/; + const _singleSlashStart = /^\//; + const _doubleSlashStart = /^\/\//; + + function _validateUri(ret: URI): void { + // scheme, https://tools.ietf.org/html/rfc3986#section-3.1 + // ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) + if (ret.scheme && !_schemePattern.test(ret.scheme)) { + throw new Error('[UriError]: Scheme contains illegal characters.'); + } + + // path, http://tools.ietf.org/html/rfc3986#section-3.3 + // If a URI contains an authority component, then the path component + // must either be empty or begin with a slash ("/") character. If a URI + // does not contain an authority component, then the path cannot begin + // with two slash characters ("//"). + if (ret.path) { + if (ret.authority) { + if (!_singleSlashStart.test(ret.path)) { + throw new Error('[UriError]: If a URI contains an authority component, then the path component must either be empty or begin with a slash ("/") character'); + } + } else { + if (_doubleSlashStart.test(ret.path)) { + throw new Error('[UriError]: If a URI does not contain an authority component, then the path cannot begin with two slash characters ("//")'); + } + } + } + } + + const _empty = ''; + const _slash = '/'; + const _regexp = /^(([^:/?#]+?):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/; + const _driveLetterPath = /^\/[a-zA-Z]:/; + const _upperCaseDrive = /^(\/)?([A-Z]:)/; + const _driveLetter = /^[a-zA-Z]:/; + + /** + * Uniform Resource Identifier (URI) http://tools.ietf.org/html/rfc3986. + * This class is a simple parser which creates the basic component paths + * (http://tools.ietf.org/html/rfc3986#section-3) with minimal validation + * and encoding. + * + * foo://example.com:8042/over/there?name=ferret#nose + * \_/ \______________/\_________/ \_________/ \__/ + * | | | | | + * scheme authority path query fragment + * | _____________________|__ + * / \ / \ + * urn:example:animal:ferret:nose + * + * + */ + export class URI implements UriComponents { + + static isUri(thing: any): thing is URI { + if (thing instanceof URI) { + return true; + } + if (!thing) { + return false; + } + return typeof (thing).authority === 'string' + && typeof (thing).fragment === 'string' + && typeof (thing).path === 'string' + && typeof (thing).query === 'string' + && typeof (thing).scheme === 'string'; + } + + /** + * scheme is the 'http' part of 'http://www.msft.com/some/path?query#fragment'. + * The part before the first colon. + */ + readonly scheme: string; + + /** + * authority is the 'www.msft.com' part of 'http://www.msft.com/some/path?query#fragment'. + * The part between the first double slashes and the next slash. + */ + readonly authority: string; + + /** + * path is the '/some/path' part of 'http://www.msft.com/some/path?query#fragment'. + */ + readonly path: string; + + /** + * query is the 'query' part of 'http://www.msft.com/some/path?query#fragment'. + */ + readonly query: string; + + /** + * fragment is the 'fragment' part of 'http://www.msft.com/some/path?query#fragment'. + */ + readonly fragment: string; + + /** + * @internal + */ + protected constructor(scheme: string, authority: string, path: string, query: string, fragment: string); + + /** + * @internal + */ + protected constructor(components: UriComponents); + + /** + * @internal + */ + protected constructor(schemeOrData: string | UriComponents, authority?: string, path?: string, query?: string, fragment?: string) { + + if (typeof schemeOrData === 'object') { + this.scheme = schemeOrData.scheme || _empty; + this.authority = schemeOrData.authority || _empty; + this.path = schemeOrData.path || _empty; + this.query = schemeOrData.query || _empty; + this.fragment = schemeOrData.fragment || _empty; + // no validation because it's this URI + // that creates uri components. + // _validateUri(this); + } else { + this.scheme = schemeOrData || _empty; + this.authority = authority || _empty; + this.path = path || _empty; + this.query = query || _empty; + this.fragment = fragment || _empty; + _validateUri(this); + } + } + + // ---- filesystem path ----------------------- + + /** + * Returns a string representing the corresponding file system path of this URI. + * Will handle UNC paths and normalize windows drive letters to lower-case. Also + * uses the platform specific path separator. Will *not* validate the path for + * invalid characters and semantics. Will *not* look at the scheme of this URI. + */ + get fsPath(): string { + return _makeFsPath(this); + } + + // ---- modify to new ------------------------- + + public with(change: { scheme?: string; authority?: string; path?: string; query?: string; fragment?: string }): URI { + + if (!change) { + return this; + } + + let { scheme, authority, path, query, fragment } = change; + if (scheme === void 0) { + scheme = this.scheme; + } else if (scheme === null) { + scheme = _empty; + } + if (authority === void 0) { + authority = this.authority; + } else if (authority === null) { + authority = _empty; + } + if (path === void 0) { + path = this.path; + } else if (path === null) { + path = _empty; + } + if (query === void 0) { + query = this.query; + } else if (query === null) { + query = _empty; + } + if (fragment === void 0) { + fragment = this.fragment; + } else if (fragment === null) { + fragment = _empty; + } + + if (scheme === this.scheme + && authority === this.authority + && path === this.path + && query === this.query + && fragment === this.fragment) { + + return this; + } + + return new _URI(scheme, authority, path, query, fragment); + } + + // ---- parse & validate ------------------------ + + public static parse(value: string): URI { + const match = _regexp.exec(value); + if (!match) { + return new _URI(_empty, _empty, _empty, _empty, _empty); + } + return new _URI( + match[2] || _empty, + decodeURIComponent(match[4] || _empty), + decodeURIComponent(match[5] || _empty), + decodeURIComponent(match[7] || _empty), + decodeURIComponent(match[9] || _empty), + ); + } + + public static file(path: string): URI { + + let authority = _empty; + + // normalize to fwd-slashes on windows, + // on other systems bwd-slashes are valid + // filename character, eg /f\oo/ba\r.txt + if (platform.isWindows) { + path = path.replace(/\\/g, _slash); + } + + // check for authority as used in UNC shares + // or use the path as given + if (path[0] === _slash && path[1] === _slash) { + let idx = path.indexOf(_slash, 2); + if (idx === -1) { + authority = path.substring(2); + path = _slash; + } else { + authority = path.substring(2, idx); + path = path.substring(idx) || _slash; + } + } + + // Ensure that path starts with a slash + // or that it is at least a slash + if (_driveLetter.test(path)) { + path = _slash + path; + + } else if (path[0] !== _slash) { + // tricky -> makes invalid paths + // but otherwise we have to stop + // allowing relative paths... + path = _slash + path; + } + + return new _URI('file', authority, path, _empty, _empty); + } + + public static from(components: { scheme?: string; authority?: string; path?: string; query?: string; fragment?: string }): URI { + return new _URI( + components.scheme, + components.authority, + components.path, + components.query, + components.fragment, + ); + } + + // ---- printing/externalize --------------------------- + + /** + * + * @param skipEncoding Do not encode the result, default is `false` + */ + public toString(skipEncoding: boolean = false): string { + return _asFormatted(this, skipEncoding); + } + + public toJSON(): object { + const res = { + $mid: 1, + fsPath: this.fsPath, + external: this.toString(), + }; + + if (this.path) { + res.path = this.path; + } + + if (this.scheme) { + res.scheme = this.scheme; + } + + if (this.authority) { + res.authority = this.authority; + } + + if (this.query) { + res.query = this.query; + } + + if (this.fragment) { + res.fragment = this.fragment; + } + + return res; + } + + static revive(data: UriComponents | any): URI { + if (!data) { + return data; + } else if (data instanceof URI) { + return data; + } else { + let result = new _URI(data); + result._fsPath = (data).fsPath; + result._formatted = (data).external; + return result; + } + } + } + + export interface UriComponents { + scheme: string; + authority: string; + path: string; + query: string; + fragment: string; + } + + interface UriState extends UriComponents { + $mid: number; + fsPath: string; + external: string; + } + + + // tslint:disable-next-line:class-name + class _URI extends URI { + + _formatted: string = null; + _fsPath: string = null; + + get fsPath(): string { + if (!this._fsPath) { + this._fsPath = _makeFsPath(this); + } + return this._fsPath; + } + + public toString(skipEncoding: boolean = false): string { + if (!skipEncoding) { + if (!this._formatted) { + this._formatted = _asFormatted(this, false); + } + return this._formatted; + } else { + // we don't cache that + return _asFormatted(this, true); + } + } + } + + + /** + * Compute `fsPath` for the given uri + * @param uri + */ + function _makeFsPath(uri: URI): string { + + let value: string; + if (uri.authority && uri.path && uri.scheme === 'file') { + // unc path: file://shares/c$/far/boo + value = `//${uri.authority}${uri.path}`; + } else if (_driveLetterPath.test(uri.path)) { + // windows drive letter: file:///c:/far/boo + value = uri.path[1].toLowerCase() + uri.path.substr(2); + } else { + // other path + value = uri.path; + } + if (platform.isWindows) { + value = value.replace(/\//g, '\\'); + } + return value; + } + + /** + * Create the external version of a uri + */ + function _asFormatted(uri: URI, skipEncoding: boolean): string { + + const encoder = !skipEncoding + ? encodeURIComponent2 + : encodeNoop; + + const parts: string[] = []; + + let { scheme, authority, path, query, fragment } = uri; + if (scheme) { + parts.push(scheme, ':'); + } + if (authority || scheme === 'file') { + parts.push('//'); + } + if (authority) { + let idx = authority.indexOf('@'); + if (idx !== -1) { + const userinfo = authority.substr(0, idx); + authority = authority.substr(idx + 1); + idx = userinfo.indexOf(':'); + if (idx === -1) { + parts.push(encoder(userinfo)); + } else { + parts.push(encoder(userinfo.substr(0, idx)), ':', encoder(userinfo.substr(idx + 1))); + } + parts.push('@'); + } + authority = authority.toLowerCase(); + idx = authority.indexOf(':'); + if (idx === -1) { + parts.push(encoder(authority)); + } else { + parts.push(encoder(authority.substr(0, idx)), authority.substr(idx)); + } + } + if (path) { + // lower-case windows drive letters in /C:/fff or C:/fff + const m = _upperCaseDrive.exec(path); + if (m) { + if (m[1]) { + path = '/' + m[2].toLowerCase() + path.substr(3); // "/c:".length === 3 + } else { + path = m[2].toLowerCase() + path.substr(2); // // "c:".length === 2 + } + } + + // encode every segement but not slashes + // make sure that # and ? are always encoded + // when occurring in paths - otherwise the result + // cannot be parsed back again + let lastIdx = 0; + while (true) { + let idx = path.indexOf(_slash, lastIdx); + if (idx === -1) { + parts.push(encoder(path.substring(lastIdx))); + break; + } + parts.push(encoder(path.substring(lastIdx, idx)), _slash); + lastIdx = idx + 1; + } + } + if (query) { + parts.push('?', encoder(query)); + } + if (fragment) { + parts.push('#', encoder(fragment)); + } + + return parts.join(_empty); + } +} diff --git a/src/test/providers/completionSource.test.ts b/src/test/providers/completionSource.unit.test.ts similarity index 100% rename from src/test/providers/completionSource.test.ts rename to src/test/providers/completionSource.unit.test.ts diff --git a/src/test/providers/repl.test.ts b/src/test/providers/repl.unit.test.ts similarity index 100% rename from src/test/providers/repl.test.ts rename to src/test/providers/repl.unit.test.ts diff --git a/src/test/providers/symbolProvider.test.ts b/src/test/providers/symbolProvider.unit.test.ts similarity index 100% rename from src/test/providers/symbolProvider.test.ts rename to src/test/providers/symbolProvider.unit.test.ts diff --git a/src/test/providers/terminal.test.ts b/src/test/providers/terminal.unit.test.ts similarity index 100% rename from src/test/providers/terminal.test.ts rename to src/test/providers/terminal.unit.test.ts diff --git a/src/test/signature/signature.jedi.test.ts b/src/test/signature/signature.jedi.test.ts index 0d3a0b5ed90b..1c1d27f57a15 100644 --- a/src/test/signature/signature.jedi.test.ts +++ b/src/test/signature/signature.jedi.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import * as path from 'path'; import * as vscode from 'vscode'; import { rootWorkspaceUri } from '../common'; -import { IS_ANALYSIS_ENGINE_TEST } from '../constants'; +import { IsAnalysisEngineTest } from '../constants'; import { closeActiveWindows, initialize, initializeTest } from '../initialize'; import { UnitTestIocContainer } from '../unittests/serviceRegistry'; @@ -25,7 +25,7 @@ suite('Signatures (Jedi)', () => { let isPython2: boolean; let ioc: UnitTestIocContainer; suiteSetup(async function () { - if (IS_ANALYSIS_ENGINE_TEST) { + if (IsAnalysisEngineTest()) { // tslint:disable-next-line:no-invalid-this this.skip(); } diff --git a/src/test/signature/signature.ptvs.test.ts b/src/test/signature/signature.ptvs.test.ts index 823433b50093..8cc9e97ed87e 100644 --- a/src/test/signature/signature.ptvs.test.ts +++ b/src/test/signature/signature.ptvs.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import * as path from 'path'; import * as vscode from 'vscode'; import { rootWorkspaceUri } from '../common'; -import { IS_ANALYSIS_ENGINE_TEST } from '../constants'; +import { IsAnalysisEngineTest } from '../constants'; import { closeActiveWindows, initialize, initializeTest } from '../initialize'; import { UnitTestIocContainer } from '../unittests/serviceRegistry'; @@ -25,7 +25,7 @@ suite('Signatures (Analysis Engine)', () => { let isPython2: boolean; let ioc: UnitTestIocContainer; suiteSetup(async function () { - if (!IS_ANALYSIS_ENGINE_TEST) { + if (!IsAnalysisEngineTest()) { // tslint:disable-next-line:no-invalid-this this.skip(); } diff --git a/src/test/terminals/codeExecution/codeExecutionManager.test.ts b/src/test/terminals/codeExecution/codeExecutionManager.unit.test.ts similarity index 100% rename from src/test/terminals/codeExecution/codeExecutionManager.test.ts rename to src/test/terminals/codeExecution/codeExecutionManager.unit.test.ts diff --git a/src/test/terminals/codeExecution/djangoShellCodeExect.test.ts b/src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts similarity index 100% rename from src/test/terminals/codeExecution/djangoShellCodeExect.test.ts rename to src/test/terminals/codeExecution/djangoShellCodeExect.unit.test.ts diff --git a/src/test/terminals/codeExecution/helper.test.ts b/src/test/terminals/codeExecution/helper.unit.test.ts similarity index 100% rename from src/test/terminals/codeExecution/helper.test.ts rename to src/test/terminals/codeExecution/helper.unit.test.ts diff --git a/src/test/vscode-mock.ts b/src/test/vscode-mock.ts index 2c3524f63311..eb401d8a4d54 100644 --- a/src/test/vscode-mock.ts +++ b/src/test/vscode-mock.ts @@ -7,6 +7,8 @@ import * as TypeMoq from 'typemoq'; import * as vscode from 'vscode'; +import * as vscodeMocks from './mocks/vsc'; +import { vscMockTelemetryReporter } from './mocks/vsc/telemetryReporter'; const Module = require('module'); type VSCode = typeof vscode; @@ -28,62 +30,48 @@ export function initialize() { generateMock('languages'); generateMock('env'); generateMock('debug'); - generateMock('extensions'); generateMock('scm'); + // When upgrading to npm 9-10, this might have to change, as we could have explicit imports (named imports). Module._load = function (request, parent) { if (request === 'vscode') { return mockedVSCode; } + if (request === 'vscode-extension-telemetry') { + return { default: vscMockTelemetryReporter }; + } return originalLoad.apply(this, arguments); }; } -/** - * Gets the mocked VS Code namespaces/classes. - * For VS Code namespaces, always return pre-mocked objects, else create a new mock object. - * @export - * @template K - * @param {K} name - * @returns {TypeMoq.IMock} - */ -export function mock(name: K): TypeMoq.IMock { - if (mockedVSCodeNamespaces[name] === undefined) { - return TypeMoq.Mock.ofType(); - } - // When re-using, always reset (other tests could have used this same instance). - const mockObj = mockedVSCodeNamespaces[name]!; - mockObj.reset(); - return mockObj as any as TypeMoq.IMock; -} - -// This is one of the very few classes that we need in our unit tests. -// It is constructed in a number of places, and this is required for verification. -// Using mocked objects for verfications does not work in typemoq. -export class Uri implements vscode.Uri { - private constructor(public readonly scheme: string, public readonly authority: string, - public readonly path: string, public readonly query: string, - public readonly fragment: string, public readonly fsPath) { - - } - public static file(path: string): Uri { - return new Uri('file', '', path, '', '', path); - } - public static parse(value: string): Uri { - return new Uri('http', '', value, '', '', value); - } - public with(change: { scheme?: string; authority?: string; path?: string; query?: string; fragment?: string }): vscode.Uri { - throw new Error('Not implemented'); - } - public toString(skipEncoding?: boolean): string { - return this.fsPath; - } - public toJSON(): any { - return this.fsPath; - } -} +mockedVSCode.Disposable = vscodeMocks.vscMock.Disposable as any; +mockedVSCode.EventEmitter = vscodeMocks.vscMock.EventEmitter; +mockedVSCode.CancellationTokenSource = vscodeMocks.vscMock.CancellationTokenSource; +mockedVSCode.CompletionItemKind = vscodeMocks.vscMock.CompletionItemKind; +mockedVSCode.SymbolKind = vscodeMocks.vscMock.SymbolKind; +mockedVSCode.Uri = vscodeMocks.vscMock.Uri as any; +mockedVSCode.Range = vscodeMocks.vscMockExtHostedTypes.Range; +mockedVSCode.Position = vscodeMocks.vscMockExtHostedTypes.Position; +mockedVSCode.Selection = vscodeMocks.vscMockExtHostedTypes.Selection; +mockedVSCode.Location = vscodeMocks.vscMockExtHostedTypes.Location; +mockedVSCode.SymbolInformation = vscodeMocks.vscMockExtHostedTypes.SymbolInformation; +mockedVSCode.CompletionItem = vscodeMocks.vscMockExtHostedTypes.CompletionItem; +mockedVSCode.CompletionItemKind = vscodeMocks.vscMockExtHostedTypes.CompletionItemKind; +mockedVSCode.CodeLens = vscodeMocks.vscMockExtHostedTypes.CodeLens; +mockedVSCode.DiagnosticSeverity = vscodeMocks.vscMockExtHostedTypes.DiagnosticSeverity; +mockedVSCode.SnippetString = vscodeMocks.vscMockExtHostedTypes.SnippetString; +mockedVSCode.EventEmitter = vscodeMocks.vscMock.EventEmitter; +mockedVSCode.ConfigurationTarget = vscodeMocks.vscMockExtHostedTypes.ConfigurationTarget; +mockedVSCode.StatusBarAlignment = vscodeMocks.vscMockExtHostedTypes.StatusBarAlignment; -mockedVSCode.Uri = Uri as any; -// tslint:disable-next-line:no-function-expression -mockedVSCode.EventEmitter = function () { return TypeMoq.Mock.ofType>(); } as any; -mockedVSCode.StatusBarAlignment = TypeMoq.Mock.ofType().object as any; +// This API is used in src/client/telemetry/telemetry.ts +const extensions = TypeMoq.Mock.ofType(); +extensions.setup(e => e.all).returns(() => []); +const extension = TypeMoq.Mock.ofType>(); +const packageJson = TypeMoq.Mock.ofType(); +const contributes = TypeMoq.Mock.ofType(); +extension.setup(e => e.packageJSON).returns(() => packageJson.object); +packageJson.setup(p => p.contributes).returns(() => contributes.object); +contributes.setup(p => p.debuggers).returns(() => [{ aiKey: '' }]); +extensions.setup(e => e.getExtension(TypeMoq.It.isAny())).returns(() => extension.object); +mockedVSCode.extensions = extensions.object; From fdaabb9e9a5f77e78ae29ddd3ae5f0a0064b9c9a Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 12 Jun 2018 12:20:46 -0700 Subject: [PATCH 334/433] Enable multi-threaded tests for experimental debugger (#1935) Fixes #1250 --- news/3 Code Health/1250.md | 1 + src/test/debugger/misc.test.ts | 7 +------ 2 files changed, 2 insertions(+), 6 deletions(-) create mode 100644 news/3 Code Health/1250.md diff --git a/news/3 Code Health/1250.md b/news/3 Code Health/1250.md new file mode 100644 index 000000000000..f4eeb42a9892 --- /dev/null +++ b/news/3 Code Health/1250.md @@ -0,0 +1 @@ +Enabled multi-thrreaded debugger tests for the `experimental` debugger. diff --git a/src/test/debugger/misc.test.ts b/src/test/debugger/misc.test.ts index 180cb88e9a26..5e4d10adf2e6 100644 --- a/src/test/debugger/misc.test.ts +++ b/src/test/debugger/misc.test.ts @@ -472,12 +472,7 @@ let testCounter = 0; debugClient.assertStoppedLocation('exception', pauseLocation) ]); }); - test('Test multi-threaded debugging', async function () { - if (debuggerType !== 'python') { - // See GitHub issue #1250 - this.skip(); - return; - } + test('Test multi-threaded debugging', async () => { await Promise.all([ debugClient.configurationSequence(), debugClient.launch(buildLaunchArgs('multiThread.py', false)), From 07d987343e052c31e0871a75b55fa4ef1b0a97d3 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 12 Jun 2018 12:21:16 -0700 Subject: [PATCH 335/433] Change error message displayed when path to a tool is invalid (#1903) * Change error message displayed when path to a tool is invalid * Use string enums * Fix message displayed * Add missing service registrations for tests * Remove duplicate registration and fix imports * Fix unit tests --- news/2 Fixes/1064.md | 1 + .../common/installer/productInstaller.ts | 131 +++++------- src/client/common/installer/productPath.ts | 101 ++++++++++ src/client/common/installer/productService.ts | 34 ++++ .../common/installer/serviceRegistry.ts | 12 +- src/client/common/installer/types.ts | 11 +- src/client/common/types.ts | 8 + src/test/common/installer.test.ts | 12 +- .../installer.invalidPath.unit.test.ts | 86 ++++++++ ...staller.test.ts => installer.unit.test.ts} | 14 +- .../common/installer/moduleInstaller.test.ts | 2 +- .../common/installer/productPath.unit.test.ts | 189 ++++++++++++++++++ src/test/linters/lint.multiroot.test.ts | 16 +- src/test/linters/lint.test.ts | 33 +-- 14 files changed, 545 insertions(+), 105 deletions(-) create mode 100644 news/2 Fixes/1064.md create mode 100644 src/client/common/installer/productPath.ts create mode 100644 src/client/common/installer/productService.ts create mode 100644 src/test/common/installer/installer.invalidPath.unit.test.ts rename src/test/common/installer/{installer.test.ts => installer.unit.test.ts} (91%) create mode 100644 src/test/common/installer/productPath.unit.test.ts diff --git a/news/2 Fixes/1064.md b/news/2 Fixes/1064.md new file mode 100644 index 000000000000..389199f587a6 --- /dev/null +++ b/news/2 Fixes/1064.md @@ -0,0 +1 @@ +Modified to change error message displayed when path to a tool (`linter`, `formatter`, etc) is invalid. diff --git a/src/client/common/installer/productInstaller.ts b/src/client/common/installer/productInstaller.ts index fa8c72aae939..38f8707f78ca 100644 --- a/src/client/common/installer/productInstaller.ts +++ b/src/client/common/installer/productInstaller.ts @@ -1,44 +1,36 @@ +// tslint:disable:max-classes-per-file max-classes-per-file + import { inject, injectable, named } from 'inversify'; import * as os from 'os'; -import * as path from 'path'; import { OutputChannel, Uri } from 'vscode'; import '../../common/extensions'; -import { IFormatterHelper } from '../../formatters/types'; import { IServiceContainer } from '../../ioc/types'; import { ILinterManager } from '../../linters/types'; -import { ITestsHelper } from '../../unittests/common/types'; import { IApplicationShell, IWorkspaceService } from '../application/types'; import { STANDARD_OUTPUT_CHANNEL } from '../constants'; import { IPlatformService } from '../platform/types'; import { IProcessServiceFactory, IPythonExecutionFactory } from '../process/types'; import { ITerminalServiceFactory } from '../terminal/types'; -import { IConfigurationService, IInstaller, ILogger, InstallerResponse, IOutputChannel, ModuleNamePurpose, Product } from '../types'; +import { IConfigurationService, IInstaller, ILogger, InstallerResponse, IOutputChannel, ModuleNamePurpose, Product, ProductType } from '../types'; import { ProductNames } from './productNames'; -import { IInstallationChannelManager } from './types'; +import { IInstallationChannelManager, IProductPathService, IProductService } from './types'; export { Product } from '../types'; const CTagsInsllationScript = os.platform() === 'darwin' ? 'brew install ctags' : 'sudo apt-get install exuberant-ctags'; -enum ProductType { - Linter, - Formatter, - TestFramework, - RefactoringLibrary, - WorkspaceSymbols -} - -// tslint:disable-next-line:max-classes-per-file export abstract class BaseInstaller { private static readonly PromptPromises = new Map>(); - protected appShell: IApplicationShell; - protected configService: IConfigurationService; + protected readonly appShell: IApplicationShell; + protected readonly configService: IConfigurationService; private readonly workspaceService: IWorkspaceService; + private readonly productService: IProductService; constructor(protected serviceContainer: IServiceContainer, protected outputChannel: OutputChannel) { this.appShell = serviceContainer.get(IApplicationShell); this.configService = serviceContainer.get(IConfigurationService); this.workspaceService = serviceContainer.get(IWorkspaceService); + this.productService = serviceContainer.get(IProductService); } public promptToInstall(product: Product, resource?: Uri): Promise { @@ -82,16 +74,10 @@ export abstract class BaseInstaller { if (product === Product.unittest) { return true; } - let moduleName: string | undefined; - try { - moduleName = translateProductToModule(product, ModuleNamePurpose.run); - // tslint:disable-next-line:no-empty - } catch { } - - // User may have customized the module name or provided the fully qualifieid path. + // User may have customized the module name or provided the fully qualified path. const executableName = this.getExecutableNameFromSettings(product, resource); - const isModule = typeof moduleName === 'string' && moduleName.length > 0 && path.basename(executableName) === executableName; + const isModule = this.isExecutableAModule(product, resource); if (isModule) { const pythonProcess = await this.serviceContainer.get(IPythonExecutionFactory).create({ resource }); return pythonProcess.isModuleInstalled(executableName); @@ -104,7 +90,14 @@ export abstract class BaseInstaller { } protected abstract promptToInstallImplementation(product: Product, resource?: Uri): Promise; protected getExecutableNameFromSettings(product: Product, resource?: Uri): string { - throw new Error('getExecutableNameFromSettings is not supported on this object'); + const productType = this.productService.getProductType(product); + const productPathService = this.serviceContainer.get(IProductPathService, productType); + return productPathService.getExecutableNameFromSettings(product, resource); + } + protected isExecutableAModule(product: Product, resource?: Uri): Boolean { + const productType = this.productService.getProductType(product); + const productPathService = this.serviceContainer.get(IProductPathService, productType); + return productPathService.isExecutableAModule(product, resource); } } @@ -133,11 +126,6 @@ export class CTagsInstaller extends BaseInstaller { const item = await this.appShell.showErrorMessage('Install CTags to enable Python workspace symbols?', 'Yes', 'No'); return item === 'Yes' ? this.install(product, resource) : InstallerResponse.Ignore; } - - protected getExecutableNameFromSettings(product: Product, resource?: Uri): string { - const settings = this.configService.getSettings(resource); - return settings.workspaceSymbols.ctagsPath; - } } export class FormatterInstaller extends BaseInstaller { @@ -151,7 +139,16 @@ export class FormatterInstaller extends BaseInstaller { const useOptions = formatterNames.map((name) => `Use ${name}`); const yesChoice = 'Yes'; - const item = await this.appShell.showErrorMessage(`Formatter ${productName} is not installed. Install?`, yesChoice, ...useOptions); + const options = [...useOptions]; + let message = `Formatter ${productName} is not installed. Install?`; + if (this.isExecutableAModule(product, resource)) { + options.splice(0, 0, yesChoice); + } else { + const executable = this.getExecutableNameFromSettings(product, resource); + message = `Path to the ${productName} formatter is invalid (${executable})`; + } + + const item = await this.appShell.showErrorMessage(message, ...options); if (item === yesChoice) { return this.install(product, resource); } else if (typeof item === 'string') { @@ -167,16 +164,8 @@ export class FormatterInstaller extends BaseInstaller { return InstallerResponse.Ignore; } - - protected getExecutableNameFromSettings(product: Product, resource?: Uri): string { - const settings = this.configService.getSettings(resource); - const formatHelper = this.serviceContainer.get(IFormatterHelper); - const settingsPropNames = formatHelper.getSettingsPropertyNames(product); - return settings.formatting[settingsPropNames.pathName] as string; - } } -// tslint:disable-next-line:max-classes-per-file export class LinterInstaller extends BaseInstaller { protected async promptToInstallImplementation(product: Product, resource?: Uri): Promise { const productName = ProductNames.get(product)!; @@ -184,8 +173,16 @@ export class LinterInstaller extends BaseInstaller { const disableAllLinting = 'Disable linting'; const disableThisLinter = `Disable ${productName}`; - const response = await this.appShell - .showErrorMessage(`Linter ${productName} is not installed.`, install, disableThisLinter, disableAllLinting); + const options = [disableThisLinter, disableAllLinting]; + let message = `Linter ${productName} is not installed.`; + if (this.isExecutableAModule(product, resource)) { + options.splice(0, 0, install); + } else { + const executable = this.getExecutableNameFromSettings(product, resource); + message = `Path to the ${productName} linter is invalid (${executable})`; + } + + const response = await this.appShell.showErrorMessage(message, ...options); if (response === install) { return this.install(product, resource); } @@ -199,66 +196,41 @@ export class LinterInstaller extends BaseInstaller { } return InstallerResponse.Ignore; } - protected getExecutableNameFromSettings(product: Product, resource?: Uri): string { - const linterManager = this.serviceContainer.get(ILinterManager); - return linterManager.getLinterInfo(product).pathName(resource); - } } -// tslint:disable-next-line:max-classes-per-file export class TestFrameworkInstaller extends BaseInstaller { protected async promptToInstallImplementation(product: Product, resource?: Uri): Promise { const productName = ProductNames.get(product)!; - const item = await this.appShell.showErrorMessage(`Test framework ${productName} is not installed. Install?`, 'Yes', 'No'); - return item === 'Yes' ? this.install(product, resource) : InstallerResponse.Ignore; - } - protected getExecutableNameFromSettings(product: Product, resource?: Uri): string { - const testHelper = this.serviceContainer.get(ITestsHelper); - const settingsPropNames = testHelper.getSettingsPropertyNames(product); - if (!settingsPropNames.pathName) { - // E.g. in the case of UnitTests we don't allow customizing the paths. - return translateProductToModule(product, ModuleNamePurpose.run); + const options: string[] = []; + let message = `Test framework ${productName} is not installed. Install?`; + if (this.isExecutableAModule(product, resource)) { + options.push(...['Yes', 'No']); + } else { + const executable = this.getExecutableNameFromSettings(product, resource); + message = `Path to the ${productName} test framework is invalid (${executable})`; } - const settings = this.configService.getSettings(resource); - return settings.unitTest[settingsPropNames.pathName] as string; + + const item = await this.appShell.showErrorMessage(message, ...options); + return item === 'Yes' ? this.install(product, resource) : InstallerResponse.Ignore; } } -// tslint:disable-next-line:max-classes-per-file export class RefactoringLibraryInstaller extends BaseInstaller { protected async promptToInstallImplementation(product: Product, resource?: Uri): Promise { const productName = ProductNames.get(product)!; const item = await this.appShell.showErrorMessage(`Refactoring library ${productName} is not installed. Install?`, 'Yes', 'No'); return item === 'Yes' ? this.install(product, resource) : InstallerResponse.Ignore; } - protected getExecutableNameFromSettings(product: Product, resource?: Uri): string { - return translateProductToModule(product, ModuleNamePurpose.run); - } } -// tslint:disable-next-line:max-classes-per-file @injectable() export class ProductInstaller implements IInstaller { - private ProductTypes = new Map(); + private readonly productService: IProductService; constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer, @inject(IOutputChannel) @named(STANDARD_OUTPUT_CHANNEL) private outputChannel: OutputChannel) { - this.ProductTypes.set(Product.flake8, ProductType.Linter); - this.ProductTypes.set(Product.mypy, ProductType.Linter); - this.ProductTypes.set(Product.pep8, ProductType.Linter); - this.ProductTypes.set(Product.prospector, ProductType.Linter); - this.ProductTypes.set(Product.pydocstyle, ProductType.Linter); - this.ProductTypes.set(Product.pylama, ProductType.Linter); - this.ProductTypes.set(Product.pylint, ProductType.Linter); - this.ProductTypes.set(Product.ctags, ProductType.WorkspaceSymbols); - this.ProductTypes.set(Product.nosetest, ProductType.TestFramework); - this.ProductTypes.set(Product.pytest, ProductType.TestFramework); - this.ProductTypes.set(Product.unittest, ProductType.TestFramework); - this.ProductTypes.set(Product.autopep8, ProductType.Formatter); - this.ProductTypes.set(Product.black, ProductType.Formatter); - this.ProductTypes.set(Product.yapf, ProductType.Formatter); - this.ProductTypes.set(Product.rope, ProductType.RefactoringLibrary); + this.productService = serviceContainer.get(IProductService); } // tslint:disable-next-line:no-empty @@ -275,9 +247,8 @@ export class ProductInstaller implements IInstaller { public translateProductToModuleName(product: Product, purpose: ModuleNamePurpose): string { return translateProductToModule(product, purpose); } - private createInstaller(product: Product): BaseInstaller { - const productType = this.ProductTypes.get(product)!; + const productType = this.productService.getProductType(product); switch (productType) { case ProductType.Formatter: return new FormatterInstaller(this.serviceContainer, this.outputChannel); diff --git a/src/client/common/installer/productPath.ts b/src/client/common/installer/productPath.ts new file mode 100644 index 000000000000..9e4500a7eabd --- /dev/null +++ b/src/client/common/installer/productPath.ts @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:max-classes-per-file + +import { inject, injectable } from 'inversify'; +import * as path from 'path'; +import { Uri } from 'vscode'; +import { IFormatterHelper } from '../../formatters/types'; +import { IServiceContainer } from '../../ioc/types'; +import { ILinterManager } from '../../linters/types'; +import { ITestsHelper } from '../../unittests/common/types'; +import { IConfigurationService, IInstaller, ModuleNamePurpose, Product } from '../types'; +import { IProductPathService } from './types'; + +@injectable() +abstract class BaseProductPathsService implements IProductPathService { + protected readonly configService: IConfigurationService; + protected readonly productInstaller: IInstaller; + constructor(@inject(IServiceContainer) protected serviceContainer: IServiceContainer) { + this.configService = serviceContainer.get(IConfigurationService); + this.productInstaller = serviceContainer.get(IInstaller); + } + public abstract getExecutableNameFromSettings(product: Product, resource?: Uri): string; + public isExecutableAModule(product: Product, resource?: Uri): Boolean { + let moduleName: string | undefined; + try { + moduleName = this.productInstaller.translateProductToModuleName(product, ModuleNamePurpose.run); + // tslint:disable-next-line:no-empty + } catch { } + + // User may have customized the module name or provided the fully qualifieid path. + const executableName = this.getExecutableNameFromSettings(product, resource); + + return typeof moduleName === 'string' && moduleName.length > 0 && path.basename(executableName) === executableName; + } +} + +@injectable() +export class CTagsProductPathService extends BaseProductPathsService { + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { + super(serviceContainer); + } + public getExecutableNameFromSettings(_: Product, resource?: Uri): string { + const settings = this.configService.getSettings(resource); + return settings.workspaceSymbols.ctagsPath; + } +} + +@injectable() +export class FormatterProductPathService extends BaseProductPathsService { + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { + super(serviceContainer); + } + public getExecutableNameFromSettings(product: Product, resource?: Uri): string { + const settings = this.configService.getSettings(resource); + const formatHelper = this.serviceContainer.get(IFormatterHelper); + const settingsPropNames = formatHelper.getSettingsPropertyNames(product); + return settings.formatting[settingsPropNames.pathName] as string; + } +} + +@injectable() +export class LinterProductPathService extends BaseProductPathsService { + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { + super(serviceContainer); + } + public getExecutableNameFromSettings(product: Product, resource?: Uri): string { + const linterManager = this.serviceContainer.get(ILinterManager); + return linterManager.getLinterInfo(product).pathName(resource); + } +} + +@injectable() +export class TestFrameworkProductPathService extends BaseProductPathsService { + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { + super(serviceContainer); + } + public getExecutableNameFromSettings(product: Product, resource?: Uri): string { + const testHelper = this.serviceContainer.get(ITestsHelper); + const settingsPropNames = testHelper.getSettingsPropertyNames(product); + if (!settingsPropNames.pathName) { + // E.g. in the case of UnitTests we don't allow customizing the paths. + return this.productInstaller.translateProductToModuleName(product, ModuleNamePurpose.run); + } + const settings = this.configService.getSettings(resource); + return settings.unitTest[settingsPropNames.pathName] as string; + } +} + +@injectable() +export class RefactoringLibraryProductPathService extends BaseProductPathsService { + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { + super(serviceContainer); + } + public getExecutableNameFromSettings(product: Product, _?: Uri): string { + return this.productInstaller.translateProductToModuleName(product, ModuleNamePurpose.run); + } +} diff --git a/src/client/common/installer/productService.ts b/src/client/common/installer/productService.ts new file mode 100644 index 000000000000..78ce5bbec847 --- /dev/null +++ b/src/client/common/installer/productService.ts @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { injectable } from 'inversify'; +import { Product, ProductType } from '../types'; +import { IProductService } from './types'; + +@injectable() +export class ProductService implements IProductService { + private ProductTypes = new Map(); + + constructor() { + this.ProductTypes.set(Product.flake8, ProductType.Linter); + this.ProductTypes.set(Product.mypy, ProductType.Linter); + this.ProductTypes.set(Product.pep8, ProductType.Linter); + this.ProductTypes.set(Product.prospector, ProductType.Linter); + this.ProductTypes.set(Product.pydocstyle, ProductType.Linter); + this.ProductTypes.set(Product.pylama, ProductType.Linter); + this.ProductTypes.set(Product.pylint, ProductType.Linter); + this.ProductTypes.set(Product.ctags, ProductType.WorkspaceSymbols); + this.ProductTypes.set(Product.nosetest, ProductType.TestFramework); + this.ProductTypes.set(Product.pytest, ProductType.TestFramework); + this.ProductTypes.set(Product.unittest, ProductType.TestFramework); + this.ProductTypes.set(Product.autopep8, ProductType.Formatter); + this.ProductTypes.set(Product.black, ProductType.Formatter); + this.ProductTypes.set(Product.yapf, ProductType.Formatter); + this.ProductTypes.set(Product.rope, ProductType.RefactoringLibrary); + } + public getProductType(product: Product): ProductType { + return this.ProductTypes.get(product)!; + } +} diff --git a/src/client/common/installer/serviceRegistry.ts b/src/client/common/installer/serviceRegistry.ts index 9f50aa77fba3..3cbd2cacaea6 100644 --- a/src/client/common/installer/serviceRegistry.ts +++ b/src/client/common/installer/serviceRegistry.ts @@ -3,15 +3,25 @@ 'use strict'; import { IServiceManager } from '../../ioc/types'; +import { ProductType } from '../types'; import { InstallationChannelManager } from './channelManager'; import { CondaInstaller } from './condaInstaller'; import { PipEnvInstaller } from './pipEnvInstaller'; import { PipInstaller } from './pipInstaller'; -import { IInstallationChannelManager, IModuleInstaller } from './types'; +import { CTagsProductPathService, FormatterProductPathService, LinterProductPathService, RefactoringLibraryProductPathService, TestFrameworkProductPathService } from './productPath'; +import { ProductService } from './productService'; +import { IInstallationChannelManager, IModuleInstaller, IProductPathService, IProductService } from './types'; export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IModuleInstaller, CondaInstaller); serviceManager.addSingleton(IModuleInstaller, PipInstaller); serviceManager.addSingleton(IModuleInstaller, PipEnvInstaller); serviceManager.addSingleton(IInstallationChannelManager, InstallationChannelManager); + + serviceManager.addSingleton(IProductService, ProductService); + serviceManager.addSingleton(IProductPathService, CTagsProductPathService, ProductType.WorkspaceSymbols); + serviceManager.addSingleton(IProductPathService, FormatterProductPathService, ProductType.Formatter); + serviceManager.addSingleton(IProductPathService, LinterProductPathService, ProductType.Linter); + serviceManager.addSingleton(IProductPathService, TestFrameworkProductPathService, ProductType.TestFramework); + serviceManager.addSingleton(IProductPathService, RefactoringLibraryProductPathService, ProductType.RefactoringLibrary); } diff --git a/src/client/common/installer/types.ts b/src/client/common/installer/types.ts index d84cf7bb64d2..c0521ee9386e 100644 --- a/src/client/common/installer/types.ts +++ b/src/client/common/installer/types.ts @@ -2,7 +2,7 @@ // Licensed under the MIT License. import { Uri } from 'vscode'; -import { Product } from '../types'; +import { Product, ProductType } from '../types'; export const IModuleInstaller = Symbol('IModuleInstaller'); export interface IModuleInstaller { @@ -23,3 +23,12 @@ export interface IInstallationChannelManager { getInstallationChannels(resource?: Uri): Promise; showNoInstallersMessage(): void; } +export const IProductService = Symbol('IProductService'); +export interface IProductService { + getProductType(product: Product): ProductType; +} +export const IProductPathService = Symbol('IProductPathService'); +export interface IProductPathService { + getExecutableNameFromSettings(product: Product, resource?: Uri): string; + isExecutableAModule(product: Product, resource?: Uri): Boolean; +} diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 6cc808d62107..954864e9596f 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -50,6 +50,14 @@ export enum InstallerResponse { Ignore } +export enum ProductType { + Linter = 'Linter', + Formatter = 'Formatter', + TestFramework = 'TestFramework', + RefactoringLibrary = 'RefactoringLibrary', + WorkspaceSymbols = 'WorkspaceSymbols' +} + export enum Product { pytest = 1, nosetest = 2, diff --git a/src/test/common/installer.test.ts b/src/test/common/installer.test.ts index 1238dcd6f52e..e32ceccf6d73 100644 --- a/src/test/common/installer.test.ts +++ b/src/test/common/installer.test.ts @@ -7,13 +7,15 @@ import { EnumEx } from '../../client/common/enumUtils'; import { createDeferred } from '../../client/common/helpers'; import { InstallationChannelManager } from '../../client/common/installer/channelManager'; import { ProductInstaller } from '../../client/common/installer/productInstaller'; -import { IInstallationChannelManager, IModuleInstaller } from '../../client/common/installer/types'; +import { CTagsProductPathService, FormatterProductPathService, LinterProductPathService, RefactoringLibraryProductPathService, TestFrameworkProductPathService } from '../../client/common/installer/productPath'; +import { ProductService } from '../../client/common/installer/productService'; +import { IInstallationChannelManager, IModuleInstaller, IProductPathService, IProductService } from '../../client/common/installer/types'; import { Logger } from '../../client/common/logger'; import { PersistentStateFactory } from '../../client/common/persistentState'; import { PathUtils } from '../../client/common/platform/pathUtils'; import { CurrentProcess } from '../../client/common/process/currentProcess'; import { IProcessServiceFactory } from '../../client/common/process/types'; -import { IConfigurationService, ICurrentProcess, IInstaller, ILogger, IPathUtils, IPersistentStateFactory, IsWindows, ModuleNamePurpose, Product } from '../../client/common/types'; +import { IConfigurationService, ICurrentProcess, IInstaller, ILogger, IPathUtils, IPersistentStateFactory, IsWindows, ModuleNamePurpose, Product, ProductType } from '../../client/common/types'; import { rootWorkspaceUri, updateSetting } from '../common'; import { MockModuleInstaller } from '../mocks/moduleInstaller'; import { MockProcessService } from '../mocks/proc'; @@ -65,6 +67,12 @@ suite('Installer', () => { ioc.registerMockProcessTypes(); ioc.serviceManager.addSingletonInstance(IsWindows, false); + ioc.serviceManager.addSingletonInstance(IProductService, new ProductService()); + ioc.serviceManager.addSingleton(IProductPathService, CTagsProductPathService, ProductType.WorkspaceSymbols); + ioc.serviceManager.addSingleton(IProductPathService, FormatterProductPathService, ProductType.Formatter); + ioc.serviceManager.addSingleton(IProductPathService, LinterProductPathService, ProductType.Linter); + ioc.serviceManager.addSingleton(IProductPathService, TestFrameworkProductPathService, ProductType.TestFramework); + ioc.serviceManager.addSingleton(IProductPathService, RefactoringLibraryProductPathService, ProductType.RefactoringLibrary); } async function resetSettings() { await updateSetting('linting.pylintEnabled', true, rootWorkspaceUri, ConfigurationTarget.Workspace); diff --git a/src/test/common/installer/installer.invalidPath.unit.test.ts b/src/test/common/installer/installer.invalidPath.unit.test.ts new file mode 100644 index 000000000000..27b1d726518a --- /dev/null +++ b/src/test/common/installer/installer.invalidPath.unit.test.ts @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { expect, use } from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import * as path from 'path'; +import * as TypeMoq from 'typemoq'; +import { OutputChannel, Uri } from 'vscode'; +import { IApplicationShell, IWorkspaceService } from '../../../client/common/application/types'; +import { EnumEx } from '../../../client/common/enumUtils'; +import '../../../client/common/extensions'; +import { ProductInstaller } from '../../../client/common/installer/productInstaller'; +import { ProductService } from '../../../client/common/installer/productService'; +import { IProductPathService, IProductService } from '../../../client/common/installer/types'; +import { Product } from '../../../client/common/types'; +import { IServiceContainer } from '../../../client/ioc/types'; + +use(chaiAsPromised); + +suite('Module Installer - Invalid Paths', () => { + [undefined, Uri.file('resource')].forEach(resource => { + ['moduleName', path.join('users', 'dev', 'tool', 'executable')].forEach(pathToExecutable => { + const isExecutableAModule = path.basename(pathToExecutable) === pathToExecutable; + + EnumEx.getNamesAndValues(Product).forEach(product => { + let installer: ProductInstaller; + let serviceContainer: TypeMoq.IMock; + let app: TypeMoq.IMock; + let workspaceService: TypeMoq.IMock; + let productPathService: TypeMoq.IMock; + setup(() => { + serviceContainer = TypeMoq.Mock.ofType(); + const outputChannel = TypeMoq.Mock.ofType(); + + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IProductService), TypeMoq.It.isAny())).returns(() => new ProductService()); + app = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IApplicationShell), TypeMoq.It.isAny())).returns(() => app.object); + workspaceService = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IWorkspaceService), TypeMoq.It.isAny())).returns(() => workspaceService.object); + + productPathService = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IProductPathService), TypeMoq.It.isAny())).returns(() => productPathService.object); + + installer = new ProductInstaller(serviceContainer.object, outputChannel.object); + }); + + switch (product.value) { + case Product.isort: + case Product.ctags: + case Product.rope: + case Product.unittest: { + return; + } + default: { + test(`Ensure invalid path message is ${isExecutableAModule ? 'not displayed' : 'displayed'} ${product.name} (${resource ? 'With a resource' : 'without a resource'})`, async () => { + // If the path to executable is a module, then we won't display error message indicating path is invalid. + + productPathService + .setup(p => p.getExecutableNameFromSettings(TypeMoq.It.isAny(), TypeMoq.It.isValue(resource))) + .returns(() => pathToExecutable) + .verifiable(TypeMoq.Times.atLeast(isExecutableAModule ? 0 : 1)); + productPathService + .setup(p => p.isExecutableAModule(TypeMoq.It.isAny(), TypeMoq.It.isValue(resource))) + .returns(() => isExecutableAModule) + .verifiable(TypeMoq.Times.atLeastOnce()); + const anyParams = [0, 1, 2, 3, 4, 5].map(() => TypeMoq.It.isAny()); + app.setup(a => a.showErrorMessage(TypeMoq.It.isAny(), ...anyParams)) + .callback(message => { + if (!isExecutableAModule) { + expect(message).contains(pathToExecutable); + } + }) + .returns(() => Promise.resolve(undefined)) + .verifiable(TypeMoq.Times.exactly(1)); + + await installer.promptToInstall(product.value, resource); + productPathService.verifyAll(); + }); + } + } + }); + }); + }); +}); diff --git a/src/test/common/installer/installer.test.ts b/src/test/common/installer/installer.unit.test.ts similarity index 91% rename from src/test/common/installer/installer.test.ts rename to src/test/common/installer/installer.unit.test.ts index 69f88f2c21ea..09c4de523d1d 100644 --- a/src/test/common/installer/installer.test.ts +++ b/src/test/common/installer/installer.unit.test.ts @@ -12,7 +12,8 @@ import { EnumEx } from '../../../client/common/enumUtils'; import '../../../client/common/extensions'; import { createDeferred, Deferred } from '../../../client/common/helpers'; import { ProductInstaller } from '../../../client/common/installer/productInstaller'; -import { IInstallationChannelManager, IModuleInstaller } from '../../../client/common/installer/types'; +import { ProductService } from '../../../client/common/installer/productService'; +import { IInstallationChannelManager, IModuleInstaller, IProductPathService, IProductService } from '../../../client/common/installer/types'; import { IDisposableRegistry, ILogger, InstallerResponse, ModuleNamePurpose, Product } from '../../../client/common/types'; import { IServiceContainer } from '../../../client/ioc/types'; @@ -34,11 +35,9 @@ suite('Module Installer', () => { serviceContainer = TypeMoq.Mock.ofType(); const outputChannel = TypeMoq.Mock.ofType(); - installer = new ProductInstaller(serviceContainer.object, outputChannel.object); - disposables = []; serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IDisposableRegistry), TypeMoq.It.isAny())).returns(() => disposables); - + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IProductService), TypeMoq.It.isAny())).returns(() => new ProductService()); installationChannel = TypeMoq.Mock.ofType(); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInstallationChannelManager), TypeMoq.It.isAny())).returns(() => installationChannel.object); app = TypeMoq.Mock.ofType(); @@ -51,6 +50,13 @@ suite('Module Installer', () => { moduleInstaller.setup((x: any) => x.then).returns(() => undefined); installationChannel.setup(i => i.getInstallationChannel(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(moduleInstaller.object)); installationChannel.setup(i => i.getInstallationChannel(TypeMoq.It.isAny())).returns(() => Promise.resolve(moduleInstaller.object)); + + const productPathService = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IProductPathService), TypeMoq.It.isAny())).returns(() => productPathService.object); + productPathService.setup(p => p.getExecutableNameFromSettings(TypeMoq.It.isAny(), TypeMoq.It.isValue(resource))).returns(() => 'xyz'); + productPathService.setup(p => p.isExecutableAModule(TypeMoq.It.isAny(), TypeMoq.It.isValue(resource))).returns(() => true); + + installer = new ProductInstaller(serviceContainer.object, outputChannel.object); }); teardown(() => { // This must be resolved, else all subsequent tests will fail (as this same promise will be used for other tests). diff --git a/src/test/common/installer/moduleInstaller.test.ts b/src/test/common/installer/moduleInstaller.test.ts index ce0fd56cb567..a7590f952896 100644 --- a/src/test/common/installer/moduleInstaller.test.ts +++ b/src/test/common/installer/moduleInstaller.test.ts @@ -16,7 +16,7 @@ import { IServiceContainer } from '../../../client/ioc/types'; import { initialize } from '../../initialize'; // tslint:disable-next-line:max-func-body-length -suite('Module Installer', () => { +suite('Module Installerx', () => { const pythonPath = path.join(__dirname, 'python'); suiteSetup(initialize); [CondaInstaller, PipInstaller].forEach(installerClass => { diff --git a/src/test/common/installer/productPath.unit.test.ts b/src/test/common/installer/productPath.unit.test.ts new file mode 100644 index 000000000000..3e34ce86677a --- /dev/null +++ b/src/test/common/installer/productPath.unit.test.ts @@ -0,0 +1,189 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:max-func-body-length no-invalid-this + +import { fail } from 'assert'; +import { expect, use } from 'chai'; +import * as chaiAsPromised from 'chai-as-promised'; +import * as TypeMoq from 'typemoq'; +import { OutputChannel, Uri } from 'vscode'; +import { EnumEx } from '../../../client/common/enumUtils'; +import '../../../client/common/extensions'; +import { ProductInstaller } from '../../../client/common/installer/productInstaller'; +import { CTagsProductPathService, FormatterProductPathService, LinterProductPathService, RefactoringLibraryProductPathService, TestFrameworkProductPathService } from '../../../client/common/installer/productPath'; +import { ProductService } from '../../../client/common/installer/productService'; +import { IProductService } from '../../../client/common/installer/types'; +import { IConfigurationService, IFormattingSettings, IInstaller, IPythonSettings, IUnitTestSettings, IWorkspaceSymbolSettings, ModuleNamePurpose, Product, ProductType } from '../../../client/common/types'; +import { IFormatterHelper } from '../../../client/formatters/types'; +import { IServiceContainer } from '../../../client/ioc/types'; +import { ILinterInfo, ILinterManager } from '../../../client/linters/types'; +import { ITestsHelper } from '../../../client/unittests/common/types'; + +use(chaiAsPromised); + +suite('Product Path', () => { + [undefined, Uri.file('resource')].forEach(resource => { + EnumEx.getNamesAndValues(Product).forEach(product => { + let serviceContainer: TypeMoq.IMock; + let formattingSettings: TypeMoq.IMock; + let unitTestSettings: TypeMoq.IMock; + let workspaceSymnbolSettings: TypeMoq.IMock; + let configService: TypeMoq.IMock; + let productInstaller: ProductInstaller; + setup(() => { + serviceContainer = TypeMoq.Mock.ofType(); + configService = TypeMoq.Mock.ofType(); + formattingSettings = TypeMoq.Mock.ofType(); + unitTestSettings = TypeMoq.Mock.ofType(); + workspaceSymnbolSettings = TypeMoq.Mock.ofType(); + + productInstaller = new ProductInstaller(serviceContainer.object, TypeMoq.Mock.ofType().object); + const pythonSettings = TypeMoq.Mock.ofType(); + pythonSettings.setup(p => p.formatting).returns(() => formattingSettings.object); + pythonSettings.setup(p => p.unitTest).returns(() => unitTestSettings.object); + pythonSettings.setup(p => p.workspaceSymbols).returns(() => workspaceSymnbolSettings.object); + configService.setup(s => s.getSettings(TypeMoq.It.isValue(resource))) + .returns(() => pythonSettings.object); + serviceContainer.setup(s => s.get(TypeMoq.It.isValue(IConfigurationService), TypeMoq.It.isAny())) + .returns(() => configService.object); + serviceContainer.setup(s => s.get(TypeMoq.It.isValue(IInstaller), TypeMoq.It.isAny())) + .returns(() => productInstaller); + + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IProductService), TypeMoq.It.isAny())).returns(() => new ProductService()); + }); + + if (product.value === Product.isort) { + return; + } + const productType = new ProductService().getProductType(product.value); + switch (productType) { + case ProductType.Formatter: { + test(`Ensure path is returned for ${product.name} (${resource ? 'With a resource' : 'without a resource'})`, async () => { + const productPathService = new FormatterProductPathService(serviceContainer.object); + const formatterHelper = TypeMoq.Mock.ofType(); + const expectedPath = 'Some Path'; + serviceContainer.setup(s => s.get(TypeMoq.It.isValue(IFormatterHelper), TypeMoq.It.isAny())) + .returns(() => formatterHelper.object); + formattingSettings.setup(f => f.autopep8Path) + .returns(() => expectedPath) + .verifiable(TypeMoq.Times.atLeastOnce()); + formatterHelper.setup(f => f.getSettingsPropertyNames(TypeMoq.It.isValue(product.value))) + .returns(() => { + return { + pathName: 'autopep8Path', + argsName: 'autopep8Args' + }; + }) + .verifiable(TypeMoq.Times.once()); + + const value = productPathService.getExecutableNameFromSettings(product.value, resource); + expect(value).to.be.equal(expectedPath); + formattingSettings.verifyAll(); + formatterHelper.verifyAll(); + }); + break; + } + case ProductType.Linter: { + test(`Ensure path is returned for ${product.name} (${resource ? 'With a resource' : 'without a resource'})`, async () => { + const productPathService = new LinterProductPathService(serviceContainer.object); + const linterManager = TypeMoq.Mock.ofType(); + const linterInfo = TypeMoq.Mock.ofType(); + const expectedPath = 'Some Path'; + serviceContainer.setup(s => s.get(TypeMoq.It.isValue(ILinterManager), TypeMoq.It.isAny())) + .returns(() => linterManager.object); + linterInfo.setup(l => l.pathName(TypeMoq.It.isValue(resource))) + .returns(() => expectedPath) + .verifiable(TypeMoq.Times.once()); + linterManager.setup(l => l.getLinterInfo(TypeMoq.It.isValue(product.value))) + .returns(() => linterInfo.object) + .verifiable(TypeMoq.Times.once()); + + const value = productPathService.getExecutableNameFromSettings(product.value, resource); + expect(value).to.be.equal(expectedPath); + linterInfo.verifyAll(); + linterManager.verifyAll(); + }); + } + case ProductType.RefactoringLibrary: { + test(`Ensure path is returned for ${product.name} (${resource ? 'With a resource' : 'without a resource'})`, async () => { + const productPathService = new RefactoringLibraryProductPathService(serviceContainer.object); + + const value = productPathService.getExecutableNameFromSettings(product.value, resource); + const moduleName = productInstaller.translateProductToModuleName(product.value, ModuleNamePurpose.run); + expect(value).to.be.equal(moduleName); + }); + break; + } + case ProductType.WorkspaceSymbols: { + test(`Ensure path is returned for ${product.name} (${resource ? 'With a resource' : 'without a resource'})`, async () => { + const productPathService = new CTagsProductPathService(serviceContainer.object); + const expectedPath = 'Some Path'; + workspaceSymnbolSettings.setup(w => w.ctagsPath) + .returns(() => expectedPath) + .verifiable(TypeMoq.Times.atLeastOnce()); + + const value = productPathService.getExecutableNameFromSettings(product.value, resource); + expect(value).to.be.equal(expectedPath); + workspaceSymnbolSettings.verifyAll(); + }); + break; + } + case ProductType.TestFramework: { + test(`Ensure path is returned for ${product.name} (${resource ? 'With a resource' : 'without a resource'})`, async () => { + const productPathService = new TestFrameworkProductPathService(serviceContainer.object); + const testHelper = TypeMoq.Mock.ofType(); + const expectedPath = 'Some Path'; + serviceContainer.setup(s => s.get(TypeMoq.It.isValue(ITestsHelper), TypeMoq.It.isAny())) + .returns(() => testHelper.object); + testHelper.setup(t => t.getSettingsPropertyNames(TypeMoq.It.isValue(product.value))) + .returns(() => { + return { + argsName: 'autoTestDiscoverOnSaveEnabled', + enabledName: 'autoTestDiscoverOnSaveEnabled', + pathName: 'nosetestPath' + }; + }) + .verifiable(TypeMoq.Times.once()); + unitTestSettings.setup(u => u.nosetestPath) + .returns(() => expectedPath) + .verifiable(TypeMoq.Times.atLeastOnce()); + + const value = productPathService.getExecutableNameFromSettings(product.value, resource); + expect(value).to.be.equal(expectedPath); + testHelper.verifyAll(); + unitTestSettings.verifyAll(); + }); + test(`Ensure module name is returned for ${product.name} (${resource ? 'With a resource' : 'without a resource'})`, async () => { + const productPathService = new TestFrameworkProductPathService(serviceContainer.object); + const testHelper = TypeMoq.Mock.ofType(); + serviceContainer.setup(s => s.get(TypeMoq.It.isValue(ITestsHelper), TypeMoq.It.isAny())) + .returns(() => testHelper.object); + testHelper.setup(t => t.getSettingsPropertyNames(TypeMoq.It.isValue(product.value))) + .returns(() => { + return { + argsName: 'autoTestDiscoverOnSaveEnabled', + enabledName: 'autoTestDiscoverOnSaveEnabled', + pathName: undefined + }; + }) + .verifiable(TypeMoq.Times.once()); + + const value = productPathService.getExecutableNameFromSettings(product.value, resource); + const moduleName = productInstaller.translateProductToModuleName(product.value, ModuleNamePurpose.run); + expect(value).to.be.equal(moduleName); + testHelper.verifyAll(); + }); + break; + } + default: { + test(`No tests for Product Path of this Product Type ${product.name}`, () => { + fail('No tests for Product Path of this Product Type'); + }); + } + } + }); + }); +}); diff --git a/src/test/linters/lint.multiroot.test.ts b/src/test/linters/lint.multiroot.test.ts index 6ec8508c3371..df56a04324f4 100644 --- a/src/test/linters/lint.multiroot.test.ts +++ b/src/test/linters/lint.multiroot.test.ts @@ -2,15 +2,19 @@ import * as assert from 'assert'; import * as path from 'path'; import { CancellationTokenSource, ConfigurationTarget, OutputChannel, Uri, workspace } from 'vscode'; import { PythonSettings } from '../../client/common/configSettings'; -import { IConfigurationService, IOutputChannel, Product } from '../../client/common/types'; +import { CTagsProductPathService, FormatterProductPathService, LinterProductPathService, RefactoringLibraryProductPathService, TestFrameworkProductPathService } from '../../client/common/installer/productPath'; +import { ProductService } from '../../client/common/installer/productService'; +import { IProductPathService, IProductService } from '../../client/common/installer/types'; +import { IConfigurationService, IOutputChannel, Product, ProductType } from '../../client/common/types'; import { ILinter, ILinterManager } from '../../client/linters/types'; import { TEST_OUTPUT_CHANNEL } from '../../client/unittests/common/constants'; import { closeActiveWindows, initialize, initializeTest, IS_MULTI_ROOT_TEST } from '../initialize'; import { UnitTestIocContainer } from '../unittests/serviceRegistry'; +// tslint:disable:max-func-body-length no-invalid-this + const multirootPath = path.join(__dirname, '..', '..', '..', 'src', 'testMultiRootWkspc'); -// tslint:disable-next-line:max-func-body-length suite('Multiroot Linting', () => { const pylintSetting = 'linting.pylintEnabled'; const flake8Setting = 'linting.flake8Enabled'; @@ -18,7 +22,6 @@ suite('Multiroot Linting', () => { let ioc: UnitTestIocContainer; suiteSetup(function () { if (!IS_MULTI_ROOT_TEST) { - // tslint:disable-next-line:no-invalid-this this.skip(); } return initialize(); @@ -41,6 +44,13 @@ suite('Multiroot Linting', () => { ioc.registerLinterTypes(); ioc.registerVariableTypes(); ioc.registerPlatformTypes(); + ioc.serviceManager.addSingletonInstance(IProductService, new ProductService()); + ioc.serviceManager.addSingleton(IProductPathService, CTagsProductPathService, ProductType.WorkspaceSymbols); + ioc.serviceManager.addSingleton(IProductPathService, FormatterProductPathService, ProductType.Formatter); + ioc.serviceManager.addSingleton(IProductPathService, LinterProductPathService, ProductType.Linter); + ioc.serviceManager.addSingleton(IProductPathService, TestFrameworkProductPathService, ProductType.TestFramework); + ioc.serviceManager.addSingleton(IProductPathService, RefactoringLibraryProductPathService, ProductType.RefactoringLibrary); + } async function createLinter(product: Product, resource?: Uri): Promise { diff --git a/src/test/linters/lint.test.ts b/src/test/linters/lint.test.ts index 56c0dc88afdf..254fb3ba6ba2 100644 --- a/src/test/linters/lint.test.ts +++ b/src/test/linters/lint.test.ts @@ -1,12 +1,14 @@ import * as assert from 'assert'; import * as fs from 'fs-extra'; import * as path from 'path'; -import { Uri } from 'vscode'; -import * as vscode from 'vscode'; +import { CancellationTokenSource, ConfigurationTarget, DiagnosticCollection, Uri, window, workspace } from 'vscode'; import { ICommandManager } from '../../client/common/application/types'; import { STANDARD_OUTPUT_CHANNEL } from '../../client/common/constants'; import { Product } from '../../client/common/installer/productInstaller'; -import { IConfigurationService, IOutputChannel } from '../../client/common/types'; +import { CTagsProductPathService, FormatterProductPathService, LinterProductPathService, RefactoringLibraryProductPathService, TestFrameworkProductPathService } from '../../client/common/installer/productPath'; +import { ProductService } from '../../client/common/installer/productService'; +import { IProductPathService, IProductService } from '../../client/common/installer/types'; +import { IConfigurationService, IOutputChannel, ProductType } from '../../client/common/types'; import { LinterManager } from '../../client/linters/linterManager'; import { ILinterManager, ILintMessage, LintMessageSeverity } from '../../client/linters/types'; import { deleteFile, PythonSettingKeys, rootWorkspaceUri } from '../common'; @@ -120,14 +122,19 @@ suite('Linting', () => { ioc.registerLinterTypes(); ioc.registerVariableTypes(); ioc.registerPlatformTypes(); - linterManager = new LinterManager(ioc.serviceContainer); configService = ioc.serviceContainer.get(IConfigurationService); + ioc.serviceManager.addSingletonInstance(IProductService, new ProductService()); + ioc.serviceManager.addSingleton(IProductPathService, CTagsProductPathService, ProductType.WorkspaceSymbols); + ioc.serviceManager.addSingleton(IProductPathService, FormatterProductPathService, ProductType.Formatter); + ioc.serviceManager.addSingleton(IProductPathService, LinterProductPathService, ProductType.Linter); + ioc.serviceManager.addSingleton(IProductPathService, TestFrameworkProductPathService, ProductType.TestFramework); + ioc.serviceManager.addSingleton(IProductPathService, RefactoringLibraryProductPathService, ProductType.RefactoringLibrary); } async function resetSettings() { // Don't run these updates in parallel, as they are updating the same file. - const target = IS_MULTI_ROOT_TEST ? vscode.ConfigurationTarget.WorkspaceFolder : vscode.ConfigurationTarget.Workspace; + const target = IS_MULTI_ROOT_TEST ? ConfigurationTarget.WorkspaceFolder : ConfigurationTarget.Workspace; await configService.updateSettingAsync('linting.enabled', true, rootWorkspaceUri, target); await configService.updateSettingAsync('linting.lintOnSave', false, rootWorkspaceUri, target); @@ -147,11 +154,11 @@ suite('Linting', () => { const output = ioc.serviceContainer.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); await configService.updateSettingAsync(setting, enabled, rootWorkspaceUri, - IS_MULTI_ROOT_TEST ? vscode.ConfigurationTarget.WorkspaceFolder : vscode.ConfigurationTarget.Workspace); + IS_MULTI_ROOT_TEST ? ConfigurationTarget.WorkspaceFolder : ConfigurationTarget.Workspace); file = file ? file : fileToLint; - const document = await vscode.workspace.openTextDocument(file); - const cancelToken = new vscode.CancellationTokenSource(); + const document = await workspace.openTextDocument(file); + const cancelToken = new CancellationTokenSource(); await linterManager.setActiveLintersAsync([product]); await linterManager.enableLintingAsync(enabled); @@ -199,8 +206,8 @@ suite('Linting', () => { // tslint:disable-next-line:no-any async function testLinterMessages(product: Product, pythonFile: string, messagesToBeReceived: ILintMessage[]): Promise { const outputChannel = ioc.serviceContainer.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); - const cancelToken = new vscode.CancellationTokenSource(); - const document = await vscode.workspace.openTextDocument(pythonFile); + const cancelToken = new CancellationTokenSource(); + const document = await workspace.openTextDocument(pythonFile); await linterManager.setActiveLintersAsync([product], document.uri); const linter = linterManager.createLinter(product, outputChannel, ioc.serviceContainer); @@ -256,15 +263,15 @@ suite('Linting', () => { this.timeout(40000); await closeActiveWindows(); - const document = await vscode.workspace.openTextDocument(path.join(pythoFilesPath, 'print.py')); - await vscode.window.showTextDocument(document); + const document = await workspace.openTextDocument(path.join(pythoFilesPath, 'print.py')); + await window.showTextDocument(document); await configService.updateSettingAsync('linting.enabled', true, workspaceUri); await configService.updateSettingAsync('linting.pylintUseMinimalCheckers', false, workspaceUri); await configService.updateSettingAsync('linting.pylintEnabled', true, workspaceUri); await configService.updateSettingAsync('linting.flake8Enabled', true, workspaceUri); const commands = ioc.serviceContainer.get(ICommandManager); - const collection = await commands.executeCommand('python.runLinting') as vscode.DiagnosticCollection; + const collection = await commands.executeCommand('python.runLinting') as DiagnosticCollection; assert.notEqual(collection, undefined, 'python.runLinting did not return valid diagnostics collection.'); const messages = collection!.get(document.uri); From 36416804c6c0eebf83bf21f4fbcb6c66418dfbf9 Mon Sep 17 00:00:00 2001 From: Derek Keeler Date: Tue, 12 Jun 2018 17:50:49 -0700 Subject: [PATCH 336/433] Remove the 'TRAVIS' from 'TRAVIS_PYTHON_PATH' to suite other CI systems. (#1946) --- .travis.yml | 2 +- src/test/common.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 35b577f75773..f24850854c15 100644 --- a/.travis.yml +++ b/.travis.yml @@ -68,7 +68,7 @@ before_install: | npm install npm@latest -g npm install -g vsce npm install -g azure-cli - export TRAVIS_PYTHON_PATH=`which python` + export CI_PYTHON_PATH=`which python` install: - python -m pip install --upgrade -r requirements.txt - python -m pip install -t ./pythonFiles/experimental/ptvsd git+https://github.com/Microsoft/ptvsd/ diff --git a/src/test/common.ts b/src/test/common.ts index a8abb1271567..755d1a41a6f2 100644 --- a/src/test/common.ts +++ b/src/test/common.ts @@ -114,8 +114,8 @@ export const setPythonPathInWorkspaceRoot = async (pythonPath: string) => retryA export const resetGlobalPythonPathSetting = async () => retryAsync(restoreGlobalPythonPathSetting)(); function getPythonPath(): string { - if (process.env.TRAVIS_PYTHON_PATH && fs.existsSync(process.env.TRAVIS_PYTHON_PATH)) { - return process.env.TRAVIS_PYTHON_PATH; + if (process.env.CI_PYTHON_PATH && fs.existsSync(process.env.CI_PYTHON_PATH)) { + return process.env.CI_PYTHON_PATH; } return 'python'; } From 81a51283645f4829629933d71b0d23b6d4a7bee4 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Wed, 13 Jun 2018 14:04:32 -0700 Subject: [PATCH 337/433] Goto definition + symbols via language server (#1934) * LS symbol providers * Progress reporting * Go to definition * Comment paths normalization * Update tests to engine changes --- CONTRIBUTING - PYTHON_ANALYSIS.md | 2 +- src/client/activation/analysis.ts | 14 +++++- src/client/activation/classic.ts | 6 ++- src/client/extension.ts | 2 - src/test/definitions/navigation.test.ts | 62 ++++++++++++++----------- 5 files changed, 53 insertions(+), 33 deletions(-) diff --git a/CONTRIBUTING - PYTHON_ANALYSIS.md b/CONTRIBUTING - PYTHON_ANALYSIS.md index 0e8ce38c8c9a..d90e684e5083 100644 --- a/CONTRIBUTING - PYTHON_ANALYSIS.md +++ b/CONTRIBUTING - PYTHON_ANALYSIS.md @@ -8,7 +8,7 @@ ### Prerequisites -1. .NET Core 2.0+ SDK +1. .NET Core 2.1 SDK - [Windows](https://www.microsoft.com/net/learn/get-started/windows) - [Mac OS](https://www.microsoft.com/net/learn/get-started/macos) - [Linux](https://www.microsoft.com/net/learn/get-started/linux/rhel) diff --git a/src/client/activation/analysis.ts b/src/client/activation/analysis.ts index 7957bdc55172..57494a338a5d 100644 --- a/src/client/activation/analysis.ts +++ b/src/client/activation/analysis.ts @@ -199,13 +199,23 @@ export class AnalysisExtensionActivator implements IExtensionActivator { properties['DatabasePath'] = path.join(context.extensionPath, analysisEngineFolder); const envProvider = this.services.get(IEnvironmentVariablesProvider); - const pythonPath = (await envProvider.getEnvironmentVariables()).PYTHONPATH; + let pythonPath = (await envProvider.getEnvironmentVariables()).PYTHONPATH; this.interpreterHash = interpreterData ? interpreterData.hash : ''; + // Make sure paths do not contain multiple slashes so file URIs + // in VS Code (Node.js) and in the language server (.NET) match. + // Note: for the language server paths separator is always ; + searchPaths = searchPaths.split(path.delimiter).map(p => path.normalize(p)).join(';'); + pythonPath = pythonPath ? path.normalize(pythonPath) : ''; + // tslint:disable-next-line:no-string-literal - properties['SearchPaths'] = `${searchPaths};${pythonPath ? pythonPath : ''}`; + properties['SearchPaths'] = `${searchPaths};${pythonPath}`; const selector: string[] = [PYTHON]; + // const searchExcludes = workspace.getConfiguration('search').get('exclude', null); + // const filesExcludes = workspace.getConfiguration('files').get('exclude', null); + // const watcherExcludes = workspace.getConfiguration('files').get('watcherExclude', null); + // Options to control the language client return { // Register the server for Python documents diff --git a/src/client/activation/classic.ts b/src/client/activation/classic.ts index 17ca5c929047..72745cab86be 100644 --- a/src/client/activation/classic.ts +++ b/src/client/activation/classic.ts @@ -17,6 +17,7 @@ import { PythonRenameProvider } from '../providers/renameProvider'; import { PythonSignatureProvider } from '../providers/signatureProvider'; import { PythonSymbolProvider } from '../providers/symbolProvider'; import { IUnitTestManagementService } from '../unittests/types'; +import { WorkspaceSymbols } from '../workspaceSymbols/main'; import { IExtensionActivator } from './types'; @injectable() @@ -46,7 +47,10 @@ export class ClassicExtensionActivator implements IExtensionActivator { context.subscriptions.push(languages.registerCompletionItemProvider(this.documentSelector, new PythonCompletionItemProvider(jediFactory, this.serviceManager), '.')); context.subscriptions.push(languages.registerCodeLensProvider(this.documentSelector, this.serviceManager.get(IShebangCodeLensProvider))); - const symbolProvider = new PythonSymbolProvider(this.serviceManager.get(IServiceContainer), jediFactory); + const serviceContainer = this.serviceManager.get(IServiceContainer); + context.subscriptions.push(new WorkspaceSymbols(serviceContainer)); + + const symbolProvider = new PythonSymbolProvider(serviceContainer, jediFactory); context.subscriptions.push(languages.registerDocumentSymbolProvider(this.documentSelector, symbolProvider)); const pythonSettings = this.serviceManager.get(IConfigurationService).getSettings(); diff --git a/src/client/extension.ts b/src/client/extension.ts index 3e59f8ae408f..422d2fb0e2a6 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -57,7 +57,6 @@ import { BlockFormatProviders } from './typeFormatters/blockFormatProvider'; import { OnEnterFormatter } from './typeFormatters/onEnterFormatter'; import { TEST_OUTPUT_CHANNEL } from './unittests/common/constants'; import { registerTypes as unitTestsRegisterTypes } from './unittests/serviceRegistry'; -import { WorkspaceSymbols } from './workspaceSymbols/main'; const activationDeferred = createDeferred(); export const activated = activationDeferred.promise; @@ -144,7 +143,6 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(new ReplProvider(serviceContainer)); context.subscriptions.push(new TerminalProvider(serviceContainer)); - context.subscriptions.push(new WorkspaceSymbols(serviceContainer)); context.subscriptions.push(languages.registerCodeActionsProvider(PYTHON, new PythonCodeActionProvider())); diff --git a/src/test/definitions/navigation.test.ts b/src/test/definitions/navigation.test.ts index 24d4a716d39c..9b15afd397aa 100644 --- a/src/test/definitions/navigation.test.ts +++ b/src/test/definitions/navigation.test.ts @@ -4,6 +4,7 @@ import * as assert from 'assert'; import * as path from 'path'; import * as vscode from 'vscode'; +import { isPythonAnalysisEngineTest } from '../../client/common/constants'; import { closeActiveWindows, initialize, initializeTest } from '../initialize'; const decoratorsPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'definition', 'navigation'); @@ -32,95 +33,102 @@ suite('Definition Navigation', () => { assert.equal(formatPosition(expectedRange.end), formatPosition(range.end), 'End position is incorrect'); }; - const buildTest = (startFile: string, startPosition: vscode.Position, expectedFile: string, expectedRange: vscode.Range) => { + const buildTest = (startFile: string, startPosition: vscode.Position, expectedFiles: string[], expectedRanges: vscode.Range[]) => { return async () => { const textDocument = await vscode.workspace.openTextDocument(startFile); await vscode.window.showTextDocument(textDocument); assert(vscode.window.activeTextEditor, 'No active editor'); const locations = await vscode.commands.executeCommand('vscode.executeDefinitionProvider', textDocument.uri, startPosition); - assert.equal(1, locations!.length, 'Wrong number of results'); + assert.equal(expectedFiles.length, locations!.length, 'Wrong number of results'); - const def = locations![0]; - assertFile(expectedFile, def.uri); - assertRange(expectedRange, def.range!); + for (let i = 0; i < locations!.length; i += 1) { + assertFile(expectedFiles[i], locations![i].uri); + assertRange(expectedRanges[i], locations![i].range!); + } }; }; test('From own definition', buildTest( fileDefinitions, new vscode.Position(2, 6), - fileDefinitions, - new vscode.Range(2, 0, 11, 17) + [fileDefinitions], + isPythonAnalysisEngineTest() ? [new vscode.Range(2, 4, 2, 16)] : [new vscode.Range(2, 0, 11, 17)] )); test('Nested function', buildTest( fileDefinitions, new vscode.Position(11, 16), - fileDefinitions, - new vscode.Range(6, 4, 10, 16) + [fileDefinitions], + isPythonAnalysisEngineTest() ? [new vscode.Range(6, 8, 6, 15)] : [new vscode.Range(6, 4, 10, 16)] )); test('Decorator usage', buildTest( fileDefinitions, new vscode.Position(13, 1), - fileDefinitions, - new vscode.Range(2, 0, 11, 17) + [fileDefinitions], + isPythonAnalysisEngineTest() ? [new vscode.Range(2, 4, 2, 16)] : [new vscode.Range(2, 0, 11, 17)] )); test('Function decorated by stdlib', buildTest( fileDefinitions, new vscode.Position(29, 6), - fileDefinitions, - new vscode.Range(21, 0, 27, 17) + [fileDefinitions], + isPythonAnalysisEngineTest() ? [new vscode.Range(21, 4, 21, 22)] : [new vscode.Range(21, 0, 27, 17)] )); test('Function decorated by local decorator', buildTest( fileDefinitions, new vscode.Position(30, 6), - fileDefinitions, - new vscode.Range(14, 0, 18, 7) + [fileDefinitions], + isPythonAnalysisEngineTest() ? [new vscode.Range(14, 4, 14, 9)] : [new vscode.Range(14, 0, 18, 7)] )); test('Module imported decorator usage', buildTest( fileUsages, new vscode.Position(3, 15), - fileDefinitions, - new vscode.Range(2, 0, 11, 17) + [fileDefinitions], + isPythonAnalysisEngineTest() ? [new vscode.Range(2, 4, 2, 16)] : [new vscode.Range(2, 0, 11, 17)] )); test('Module imported function decorated by stdlib', buildTest( fileUsages, new vscode.Position(11, 19), - fileDefinitions, - new vscode.Range(21, 0, 27, 17) + [fileDefinitions], + isPythonAnalysisEngineTest() ? [new vscode.Range(21, 4, 21, 22)] : [new vscode.Range(21, 0, 27, 17)] )); test('Module imported function decorated by local decorator', buildTest( fileUsages, new vscode.Position(12, 19), - fileDefinitions, - new vscode.Range(14, 0, 18, 7) + [fileDefinitions], + isPythonAnalysisEngineTest() ? [new vscode.Range(14, 4, 14, 9)] : [new vscode.Range(14, 0, 18, 7)] )); test('Specifically imported decorator usage', buildTest( fileUsages, new vscode.Position(7, 1), - fileDefinitions, - new vscode.Range(2, 0, 11, 17) + isPythonAnalysisEngineTest() ? [fileUsages, fileDefinitions] : [fileDefinitions], + isPythonAnalysisEngineTest() + ? [new vscode.Range(1, 45, 1, 57), new vscode.Range(2, 4, 2, 16)] + : [new vscode.Range(2, 0, 11, 17)] )); test('Specifically imported function decorated by stdlib', buildTest( fileUsages, new vscode.Position(14, 6), - fileDefinitions, - new vscode.Range(21, 0, 27, 17) + isPythonAnalysisEngineTest() ? [fileUsages, fileDefinitions] : [fileDefinitions], + isPythonAnalysisEngineTest() + ? [new vscode.Range(1, 25, 1, 43), new vscode.Range(21, 4, 21, 22)] + : [new vscode.Range(21, 0, 27, 17)] )); test('Specifically imported function decorated by local decorator', buildTest( fileUsages, new vscode.Position(15, 6), - fileDefinitions, - new vscode.Range(14, 0, 18, 7) + isPythonAnalysisEngineTest() ? [fileUsages, fileDefinitions] : [fileDefinitions], + isPythonAnalysisEngineTest() + ? [new vscode.Range(1, 59, 1, 64), new vscode.Range(14, 4, 14, 9)] + : [new vscode.Range(14, 0, 18, 7)] )); }); From 574d072356a9eadee3832b52c0e12f3541ab9ab5 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 13 Jun 2018 15:45:59 -0700 Subject: [PATCH 338/433] Add metadata for sort imports code action and change title (#1952) Fixes #1951 --- src/client/extension.ts | 4 ++-- src/client/providers/codeActionsProvider.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/extension.ts b/src/client/extension.ts index 422d2fb0e2a6..4e49e23a0786 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -10,7 +10,7 @@ import { StopWatch } from './common/stopWatch'; const stopWatch = new StopWatch(); import { Container } from 'inversify'; -import { debug, Disposable, ExtensionContext, extensions, IndentAction, languages, Memento, OutputChannel, window } from 'vscode'; +import { CodeActionKind, debug, Disposable, ExtensionContext, extensions, IndentAction, languages, Memento, OutputChannel, window } from 'vscode'; import { registerTypes as activationRegisterTypes } from './activation/serviceRegistry'; import { IExtensionActivationService } from './activation/types'; import { IWorkspaceService } from './common/application/types'; @@ -144,7 +144,7 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(new ReplProvider(serviceContainer)); context.subscriptions.push(new TerminalProvider(serviceContainer)); - context.subscriptions.push(languages.registerCodeActionsProvider(PYTHON, new PythonCodeActionProvider())); + context.subscriptions.push(languages.registerCodeActionsProvider(PYTHON, new PythonCodeActionProvider(), { providedCodeActionKinds: [CodeActionKind.SourceOrganizeImports] })); type ConfigurationProvider = BaseConfigurationProvider; serviceContainer.getAll(IDebugConfigurationProvider).forEach(debugConfig => { diff --git a/src/client/providers/codeActionsProvider.ts b/src/client/providers/codeActionsProvider.ts index 5bc2975f5670..cd113223b819 100644 --- a/src/client/providers/codeActionsProvider.ts +++ b/src/client/providers/codeActionsProvider.ts @@ -8,7 +8,7 @@ import * as vscode from 'vscode'; export class PythonCodeActionProvider implements vscode.CodeActionProvider { public provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken): vscode.ProviderResult { const sortImports = new vscode.CodeAction( - 'Sort imports on save', + 'Sort imports', vscode.CodeActionKind.SourceOrganizeImports ); sortImports.command = { From ca7426ebb7c9a77580dbd86b9ba25077ab80d47c Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 13 Jun 2018 16:44:25 -0700 Subject: [PATCH 339/433] Fix django and flask debugger tests when using the experimental debugger (#1938) * Fix django and flask debugger tests when using the experimental debugger * Wait for resources to load instead of adding an arbitrary wait * Remove wait-on and wait for http server manually --- news/3 Code Health/1407.md | 1 + package-lock.json | 8 ++----- src/test/debugger/web.framework.test.ts | 31 ++++++++++++++++++++++--- 3 files changed, 31 insertions(+), 9 deletions(-) create mode 100644 news/3 Code Health/1407.md diff --git a/news/3 Code Health/1407.md b/news/3 Code Health/1407.md new file mode 100644 index 000000000000..d61ba644abe2 --- /dev/null +++ b/news/3 Code Health/1407.md @@ -0,0 +1 @@ +Fix django and flask debugger tests when using the `experimental` debugger. diff --git a/package-lock.json b/package-lock.json index c5210b5d241a..060f25faeccc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2704,14 +2704,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2731,8 +2729,7 @@ "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", @@ -2880,7 +2877,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } diff --git a/src/test/debugger/web.framework.test.ts b/src/test/debugger/web.framework.test.ts index bb3be26a80b6..1528e6f2ad5f 100644 --- a/src/test/debugger/web.framework.test.ts +++ b/src/test/debugger/web.framework.test.ts @@ -11,6 +11,7 @@ import * as path from 'path'; import { DebugClient } from 'vscode-debugadapter-testsupport'; import { EXTENSION_ROOT_DIR } from '../../client/common/constants'; import { noop } from '../../client/common/core.utils'; +import { StopWatch } from '../../client/common/stopWatch'; import { DebugOptions, LaunchRequestArguments } from '../../client/debugger/Common/Contracts'; import { PYTHON_PATH, sleep } from '../common'; import { IS_MULTI_ROOT_TEST, TEST_DEBUGGER } from '../initialize'; @@ -92,6 +93,27 @@ suite(`Django and Flask Debugging: ${debuggerType}`, () => { return { options, port }; } + async function waitForWebServerToStart(url: string): Promise { + return new Promise((resolve, reject) => { + let maxTries = 50; + const timeout = 10_000; + const start = new StopWatch(); + const fn = () => { + makeHttpRequest(url) + .then(resolve) + .catch(ex => { + maxTries -= 1; + if (maxTries === 0 || start.elapsedTime >= timeout) { + reject(ex); + } else { + setTimeout(fn, 100); + } + }); + }; + fn(); + }); + } + async function testTemplateDebugging(launchArgs: LaunchRequestArguments, port: number, viewFile: string, viewLine: number, templateFile: string, templateLine: number) { await Promise.all([ debugClient.configurationSequence(), @@ -101,18 +123,21 @@ suite(`Django and Flask Debugging: ${debuggerType}`, () => { debugClient.waitForEvent('thread') ]); - const httpResult = await makeHttpRequest(`http://localhost:${port}`); + const url = `http://localhost:${port}`; + + await waitForWebServerToStart(url); + const httpResult = await makeHttpRequest(url); expect(httpResult).to.contain('Hello this_is_a_value_from_server'); expect(httpResult).to.contain('Hello this_is_another_value_from_server'); - await hitHttpBreakpoint(debugClient, `http://localhost:${port}`, viewFile, viewLine); + await hitHttpBreakpoint(debugClient, url, viewFile, viewLine); await continueDebugging(debugClient); await debugClient.setBreakpointsRequest({ breakpoints: [], lines: [], source: { path: viewFile } }); // Template debugging. - const [stackTrace, htmlResultPromise] = await hitHttpBreakpoint(debugClient, `http://localhost:${port}`, templateFile, templateLine); + const [stackTrace, htmlResultPromise] = await hitHttpBreakpoint(debugClient, url, templateFile, templateLine); // Wait for breakpoint to hit const expectedVariables: ExpectedVariable[] = [ From fca30ebacc1c4098afe116c4e601a6536cf43f3d Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 13 Jun 2018 17:37:51 -0700 Subject: [PATCH 340/433] Fix rename refactor unit tests (#1955) Fixes #1953 --- news/3 Code Health/1953.md | 1 + src/client/common/extensions.ts | 6 +++--- src/test/refactor/rename.test.ts | 11 +++++++---- 3 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 news/3 Code Health/1953.md diff --git a/news/3 Code Health/1953.md b/news/3 Code Health/1953.md new file mode 100644 index 000000000000..4749e1048d14 --- /dev/null +++ b/news/3 Code Health/1953.md @@ -0,0 +1 @@ +Fix rename refactoring unit tests. diff --git a/src/client/common/extensions.ts b/src/client/common/extensions.ts index 96e329532df9..ea4ec4d1bf3a 100644 --- a/src/client/common/extensions.ts +++ b/src/client/common/extensions.ts @@ -15,7 +15,7 @@ declare interface String { * By default lines are trimmed and empty lines are removed. * @param {SplitLinesOptions=} splitOptions - Options used for splitting the string. */ - splitLines(splitOptions?: { trim: boolean, removeEmptyEntries?: boolean }): string[]; + splitLines(splitOptions?: { trim: boolean; removeEmptyEntries?: boolean }): string[]; /** * Appropriately formats a string so it can be used as an argument for a command in a shell. * E.g. if an argument contains a space, then it will be enclosed within double quotes. @@ -33,10 +33,10 @@ declare interface String { * By default lines are trimmed and empty lines are removed. * @param {SplitLinesOptions=} splitOptions - Options used for splitting the string. */ -String.prototype.splitLines = function (this: string, splitOptions: { trim: boolean, removeEmptyEntries: boolean } = { removeEmptyEntries: true, trim: true }): string[] { +String.prototype.splitLines = function (this: string, splitOptions: { trim: boolean; removeEmptyEntries: boolean } = { removeEmptyEntries: true, trim: true }): string[] { let lines = this.split(/\r?\n/g); if (splitOptions && splitOptions.trim) { - lines = lines.filter(line => line.trim()); + lines = lines.map(line => line.trim()); } if (splitOptions && splitOptions.removeEmptyEntries) { lines = lines.filter(line => line.length > 0); diff --git a/src/test/refactor/rename.test.ts b/src/test/refactor/rename.test.ts index cbc4f641ddf7..03bffd245899 100644 --- a/src/test/refactor/rename.test.ts +++ b/src/test/refactor/rename.test.ts @@ -9,6 +9,7 @@ import * as path from 'path'; import * as typeMoq from 'typemoq'; import { Range, TextEditorCursorStyle, TextEditorLineNumbersStyle, TextEditorOptions, window, workspace } from 'vscode'; import { EXTENSION_ROOT_DIR } from '../../client/common/constants'; +import '../../client/common/extensions'; import { BufferDecoder } from '../../client/common/process/decoder'; import { ProcessService } from '../../client/common/process/proc'; import { PythonExecutionFactory } from '../../client/common/process/pythonExecutionFactory'; @@ -47,7 +48,8 @@ suite('Refactor Rename', () => { test('Rename function in source without a trailing empty line', async () => { const sourceFile = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'refactoring', 'source folder', 'without empty line.py'); - const expectedDiff = `--- a/${path.basename(sourceFile)}${EOL}+++ b/${path.basename(sourceFile)}${EOL}@@ -1,8 +1,8 @@${EOL} import os${EOL} ${EOL}-def one():${EOL}+def three():${EOL} return True${EOL} ${EOL} def two():${EOL}- if one():${EOL}- print(\"A\" + one())${EOL}+ if three():${EOL}+ print(\"A\" + three())${EOL}`; + const expectedDiff = `--- a/${path.basename(sourceFile)}${EOL}+++ b/${path.basename(sourceFile)}${EOL}@@ -1,8 +1,8 @@${EOL} import os${EOL} ${EOL}-def one():${EOL}+def three():${EOL} return True${EOL} ${EOL} def two():${EOL}- if one():${EOL}- print(\"A\" + one())${EOL}+ if three():${EOL}+ print(\"A\" + three())${EOL}` + .splitLines({ removeEmptyEntries: false, trim: false }); const proxy = new RefactorProxy(EXTENSION_ROOT_DIR, pythonSettings.object, path.dirname(sourceFile), serviceContainer.object); const textDocument = await workspace.openTextDocument(sourceFile); @@ -55,11 +57,12 @@ suite('Refactor Rename', () => { const response = await proxy.rename(textDocument, 'three', sourceFile, new Range(7, 20, 7, 23), options); expect(response.results).to.be.lengthOf(1); - expect(response.results[0].diff).to.be.equal(expectedDiff); + expect(response.results[0].diff.splitLines({ removeEmptyEntries: false, trim: false })).to.be.deep.equal(expectedDiff); }); test('Rename function in source with a trailing empty line', async () => { const sourceFile = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'refactoring', 'source folder', 'with empty line.py'); - const expectedDiff = `--- a/${path.basename(sourceFile)}${EOL}+++ b/${path.basename(sourceFile)}${EOL}@@ -1,8 +1,8 @@${EOL} import os${EOL} ${EOL}-def one():${EOL}+def three():${EOL} return True${EOL} ${EOL} def two():${EOL}- if one():${EOL}- print(\"A\" + one())${EOL}+ if three():${EOL}+ print(\"A\" + three())${EOL}`; + const expectedDiff = `--- a/${path.basename(sourceFile)}${EOL}+++ b/${path.basename(sourceFile)}${EOL}@@ -1,8 +1,8 @@${EOL} import os${EOL} ${EOL}-def one():${EOL}+def three():${EOL} return True${EOL} ${EOL} def two():${EOL}- if one():${EOL}- print(\"A\" + one())${EOL}+ if three():${EOL}+ print(\"A\" + three())${EOL}` + .splitLines({ removeEmptyEntries: false, trim: false }); const proxy = new RefactorProxy(EXTENSION_ROOT_DIR, pythonSettings.object, path.dirname(sourceFile), serviceContainer.object); const textDocument = await workspace.openTextDocument(sourceFile); @@ -67,6 +70,6 @@ suite('Refactor Rename', () => { const response = await proxy.rename(textDocument, 'three', sourceFile, new Range(7, 20, 7, 23), options); expect(response.results).to.be.lengthOf(1); - expect(response.results[0].diff).to.be.equal(expectedDiff); + expect(response.results[0].diff.splitLines({ removeEmptyEntries: false, trim: false })).to.be.deep.equal(expectedDiff); }); }); From a42ce569d5697ed7d38c218f1a24ce542b02c7f6 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 13 Jun 2018 17:38:44 -0700 Subject: [PATCH 341/433] Fix failing test on Mac when validating the path of a python interperter (#1958) --- news/3 Code Health/1957.md | 1 + .../process/pythonProc.simple.multiroot.test.ts | 14 ++++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 news/3 Code Health/1957.md diff --git a/news/3 Code Health/1957.md b/news/3 Code Health/1957.md new file mode 100644 index 000000000000..9c638c49be95 --- /dev/null +++ b/news/3 Code Health/1957.md @@ -0,0 +1 @@ +Fix failing test on Mac when validating the path of a python interperter. diff --git a/src/test/common/process/pythonProc.simple.multiroot.test.ts b/src/test/common/process/pythonProc.simple.multiroot.test.ts index c0c914ec7982..b4568e4db817 100644 --- a/src/test/common/process/pythonProc.simple.multiroot.test.ts +++ b/src/test/common/process/pythonProc.simple.multiroot.test.ts @@ -4,6 +4,7 @@ import { expect, use } from 'chai'; import * as chaiAsPromised from 'chai-as-promised'; import { execFile } from 'child_process'; +import * as fs from 'fs-extra'; import { Container } from 'inversify'; import { EOL } from 'os'; import * as path from 'path'; @@ -114,11 +115,16 @@ suite('PythonExecutableService', () => { test('Ensure correct path to executable is returned', async () => { const pythonPath = PythonSettings.getInstance(workspace4Path).pythonPath; - const expectedExecutablePath = await new Promise(resolve => { - execFile(pythonPath, ['-c', 'import sys;print(sys.executable)'], (_error, stdout, _stdErr) => { - resolve(stdout.trim()); + let expectedExecutablePath: string; + if (await fs.pathExists(pythonPath)) { + expectedExecutablePath = pythonPath; + } else { + expectedExecutablePath = await new Promise(resolve => { + execFile(pythonPath, ['-c', 'import sys;print(sys.executable)'], (_error, stdout, _stdErr) => { + resolve(stdout.trim()); + }); }); - }); + } const pythonExecService = await pythonExecFactory.create({ resource: workspace4PyFile }); const executablePath = await pythonExecService.getExecutablePath(); expect(executablePath).to.equal(expectedExecutablePath, 'Executable paths are not the same'); From fa8af4560cd94f607653a421b2d159fa5da3bc75 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 13 Jun 2018 18:19:39 -0700 Subject: [PATCH 342/433] Ensure all CI tests (except for debugger) are no longer allowed to fail (#1942) --- .appveyor.yml | 18 ------------------ .travis.yml | 12 ------------ news/3 Code Health/1614.md | 1 + 3 files changed, 1 insertion(+), 30 deletions(-) create mode 100644 news/3 Code Health/1614.md diff --git a/.appveyor.yml b/.appveyor.yml index 88aa597ac85e..08045af6336d 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -48,24 +48,6 @@ matrix: nodejs_version: "8.9.1" APPVEYOR: "true" DEBUGGER_TEST_RELEASE: "true" - - PYTHON: "C:\\Python36" - PYTHON_VERSION: "3.6.3" - PYTHON_ARCH: "32" - nodejs_version: "8.9.1" - APPVEYOR: "true" - SINGLE_WORKSPACE_TEST: "true" - - PYTHON: "C:\\Python36" - PYTHON_VERSION: "3.6.3" - PYTHON_ARCH: "32" - nodejs_version: "8.9.1" - APPVEYOR: "true" - MULTIROOT_WORKSPACE_TEST: "true" - - PYTHON: "C:\\Python36" - PYTHON_VERSION: "3.6.3" - PYTHON_ARCH: "32" - nodejs_version: "8.9.1" - APPVEYOR: "true" - ANALYSIS_TEST: "true" init: - "ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH%" diff --git a/.travis.yml b/.travis.yml index f24850854c15..6c50843fbcaa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,24 +37,12 @@ matrix: - os: linux python: "2.7" env: DEBUGGER_TEST_RELEASE=true - - os: linux - python: "2.7" - env: SINGLE_WORKSPACE_TEST=true - - os: linux - python: "2.7" - env: MULTIROOT_WORKSPACE_TEST=true - os: linux python: "3.6-dev" env: DEBUGGER_TEST=true - os: linux python: "3.6-dev" env: DEBUGGER_TEST_RELEASE=true - - os: linux - python: "3.6-dev" - env: SINGLE_WORKSPACE_TEST=true - - os: linux - python: "3.6-dev" - env: MULTIROOT_WORKSPACE_TEST=true before_install: | if [ $TRAVIS_OS_NAME == "linux" ]; then export CXX="g++-4.9" CC="gcc-4.9" DISPLAY=:99.0; diff --git a/news/3 Code Health/1614.md b/news/3 Code Health/1614.md new file mode 100644 index 000000000000..6a5ad111df25 --- /dev/null +++ b/news/3 Code Health/1614.md @@ -0,0 +1 @@ +Ensure all CI tests (except for debugger) are no longer allowed to fail. From c61bdf3e76c2460147cb5d044c7e60894ed9cc7c Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 14 Jun 2018 08:51:58 -0700 Subject: [PATCH 343/433] Log env info when the existence of pipenv cannot be determined (#1962) Fixes #1338 --- news/3 Code Health/1338.md | 1 + .../locators/services/pipEnvService.ts | 11 ++++++++++- ...ice.test.ts => pipEnvService.unit.test.ts} | 19 +++++++++++-------- 3 files changed, 22 insertions(+), 9 deletions(-) create mode 100644 news/3 Code Health/1338.md rename src/test/interpreters/{pipEnvService.test.ts => pipEnvService.unit.test.ts} (89%) diff --git a/news/3 Code Health/1338.md b/news/3 Code Health/1338.md new file mode 100644 index 000000000000..9846aecaa276 --- /dev/null +++ b/news/3 Code Health/1338.md @@ -0,0 +1 @@ +Log relevant environment information when the existence of `pipenv` cannot be determined. diff --git a/src/client/interpreter/locators/services/pipEnvService.ts b/src/client/interpreter/locators/services/pipEnvService.ts index e8d69601d053..708cc544a45a 100644 --- a/src/client/interpreter/locators/services/pipEnvService.ts +++ b/src/client/interpreter/locators/services/pipEnvService.ts @@ -5,7 +5,7 @@ import { inject, injectable } from 'inversify'; import * as path from 'path'; import { Uri } from 'vscode'; import { IApplicationShell, IWorkspaceService } from '../../../common/application/types'; -import { IFileSystem } from '../../../common/platform/types'; +import { IFileSystem, IPlatformService } from '../../../common/platform/types'; import { IProcessServiceFactory } from '../../../common/process/types'; import { ICurrentProcess, ILogger } from '../../../common/types'; import { IServiceContainer } from '../../../ioc/types'; @@ -126,7 +126,16 @@ export class PipEnvService extends CacheableLocatorService implements IPipEnvSer } // tslint:disable-next-line:no-empty } catch (error) { + const platformService = this.serviceContainer.get(IPlatformService); + const currentProc = this.serviceContainer.get(ICurrentProcess); + const enviromentVariableValues = { + LC_ALL: currentProc.env.LC_ALL, + LANG: currentProc.env.LANG + }; + enviromentVariableValues[platformService.pathVariableName] = currentProc.env[platformService.pathVariableName]; + this.logger.logWarning('Error in invoking PipEnv', error); + this.logger.logWarning(`Relevant Environment Variables ${JSON.stringify(enviromentVariableValues, undefined, 4)}`); const errorMessage = error.message || error; const appShell = this.serviceContainer.get(IApplicationShell); appShell.showWarningMessage(`Workspace contains pipfile but attempt to run 'pipenv --venv' failed with '${errorMessage}'. Make sure pipenv is on the PATH.`); diff --git a/src/test/interpreters/pipEnvService.test.ts b/src/test/interpreters/pipEnvService.unit.test.ts similarity index 89% rename from src/test/interpreters/pipEnvService.test.ts rename to src/test/interpreters/pipEnvService.unit.test.ts index fdd6a1c6b9e0..aba7709b35be 100644 --- a/src/test/interpreters/pipEnvService.test.ts +++ b/src/test/interpreters/pipEnvService.unit.test.ts @@ -11,7 +11,7 @@ import * as TypeMoq from 'typemoq'; import { Uri, WorkspaceFolder } from 'vscode'; import { IApplicationShell, IWorkspaceService } from '../../client/common/application/types'; import { EnumEx } from '../../client/common/enumUtils'; -import { IFileSystem } from '../../client/common/platform/types'; +import { IFileSystem, IPlatformService } from '../../client/common/platform/types'; import { IProcessService, IProcessServiceFactory } from '../../client/common/process/types'; import { ICurrentProcess, ILogger, IPersistentState, IPersistentStateFactory } from '../../client/common/types'; import { IEnvironmentVariablesProvider } from '../../client/common/variables/types'; @@ -40,6 +40,7 @@ suite('Interpreters - PipEnv', () => { let envVarsProvider: TypeMoq.IMock; let procServiceFactory: TypeMoq.IMock; let logger: TypeMoq.IMock; + let platformService: TypeMoq.IMock; setup(() => { serviceContainer = TypeMoq.Mock.ofType(); const workspaceService = TypeMoq.Mock.ofType(); @@ -52,6 +53,7 @@ suite('Interpreters - PipEnv', () => { envVarsProvider = TypeMoq.Mock.ofType(); procServiceFactory = TypeMoq.Mock.ofType(); logger = TypeMoq.Mock.ofType(); + platformService = TypeMoq.Mock.ofType(); processService.setup((x: any) => x.then).returns(() => undefined); procServiceFactory.setup(p => p.create(TypeMoq.It.isAny())).returns(() => Promise.resolve(processService.object)); @@ -76,6 +78,7 @@ suite('Interpreters - PipEnv', () => { serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPersistentStateFactory))).returns(() => persistentStateFactory.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IEnvironmentVariablesProvider))).returns(() => envVarsProvider.object); serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ILogger))).returns(() => logger.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPlatformService))).returns(() => platformService.object); pipEnvService = new PipEnvService(serviceContainer.object); }); @@ -84,7 +87,7 @@ suite('Interpreters - PipEnv', () => { const environments = pipEnvService.getInterpreters(resource); expect(environments).to.be.eventually.deep.equal([]); }); - test(`Should return an empty list if there is a \'PipFile\'${testSuffix}`, async () => { + test(`Should return an empty list if there is no \'PipFile\'${testSuffix}`, async () => { const env = {}; envVarsProvider.setup(e => e.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})).verifiable(TypeMoq.Times.once()); currentProcess.setup(c => c.env).returns(() => env); @@ -97,10 +100,10 @@ suite('Interpreters - PipEnv', () => { test(`Should display warning message if there is a \'PipFile\' but \'pipenv --venv\' failes ${testSuffix}`, async () => { const env = {}; currentProcess.setup(c => c.env).returns(() => env); - processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.reject('')); + processService.setup(p => p.exec(TypeMoq.It.isValue('pipenv'), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.reject('')); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(true)); appShell.setup(a => a.showWarningMessage(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('')).verifiable(TypeMoq.Times.once()); - logger.setup(l => l.logWarning(TypeMoq.It.isAny(), TypeMoq.It.isAny())).verifiable(TypeMoq.Times.once()); + logger.setup(l => l.logWarning(TypeMoq.It.isAny(), TypeMoq.It.isAny())).verifiable(TypeMoq.Times.exactly(2)); const environments = await pipEnvService.getInterpreters(resource); expect(environments).to.be.deep.equal([]); @@ -110,10 +113,10 @@ suite('Interpreters - PipEnv', () => { test(`Should display warning message if there is a \'PipFile\' but \'pipenv --venv\' failes with stderr ${testSuffix}`, async () => { const env = {}; currentProcess.setup(c => c.env).returns(() => env); - processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stderr: 'PipEnv Failed', stdout: '' })); + processService.setup(p => p.exec(TypeMoq.It.isValue('pipenv'), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stderr: 'PipEnv Failed', stdout: '' })); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(true)); appShell.setup(a => a.showWarningMessage(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve('')).verifiable(TypeMoq.Times.once()); - logger.setup(l => l.logWarning(TypeMoq.It.isAny(), TypeMoq.It.isAny())).verifiable(TypeMoq.Times.once()); + logger.setup(l => l.logWarning(TypeMoq.It.isAny(), TypeMoq.It.isAny())).verifiable(TypeMoq.Times.exactly(2)); const environments = await pipEnvService.getInterpreters(resource); expect(environments).to.be.deep.equal([]); @@ -125,7 +128,7 @@ suite('Interpreters - PipEnv', () => { const pythonPath = 'one'; envVarsProvider.setup(e => e.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})).verifiable(TypeMoq.Times.once()); currentProcess.setup(c => c.env).returns(() => env); - processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: pythonPath })); + processService.setup(p => p.exec(TypeMoq.It.isValue('pipenv'), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: pythonPath })); interpreterHelper.setup(v => v.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: 'xyz' })); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(true)).verifiable(); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(pythonPath))).returns(() => Promise.resolve(true)).verifiable(); @@ -142,7 +145,7 @@ suite('Interpreters - PipEnv', () => { const pythonPath = 'one'; envVarsProvider.setup(e => e.getEnvironmentVariables(TypeMoq.It.isAny())).returns(() => Promise.resolve({})).verifiable(TypeMoq.Times.once()); currentProcess.setup(c => c.env).returns(() => env); - processService.setup(p => p.exec(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: pythonPath })); + processService.setup(p => p.exec(TypeMoq.It.isValue('pipenv'), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({ stdout: pythonPath })); interpreterHelper.setup(v => v.getInterpreterInformation(TypeMoq.It.isAny())).returns(() => Promise.resolve({ version: 'xyz' })); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(path.join(rootWorkspace, 'Pipfile')))).returns(() => Promise.resolve(false)).verifiable(TypeMoq.Times.never()); fileSystem.setup(fs => fs.fileExists(TypeMoq.It.isValue(path.join(rootWorkspace, envPipFile)))).returns(() => Promise.resolve(true)).verifiable(TypeMoq.Times.once()); From 666786844314285aef86ba58e92480bf7aa0985a Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 14 Jun 2018 09:01:46 -0700 Subject: [PATCH 344/433] Change keyboard shortcut for execSelectionInTerminal to Shit+Enter (#1961) * Change keyboard shortcut for execSelectionInTerminal to Shit+Enter * Update condition for context menu --- news/2 Fixes/1875.md | 1 + package.json | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 news/2 Fixes/1875.md diff --git a/news/2 Fixes/1875.md b/news/2 Fixes/1875.md new file mode 100644 index 000000000000..d687dcf63a14 --- /dev/null +++ b/news/2 Fixes/1875.md @@ -0,0 +1 @@ +Change keyboard shortcut for `Run Selection/Line in Python Terminal` to `Shit+Enter`. diff --git a/package.json b/package.json index 58851c4dcdc6..b3d56f1cd7ea 100644 --- a/package.json +++ b/package.json @@ -98,8 +98,8 @@ "keybindings": [ { "command": "python.execSelectionInTerminal", - "key": "ctrl+enter", - "when": "editorFocus && editorHasSelection && editorLangId == python" + "key": "shift+enter", + "when": "editorFocus && editorLangId == python" } ], "commands": [ @@ -247,7 +247,7 @@ { "command": "python.execSelectionInTerminal", "group": "Python", - "when": "editorHasSelection && editorLangId == python" + "when": "editorFocus && editorLangId == python" }, { "command": "python.execSelectionInDjangoShell", From 2d99e17486826203b80e22d33eb1cd0e640f4c4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my?= Date: Thu, 14 Jun 2018 21:41:39 +0200 Subject: [PATCH 345/433] French language translation (#1394) Closes #1959 --- package.nls.fr.json | 49 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 package.nls.fr.json diff --git a/package.nls.fr.json b/package.nls.fr.json new file mode 100644 index 000000000000..a49129e54c88 --- /dev/null +++ b/package.nls.fr.json @@ -0,0 +1,49 @@ +{ + "python.command.python.sortImports.title": "Trier les imports", + "python.command.python.startREPL.title": "Démarrer la console interactive", + "python.command.python.createTerminal.title": "Créer un terminal", + "python.command.python.buildWorkspaceSymbols.title": "Construire les symboles de l'espace de travail", + "python.command.python.runtests.title": "Exécuter tous les tests unitaires", + "python.command.python.debugtests.title": "Déboguer tous les tests unitaires", + "python.command.python.execInTerminal.title": "Exécuter le script Python dans un terminal", + "python.command.python.setInterpreter.title": "Selectionner l'interpreteur", + "python.command.python.updateSparkLibrary.title": "Mettre à jour les librairies de l'espace de travail PySpark", + "python.command.python.refactorExtractVariable.title": "Extraire la variable", + "python.command.python.refactorExtractMethod.title": "Extraire la méthode", + "python.command.python.viewTestOutput.title": "Afficher la sortie des tests unitaires", + "python.command.python.selectAndRunTestMethod.title": "Exécuter la méthode de test unitaire ...", + "python.command.python.selectAndDebugTestMethod.title": "Déboguer la méthode de test unitaire ...", + "python.command.python.selectAndRunTestFile.title": "Exécuter le fichier de test unitaire ...", + "python.command.python.runCurrentTestFile.title": "Exécuter le fichier de test unitaire courant", + "python.command.python.runFailedTests.title": "Exécuter les derniers test unitaires échoués", + "python.command.python.execSelectionInTerminal.title": "Exécuter la ligne/sélection dans un terminal Python", + "python.command.python.execSelectionInDjangoShell.title": "Exécuter la ligne/sélection dans un shell Django", + "python.command.python.goToPythonObject.title": "Se rendre à l'objet Python", + "python.command.python.setLinter.title": "Selectionner le linter", + "python.command.python.enableLinting.title": "Activer le linting", + "python.command.python.runLinting.title": "Exécuter le linting", + "python.snippet.launch.standard.label": "Python : Fichier actuel", + "python.snippet.launch.standard.description": "Déboguer un programme Python avec la sortie standard", + "python.snippet.launch.pyspark.label": "Python : PySpark", + "python.snippet.launch.pyspark.description": "Déboguer PySpark", + "python.snippet.launch.module.label": "Python: Module", + "python.snippet.launch.module.description": "Déboguer un module Python", + "python.snippet.launch.terminal.label": "Python : Terminal (intégré)", + "python.snippet.launch.terminal.description": "Déboguer un programme Python avec la console intégrée", + "python.snippet.launch.externalTerminal.label": "Python : Terminal (externe)", + "python.snippet.launch.externalTerminal.description": "Déboguer un programme Python avec une console externe", + "python.snippet.launch.django.label": "Python : Django", + "python.snippet.launch.django.description": "Déboguer une application Django", + "python.snippet.launch.flask.label": "Python : Flask (0.11.x ou supérieur)", + "python.snippet.launch.flask.description": "Déboguer une application Flask", + "python.snippet.launch.flaskOld.label": "Python : Flask (0.10.x ou antérieur)", + "python.snippet.launch.flaskOld.description": "Déboguer une application Flask (0.10.x ou antérieur)", + "python.snippet.launch.pyramid.label": "Python : application Pyramid", + "python.snippet.launch.pyramid.description": "Déboguer une application Pyramid", + "python.snippet.launch.watson.label": "Python: Application Watson", + "python.snippet.launch.watson.description": "Déboguer une Application Watson", + "python.snippet.launch.attach.label": "Python: Attacher", + "python.snippet.launch.attach.description": "Attacher le débogueur pour un debugging distant", + "python.snippet.launch.scrapy.label": "Python : Scrapy", + "python.snippet.launch.scrapy.description": "Scrapy avec un terminal intégré" +} From 6d8fc8bd6d5d6257daedf3c06421cfabc80ba018 Mon Sep 17 00:00:00 2001 From: Laurent Mazuel Date: Thu, 14 Jun 2018 13:20:04 -0700 Subject: [PATCH 346/433] Debugging translation (#1969) --- package.nls.fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.nls.fr.json b/package.nls.fr.json index a49129e54c88..ea3dfd6dd7eb 100644 --- a/package.nls.fr.json +++ b/package.nls.fr.json @@ -43,7 +43,7 @@ "python.snippet.launch.watson.label": "Python: Application Watson", "python.snippet.launch.watson.description": "Déboguer une Application Watson", "python.snippet.launch.attach.label": "Python: Attacher", - "python.snippet.launch.attach.description": "Attacher le débogueur pour un debugging distant", + "python.snippet.launch.attach.description": "Attacher le débogueur pour un débogage distant", "python.snippet.launch.scrapy.label": "Python : Scrapy", "python.snippet.launch.scrapy.description": "Scrapy avec un terminal intégré" } From 9156f39f3da3e52390041626c75aa6b9a01eb760 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 15 Jun 2018 12:32:37 -0700 Subject: [PATCH 347/433] Display banner prompting user to fill a survey for Experimental Debugger (#1976) --- news/3 Code Health/1968.md | 1 + .../application/applicationEnvironment.ts | 27 ++++ src/client/common/application/debugService.ts | 5 +- src/client/common/application/types.ts | 86 +++++++--- src/client/common/net/browser.ts | 9 ++ src/client/common/serviceRegistry.ts | 8 +- src/client/common/types.ts | 5 + src/client/debugger/Common/Contracts.ts | 5 +- src/client/debugger/Common/constants.ts | 1 + src/client/debugger/banner.ts | 129 +++++++++++++++ src/client/debugger/serviceRegistry.ts | 11 +- src/client/debugger/types.ts | 10 ++ src/client/extension.ts | 6 +- src/client/unittests/common/debugLauncher.ts | 3 +- src/test/debugger/banner.unit.test.ts | 150 ++++++++++++++++++ 15 files changed, 425 insertions(+), 31 deletions(-) create mode 100644 news/3 Code Health/1968.md create mode 100644 src/client/common/application/applicationEnvironment.ts create mode 100644 src/client/debugger/banner.ts create mode 100644 src/test/debugger/banner.unit.test.ts diff --git a/news/3 Code Health/1968.md b/news/3 Code Health/1968.md new file mode 100644 index 000000000000..b4a14a27dde1 --- /dev/null +++ b/news/3 Code Health/1968.md @@ -0,0 +1 @@ +Display banner prompting user to complete a survey for the use of the `Experimental Debugger`. diff --git a/src/client/common/application/applicationEnvironment.ts b/src/client/common/application/applicationEnvironment.ts new file mode 100644 index 000000000000..95e2c4b2e4dc --- /dev/null +++ b/src/client/common/application/applicationEnvironment.ts @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { injectable } from 'inversify'; +import * as vscode from 'vscode'; +import { IApplicationEnvironment } from './types'; + +@injectable() +export class ApplicationEnvironment implements IApplicationEnvironment { + public get appName(): string { + return vscode.env.appName; + } + public get appRoot(): string { + return vscode.env.appRoot; + } + public get language(): string { + return vscode.env.language; + } + public get sessionId(): string { + return vscode.env.sessionId; + } + public get machineId(): string { + return vscode.env.machineId; + } +} diff --git a/src/client/common/application/debugService.ts b/src/client/common/application/debugService.ts index 13ac170a8499..975feee63f4e 100644 --- a/src/client/common/application/debugService.ts +++ b/src/client/common/application/debugService.ts @@ -4,11 +4,14 @@ 'use strict'; import { injectable } from 'inversify'; -import { debug, DebugConfiguration, WorkspaceFolder } from 'vscode'; +import { debug, DebugConfiguration, DebugSession, Event, WorkspaceFolder } from 'vscode'; import { IDebugService } from './types'; @injectable() export class DebugService implements IDebugService { + public get onDidStartDebugSession(): Event{ + return debug.onDidStartDebugSession; + } public startDebugging(folder: WorkspaceFolder | undefined, nameOrConfiguration: string | DebugConfiguration): Thenable { return debug.startDebugging(folder, nameOrConfiguration); } diff --git a/src/client/common/application/types.ts b/src/client/common/application/types.ts index 9ad34b5d8f4e..f7dab889e065 100644 --- a/src/client/common/application/types.ts +++ b/src/client/common/application/types.ts @@ -4,12 +4,13 @@ // tslint:disable:no-any unified-signatures -import * as vscode from 'vscode'; -import { CancellationToken, ConfigurationChangeEvent, Disposable, Event, FileSystemWatcher, GlobPattern, TextDocument, TextDocumentShowOptions, WorkspaceConfiguration, WorkspaceFolderPickOptions } from 'vscode'; -import { TextEditor, TextEditorEdit, TextEditorOptionsChangeEvent, TextEditorSelectionChangeEvent, TextEditorViewColumnChangeEvent } from 'vscode'; -import { StatusBarAlignment, StatusBarItem } from 'vscode'; -import { Uri, ViewColumn, WorkspaceFolder, WorkspaceFoldersChangeEvent } from 'vscode'; -import { Terminal, TerminalOptions } from 'vscode'; +import { + CancellationToken, ConfigurationChangeEvent, DebugConfiguration, DebugSession, Disposable, Event, FileSystemWatcher, GlobPattern, InputBoxOptions, MessageItem, + MessageOptions, OpenDialogOptions, QuickPickItem, QuickPickOptions, SaveDialogOptions, + StatusBarAlignment, StatusBarItem, Terminal, TerminalOptions, TextDocument, TextDocumentShowOptions, TextEditor, + TextEditorEdit, TextEditorOptionsChangeEvent, TextEditorSelectionChangeEvent, TextEditorViewColumnChangeEvent, Uri, ViewColumn, WorkspaceConfiguration, WorkspaceFolder, + WorkspaceFolderPickOptions, WorkspaceFoldersChangeEvent +} from 'vscode'; export const IApplicationShell = Symbol('IApplicationShell'); export interface IApplicationShell { @@ -24,7 +25,7 @@ export interface IApplicationShell { * @param items A set of items that will be rendered as actions in the message. * @return A thenable that resolves to the selected item or `undefined` when being dismissed. */ - showInformationMessage(message: string, options: vscode.MessageOptions, ...items: string[]): Thenable; + showInformationMessage(message: string, options: MessageOptions, ...items: string[]): Thenable; /** * Show an information message. @@ -35,7 +36,7 @@ export interface IApplicationShell { * @param items A set of items that will be rendered as actions in the message. * @return A thenable that resolves to the selected item or `undefined` when being dismissed. */ - showInformationMessage(message: string, ...items: T[]): Thenable; + showInformationMessage(message: string, ...items: T[]): Thenable; /** * Show an information message. @@ -47,7 +48,7 @@ export interface IApplicationShell { * @param items A set of items that will be rendered as actions in the message. * @return A thenable that resolves to the selected item or `undefined` when being dismissed. */ - showInformationMessage(message: string, options: vscode.MessageOptions, ...items: T[]): Thenable; + showInformationMessage(message: string, options: MessageOptions, ...items: T[]): Thenable; /** * Show a warning message. @@ -70,7 +71,7 @@ export interface IApplicationShell { * @param items A set of items that will be rendered as actions in the message. * @return A thenable that resolves to the selected item or `undefined` when being dismissed. */ - showWarningMessage(message: string, options: vscode.MessageOptions, ...items: string[]): Thenable; + showWarningMessage(message: string, options: MessageOptions, ...items: string[]): Thenable; /** * Show a warning message. @@ -81,7 +82,7 @@ export interface IApplicationShell { * @param items A set of items that will be rendered as actions in the message. * @return A thenable that resolves to the selected item or `undefined` when being dismissed. */ - showWarningMessage(message: string, ...items: T[]): Thenable; + showWarningMessage(message: string, ...items: T[]): Thenable; /** * Show a warning message. @@ -93,7 +94,7 @@ export interface IApplicationShell { * @param items A set of items that will be rendered as actions in the message. * @return A thenable that resolves to the selected item or `undefined` when being dismissed. */ - showWarningMessage(message: string, options: vscode.MessageOptions, ...items: T[]): Thenable; + showWarningMessage(message: string, options: MessageOptions, ...items: T[]): Thenable; /** * Show an error message. @@ -116,7 +117,7 @@ export interface IApplicationShell { * @param items A set of items that will be rendered as actions in the message. * @return A thenable that resolves to the selected item or `undefined` when being dismissed. */ - showErrorMessage(message: string, options: vscode.MessageOptions, ...items: string[]): Thenable; + showErrorMessage(message: string, options: MessageOptions, ...items: string[]): Thenable; /** * Show an error message. @@ -127,7 +128,7 @@ export interface IApplicationShell { * @param items A set of items that will be rendered as actions in the message. * @return A thenable that resolves to the selected item or `undefined` when being dismissed. */ - showErrorMessage(message: string, ...items: T[]): Thenable; + showErrorMessage(message: string, ...items: T[]): Thenable; /** * Show an error message. @@ -139,7 +140,7 @@ export interface IApplicationShell { * @param items A set of items that will be rendered as actions in the message. * @return A thenable that resolves to the selected item or `undefined` when being dismissed. */ - showErrorMessage(message: string, options: vscode.MessageOptions, ...items: T[]): Thenable; + showErrorMessage(message: string, options: MessageOptions, ...items: T[]): Thenable; /** * Shows a selection list. @@ -149,7 +150,7 @@ export interface IApplicationShell { * @param token A token that can be used to signal cancellation. * @return A promise that resolves to the selection or `undefined`. */ - showQuickPick(items: string[] | Thenable, options?: vscode.QuickPickOptions, token?: vscode.CancellationToken): Thenable; + showQuickPick(items: string[] | Thenable, options?: QuickPickOptions, token?: CancellationToken): Thenable; /** * Shows a selection list. @@ -159,7 +160,7 @@ export interface IApplicationShell { * @param token A token that can be used to signal cancellation. * @return A promise that resolves to the selected item or `undefined`. */ - showQuickPick(items: T[] | Thenable, options?: vscode.QuickPickOptions, token?: vscode.CancellationToken): Thenable; + showQuickPick(items: T[] | Thenable, options?: QuickPickOptions, token?: CancellationToken): Thenable; /** * Shows a file open dialog to the user which allows to select a file @@ -168,7 +169,7 @@ export interface IApplicationShell { * @param options Options that control the dialog. * @returns A promise that resolves to the selected resources or `undefined`. */ - showOpenDialog(options: vscode.OpenDialogOptions): Thenable; + showOpenDialog(options: OpenDialogOptions): Thenable; /** * Shows a file save dialog to the user which allows to select a file @@ -177,7 +178,7 @@ export interface IApplicationShell { * @param options Options that control the dialog. * @returns A promise that resolves to the selected resource or `undefined`. */ - showSaveDialog(options: vscode.SaveDialogOptions): Thenable; + showSaveDialog(options: SaveDialogOptions): Thenable; /** * Opens an input box to ask the user for input. @@ -190,7 +191,7 @@ export interface IApplicationShell { * @param token A token that can be used to signal cancellation. * @return A promise that resolves to a string the user provided or to `undefined` in case of dismissal. */ - showInputBox(options?: vscode.InputBoxOptions, token?: vscode.CancellationToken): Thenable; + showInputBox(options?: InputBoxOptions, token?: CancellationToken): Thenable; /** * Opens URL in a default browser. @@ -534,6 +535,10 @@ export interface ITerminalManager { export const IDebugService = Symbol('IDebugManager'); export interface IDebugService { + /** + * An [event](#Event) which fires when a new [debug session](#DebugSession) has been started. + */ + onDidStartDebugSession: Event; /** * Start debugging by using either a named launch or named compound configuration, * or by directly passing a [DebugConfiguration](#DebugConfiguration). @@ -544,5 +549,44 @@ export interface IDebugService { * @param nameOrConfiguration Either the name of a debug or compound configuration or a [DebugConfiguration](#DebugConfiguration) object. * @return A thenable that resolves when debugging could be successfully started. */ - startDebugging(folder: WorkspaceFolder | undefined, nameOrConfiguration: string | vscode.DebugConfiguration): Thenable; + startDebugging(folder: WorkspaceFolder | undefined, nameOrConfiguration: string | DebugConfiguration): Thenable; +} + +export const IApplicationEnvironment = Symbol('IApplicationEnvironment'); +export interface IApplicationEnvironment { + /** + * The application name of the editor, like 'VS Code'. + * + * @readonly + */ + appName: string; + + /** + * The application root folder from which the editor is running. + * + * @readonly + */ + appRoot: string; + + /** + * Represents the preferred user-language, like `de-CH`, `fr`, or `en-US`. + * + * @readonly + */ + language: string; + + /** + * A unique identifier for the computer. + * + * @readonly + */ + machineId: string; + + /** + * A unique identifier for the current session. + * Changes each time the editor is started. + * + * @readonly + */ + sessionId: string; } diff --git a/src/client/common/net/browser.ts b/src/client/common/net/browser.ts index 528f2c146f2b..cb458b7c3735 100644 --- a/src/client/common/net/browser.ts +++ b/src/client/common/net/browser.ts @@ -2,7 +2,9 @@ // Licensed under the MIT License. import * as child_process from 'child_process'; +import { injectable } from 'inversify'; import * as os from 'os'; +import { IBrowserService } from '../types'; export function launch(url: string) { let openCommand: string | undefined; @@ -19,3 +21,10 @@ export function launch(url: string) { } child_process.spawn(openCommand, [url]); } + +@injectable() +export class BrowserService implements IBrowserService { + public launch(url: string): void{ + launch(url); + } +} diff --git a/src/client/common/serviceRegistry.ts b/src/client/common/serviceRegistry.ts index e50399e644f6..42aa4e6b7d43 100644 --- a/src/client/common/serviceRegistry.ts +++ b/src/client/common/serviceRegistry.ts @@ -2,16 +2,18 @@ // Licensed under the MIT License. import { IServiceManager } from '../ioc/types'; +import { ApplicationEnvironment } from './application/applicationEnvironment'; import { ApplicationShell } from './application/applicationShell'; import { CommandManager } from './application/commandManager'; import { DebugService } from './application/debugService'; import { DocumentManager } from './application/documentManager'; import { TerminalManager } from './application/terminalManager'; -import { IApplicationShell, ICommandManager, IDebugService, IDocumentManager, ITerminalManager, IWorkspaceService } from './application/types'; +import { IApplicationEnvironment, IApplicationShell, ICommandManager, IDebugService, IDocumentManager, ITerminalManager, IWorkspaceService } from './application/types'; import { WorkspaceService } from './application/workspace'; import { ConfigurationService } from './configuration/service'; import { ProductInstaller } from './installer/productInstaller'; import { Logger } from './logger'; +import { BrowserService } from './net/browser'; import { PersistentStateFactory } from './persistentState'; import { IS_64_BIT, IS_WINDOWS } from './platform/constants'; import { PathUtils } from './platform/pathUtils'; @@ -21,7 +23,7 @@ import { CommandPromptAndPowerShell } from './terminal/environmentActivationProv import { TerminalServiceFactory } from './terminal/factory'; import { TerminalHelper } from './terminal/helper'; import { ITerminalActivationCommandProvider, ITerminalHelper, ITerminalServiceFactory } from './terminal/types'; -import { IConfigurationService, ICurrentProcess, IInstaller, ILogger, IPathUtils, IPersistentStateFactory, Is64Bit, IsWindows } from './types'; +import { IBrowserService, IConfigurationService, ICurrentProcess, IInstaller, ILogger, IPathUtils, IPersistentStateFactory, Is64Bit, IsWindows } from './types'; export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingletonInstance(IsWindows, IS_WINDOWS); @@ -40,6 +42,8 @@ export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IDocumentManager, DocumentManager); serviceManager.addSingleton(ITerminalManager, TerminalManager); serviceManager.addSingleton(IDebugService, DebugService); + serviceManager.addSingleton(IApplicationEnvironment, ApplicationEnvironment); + serviceManager.addSingleton(IBrowserService, BrowserService); serviceManager.addSingleton(ITerminalHelper, TerminalHelper); serviceManager.addSingleton(ITerminalActivationCommandProvider, Bash, 'bashCShellFish'); diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 954864e9596f..a44fba79fbc7 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -248,3 +248,8 @@ export interface ISocketServer extends Disposable { export const IExtensionContext = Symbol('ExtensionContext'); export interface IExtensionContext extends ExtensionContext { } + +export const IBrowserService = Symbol('IBrowserService'); +export interface IBrowserService { + launch(url: string): void; +} diff --git a/src/client/debugger/Common/Contracts.ts b/src/client/debugger/Common/Contracts.ts index 567ed2eda866..78590415714b 100644 --- a/src/client/debugger/Common/Contracts.ts +++ b/src/client/debugger/Common/Contracts.ts @@ -4,8 +4,9 @@ import { ChildProcess } from 'child_process'; import * as net from 'net'; import { OutputEvent } from 'vscode-debugadapter'; -import { DebugProtocol } from 'vscode-debugprotocol'; +import { DebugProtocol } from 'vscode-debugprotocol/lib/debugProtocol'; import { DebuggerPerformanceTelemetry, DebuggerTelemetry } from '../../telemetry/types'; +import { ExperimentalDebuggerType } from './constants'; export class TelemetryEvent extends OutputEvent { body!: { @@ -56,7 +57,7 @@ export interface ExceptionHandling { unhandled: string[]; } -export type DebuggerType = 'python' | 'pythonExperimental'; +export type DebuggerType = 'python' | typeof ExperimentalDebuggerType; export interface AdditionalLaunchDebugOptions { redirectOutput?: boolean; diff --git a/src/client/debugger/Common/constants.ts b/src/client/debugger/Common/constants.ts index e24fb1b790e5..e153527c27d1 100644 --- a/src/client/debugger/Common/constants.ts +++ b/src/client/debugger/Common/constants.ts @@ -7,3 +7,4 @@ import * as path from 'path'; import { EXTENSION_ROOT_DIR } from '../../common/constants'; export const PTVSD_PATH = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'experimental', 'ptvsd'); +export const ExperimentalDebuggerType = 'pythonExperimental'; diff --git a/src/client/debugger/banner.ts b/src/client/debugger/banner.ts new file mode 100644 index 000000000000..3062ed83cf87 --- /dev/null +++ b/src/client/debugger/banner.ts @@ -0,0 +1,129 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as crypto from 'crypto'; +import { inject, injectable } from 'inversify'; +import { Disposable } from 'vscode'; +import { IApplicationEnvironment, IApplicationShell, IDebugService } from '../common/application/types'; +import '../common/extensions'; +import { IBrowserService, IDisposableRegistry, ILogger, IPersistentStateFactory } from '../common/types'; +import { IServiceContainer } from '../ioc/types'; +import { ExperimentalDebuggerType } from './Common/constants'; +import { IExperimentalDebuggerBanner } from './types'; + +export enum PersistentStateKeys { + ShowBanner = 'ShowBanner', + DebuggerLaunchCounter = 'DebuggerLaunchCounter', + DebuggerLaunchThresholdCounter = 'DebuggerLaunchThresholdCounter' +} + +@injectable() +export class ExperimentalDebuggerBanner implements IExperimentalDebuggerBanner { + private initialized?: boolean; + private disabled?: boolean; + public get enabled(): boolean { + const factory = this.serviceContainer.get(IPersistentStateFactory); + return factory.createGlobalPersistentState(PersistentStateKeys.ShowBanner, true).value; + } + constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { } + public initialize() { + if (this.initialized) { + return; + } + this.initialized = true; + + // Don't even bother adding handlers if banner has been turned off. + if (!this.enabled) { + return; + } + const debuggerService = this.serviceContainer.get(IDebugService); + const disposable = debuggerService.onDidStartDebugSession(e => { + if (e.type === ExperimentalDebuggerType) { + const logger = this.serviceContainer.get(ILogger); + this.onDebugSessionStarted() + .catch(ex => logger.logError('Error in debugger Banner', ex)); + } + }); + + this.serviceContainer.get(IDisposableRegistry).push(disposable); + } + public async showBanner(): Promise { + const appShell = this.serviceContainer.get(IApplicationShell); + const yes = 'Take Survey'; + const no = 'No thanks'; + const response = await appShell.showInformationMessage('Can you take 2 minutes to tell us how the Experimental Debugger is working for you?', yes, no); + switch (response) { + case yes: + { + await this.launchSurvey(); + await this.disable(); + break; + } + case no: { + await this.disable(); + break; + } + default: { + return; + } + } + } + public async shouldShowBanner(): Promise { + if (!this.enabled) { + return false; + } + const [threshold, debuggerCounter] = await Promise.all([this.getDebuggerLaunchThresholdCounter(), this.getGetDebuggerLaunchCounter()]); + return debuggerCounter >= threshold; + } + + public async disable(): Promise { + const factory = this.serviceContainer.get(IPersistentStateFactory); + await factory.createGlobalPersistentState(PersistentStateKeys.ShowBanner, false).updateValue(false); + this.disabled = true; + } + public async launchSurvey(): Promise { + const debuggerLaunchCounter = await this.getGetDebuggerLaunchCounter(); + const browser = this.serviceContainer.get(IBrowserService); + browser.launch(`https://www.research.net/r/N7B25RV?n=${debuggerLaunchCounter}`); + } + private async incrementDebuggerLaunchCounter(): Promise { + const factory = this.serviceContainer.get(IPersistentStateFactory); + const state = factory.createGlobalPersistentState(PersistentStateKeys.DebuggerLaunchCounter, 0); + await state.updateValue(state.value + 1); + } + private async getGetDebuggerLaunchCounter(): Promise { + const factory = this.serviceContainer.get(IPersistentStateFactory); + const state = factory.createGlobalPersistentState(PersistentStateKeys.DebuggerLaunchCounter, 0); + return state.value; + } + private async getDebuggerLaunchThresholdCounter(): Promise { + const factory = this.serviceContainer.get(IPersistentStateFactory); + const state = factory.createGlobalPersistentState(PersistentStateKeys.DebuggerLaunchThresholdCounter, undefined); + if (state.value === undefined) { + const hexValue = parseInt(`0x${this.getRandomHex()}`, 16); + const randomNumber = Math.floor((10 * hexValue) / 16) + 1; + await state.updateValue(randomNumber); + } + return state.value!; + } + private getRandomHex() { + const appEnv = this.serviceContainer.get(IApplicationEnvironment); + const lastHexValue = appEnv.machineId.slice(-1); + const num = parseInt(`0x${lastHexValue}`, 16); + return isNaN(num) ? crypto.randomBytes(1).toString('hex').slice(-1) : lastHexValue; + } + private async onDebugSessionStarted(): Promise { + if (this.disabled) { + return; + } + await this.incrementDebuggerLaunchCounter(); + const show = await this.shouldShowBanner(); + if (!show) { + return; + } + + await this.showBanner(); + } +} diff --git a/src/client/debugger/serviceRegistry.ts b/src/client/debugger/serviceRegistry.ts index b3e2a657d32a..e634d828eed7 100644 --- a/src/client/debugger/serviceRegistry.ts +++ b/src/client/debugger/serviceRegistry.ts @@ -13,22 +13,23 @@ import { ICurrentProcess, ISocketServer } from '../common/types'; import { ServiceContainer } from '../ioc/container'; import { ServiceManager } from '../ioc/serviceManager'; import { IServiceContainer, IServiceManager } from '../ioc/types'; +import { ExperimentalDebuggerBanner } from './banner'; import { DebugStreamProvider } from './Common/debugStreamProvider'; import { ProtocolLogger } from './Common/protocolLogger'; import { ProtocolParser } from './Common/protocolParser'; import { ProtocolMessageWriter } from './Common/protocolWriter'; -import { IDebugStreamProvider, IProtocolLogger, IProtocolMessageWriter, IProtocolParser } from './types'; +import { IDebugStreamProvider, IExperimentalDebuggerBanner, IProtocolLogger, IProtocolMessageWriter, IProtocolParser } from './types'; export function initializeIoc(): IServiceContainer { const cont = new Container(); const serviceManager = new ServiceManager(cont); const serviceContainer = new ServiceContainer(cont); serviceManager.addSingletonInstance(IServiceContainer, serviceContainer); - registerTypes(serviceManager); + registerDebuggerTypes(serviceManager); return serviceContainer; } -function registerTypes(serviceManager: IServiceManager) { +function registerDebuggerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(ICurrentProcess, CurrentProcess); serviceManager.addSingleton(IDebugStreamProvider, DebugStreamProvider); serviceManager.addSingleton(IProtocolLogger, ProtocolLogger); @@ -38,3 +39,7 @@ function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(ISocketServer, SocketServer); serviceManager.addSingleton(IProtocolMessageWriter, ProtocolMessageWriter); } + +export function registerTypes(serviceManager: IServiceManager) { + serviceManager.addSingleton(IExperimentalDebuggerBanner, ExperimentalDebuggerBanner); +} diff --git a/src/client/debugger/types.ts b/src/client/debugger/types.ts index c34031b082af..6a641a08e8ad 100644 --- a/src/client/debugger/types.ts +++ b/src/client/debugger/types.ts @@ -38,3 +38,13 @@ export interface IProtocolMessageWriter { } export const IDebugConfigurationProvider = Symbol('DebugConfigurationProvider'); + +export const IExperimentalDebuggerBanner = Symbol('IExperimentalDebuggerBanner'); +export interface IExperimentalDebuggerBanner { + enabled: boolean; + initialize(): void; + showBanner(): Promise; + shouldShowBanner(): Promise; + disable(): Promise; + launchSurvey(): Promise; +} diff --git a/src/client/extension.ts b/src/client/extension.ts index 4e49e23a0786..d4bd8d76cfb8 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -29,7 +29,8 @@ import { registerTypes as variableRegisterTypes } from './common/variables/servi import { AttachRequestArguments, LaunchRequestArguments } from './debugger/Common/Contracts'; import { BaseConfigurationProvider } from './debugger/configProviders/baseProvider'; import { registerTypes as debugConfigurationRegisterTypes } from './debugger/configProviders/serviceRegistry'; -import { IDebugConfigurationProvider } from './debugger/types'; +import { registerTypes as debuggerRegisterTypes } from './debugger/serviceRegistry'; +import { IDebugConfigurationProvider, IExperimentalDebuggerBanner } from './debugger/types'; import { registerTypes as formattersRegisterTypes } from './formatters/serviceRegistry'; import { IInterpreterSelector } from './interpreter/configuration/types'; import { ICondaService, IInterpreterService, PythonInterpreter } from './interpreter/contracts'; @@ -150,6 +151,8 @@ export async function activate(context: ExtensionContext) { serviceContainer.getAll(IDebugConfigurationProvider).forEach(debugConfig => { context.subscriptions.push(debug.registerDebugConfigurationProvider(debugConfig.debugType, debugConfig)); }); + + serviceContainer.get(IExperimentalDebuggerBanner).initialize(); activationDeferred.resolve(); } @@ -178,6 +181,7 @@ function registerServices(context: ExtensionContext, serviceManager: ServiceMana installerRegisterTypes(serviceManager); commonRegisterTerminalTypes(serviceManager); debugConfigurationRegisterTypes(serviceManager); + debuggerRegisterTypes(serviceManager); } async function sendStartupTelemetry(activatedPromise: Promise, serviceContainer: IServiceContainer) { diff --git a/src/client/unittests/common/debugLauncher.ts b/src/client/unittests/common/debugLauncher.ts index 79654590eaf8..934ab9e6b2e0 100644 --- a/src/client/unittests/common/debugLauncher.ts +++ b/src/client/unittests/common/debugLauncher.ts @@ -4,6 +4,7 @@ import { Uri } from 'vscode'; import { IDebugService, IWorkspaceService } from '../../common/application/types'; import { EXTENSION_ROOT_DIR } from '../../common/constants'; import { IConfigurationService } from '../../common/types'; +import { ExperimentalDebuggerType } from '../../debugger/Common/constants'; import { DebugOptions } from '../../debugger/Common/Contracts'; import { IServiceContainer } from '../../ioc/types'; import { ITestDebugLauncher, LaunchOptions, TestProvider } from './types'; @@ -29,7 +30,7 @@ export class DebugLauncher implements ITestDebugLauncher { const configSettings = this.serviceContainer.get(IConfigurationService).getSettings(Uri.file(cwd)); const useExperimentalDebugger = configSettings.unitTest.useExperimentalDebugger === true; const debugManager = this.serviceContainer.get(IDebugService); - const debuggerType = useExperimentalDebugger ? 'pythonExperimental' : 'python'; + const debuggerType = useExperimentalDebugger ? ExperimentalDebuggerType : 'python'; const debugArgs = this.fixArgs(options.args, options.testProvider, useExperimentalDebugger); const program = this.getTestLauncherScript(options.testProvider, useExperimentalDebugger); return debugManager.startDebugging(workspaceFolder, { diff --git a/src/test/debugger/banner.unit.test.ts b/src/test/debugger/banner.unit.test.ts new file mode 100644 index 000000000000..f2c13cf21bd5 --- /dev/null +++ b/src/test/debugger/banner.unit.test.ts @@ -0,0 +1,150 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:no-any max-func-body-length + +import { expect } from 'chai'; +import * as typemoq from 'typemoq'; +import { DebugSession } from 'vscode'; +import { IDebugService } from '../../client/common/application/types'; +import { IBrowserService, IDisposableRegistry, ILogger, IPersistentState, IPersistentStateFactory } from '../../client/common/types'; +import { ExperimentalDebuggerBanner, PersistentStateKeys } from '../../client/debugger/banner'; +import { ExperimentalDebuggerType } from '../../client/debugger/Common/constants'; +import { IExperimentalDebuggerBanner } from '../../client/debugger/types'; +import { IServiceContainer } from '../../client/ioc/types'; + +suite('Debugging - Banner', () => { + let serviceContainer: typemoq.IMock; + let browser: typemoq.IMock; + let launchCounterState: typemoq.IMock>; + let launchThresholdCounterState: typemoq.IMock>; + let showBannerState: typemoq.IMock>; + let debugService: typemoq.IMock; + let banner: IExperimentalDebuggerBanner; + setup(() => { + serviceContainer = typemoq.Mock.ofType(); + browser = typemoq.Mock.ofType(); + debugService = typemoq.Mock.ofType(); + const logger = typemoq.Mock.ofType(); + + launchCounterState = typemoq.Mock.ofType>(); + showBannerState = typemoq.Mock.ofType>(); + launchThresholdCounterState = typemoq.Mock.ofType>(); + const factory = typemoq.Mock.ofType(); + factory + .setup(f => f.createGlobalPersistentState(typemoq.It.isValue(PersistentStateKeys.DebuggerLaunchCounter), typemoq.It.isAny())) + .returns(() => launchCounterState.object); + factory + .setup(f => f.createGlobalPersistentState(typemoq.It.isValue(PersistentStateKeys.ShowBanner), typemoq.It.isAny())) + .returns(() => showBannerState.object); + factory + .setup(f => f.createGlobalPersistentState(typemoq.It.isValue(PersistentStateKeys.DebuggerLaunchThresholdCounter), typemoq.It.isAny())) + .returns(() => launchThresholdCounterState.object); + + serviceContainer.setup(s => s.get(typemoq.It.isValue(IBrowserService))).returns(() => browser.object); + serviceContainer.setup(s => s.get(typemoq.It.isValue(IPersistentStateFactory))).returns(() => factory.object); + serviceContainer.setup(s => s.get(typemoq.It.isValue(IDebugService))).returns(() => debugService.object); + serviceContainer.setup(s => s.get(typemoq.It.isValue(ILogger))).returns(() => logger.object); + serviceContainer.setup(s => s.get(typemoq.It.isValue(IDisposableRegistry))).returns(() => []); + + banner = new ExperimentalDebuggerBanner(serviceContainer.object); + }); + test('Browser is displayed when launching service along with debugger launch counter', async () => { + const debuggerLaunchCounter = 1234; + launchCounterState.setup(l => l.value).returns(() => debuggerLaunchCounter).verifiable(typemoq.Times.once()); + browser.setup(b => b.launch(typemoq.It.isValue(`https://www.research.net/r/N7B25RV?n=${debuggerLaunchCounter}`))) + .verifiable(typemoq.Times.once()); + + await banner.launchSurvey(); + + launchCounterState.verifyAll(); + browser.verifyAll(); + }); + test('Increment Debugger Launch Counter when debug session starts', async () => { + let onDidStartDebugSessionCb: (e: DebugSession) => void; + debugService.setup(d => d.onDidStartDebugSession(typemoq.It.isAny())) + .callback(cb => onDidStartDebugSessionCb = cb) + .verifiable(typemoq.Times.once()); + + const debuggerLaunchCounter = 1234; + launchCounterState.setup(l => l.value).returns(() => debuggerLaunchCounter) + .verifiable(typemoq.Times.once()); + launchCounterState.setup(l => l.updateValue(typemoq.It.isValue(debuggerLaunchCounter + 1))) + .verifiable(typemoq.Times.once()); + showBannerState.setup(s => s.value).returns(() => true) + .verifiable(typemoq.Times.once()); + + banner.initialize(); + onDidStartDebugSessionCb!({ type: ExperimentalDebuggerType } as any); + + launchCounterState.verifyAll(); + browser.verifyAll(); + debugService.verifyAll(); + showBannerState.verifyAll(); + }); + test('Do not Increment Debugger Launch Counter when debug session starts when Banner is disabled', async () => { + debugService.setup(d => d.onDidStartDebugSession(typemoq.It.isAny())) + .verifiable(typemoq.Times.never()); + + const debuggerLaunchCounter = 1234; + launchCounterState.setup(l => l.value).returns(() => debuggerLaunchCounter) + .verifiable(typemoq.Times.never()); + launchCounterState.setup(l => l.updateValue(typemoq.It.isValue(debuggerLaunchCounter + 1))) + .verifiable(typemoq.Times.never()); + showBannerState.setup(s => s.value).returns(() => false) + .verifiable(typemoq.Times.atLeastOnce()); + + banner.initialize(); + + launchCounterState.verifyAll(); + browser.verifyAll(); + debugService.verifyAll(); + showBannerState.verifyAll(); + }); + test('shouldShowBanner returnes false when Banner is disabled', async () => { + showBannerState.setup(s => s.value).returns(() => false) + .verifiable(typemoq.Times.once()); + + expect(await banner.shouldShowBanner()).to.be.equal(false, 'Incorrect value'); + + showBannerState.verifyAll(); + }); + test('shouldShowBanner returnes false when Banner is enabled and debug counter is not same as threshold', async () => { + showBannerState.setup(s => s.value).returns(() => true) + .verifiable(typemoq.Times.once()); + launchCounterState.setup(l => l.value).returns(() => 1) + .verifiable(typemoq.Times.once()); + launchThresholdCounterState.setup(t => t.value).returns(() => 10) + .verifiable(typemoq.Times.atLeastOnce()); + + expect(await banner.shouldShowBanner()).to.be.equal(false, 'Incorrect value'); + + showBannerState.verifyAll(); + launchCounterState.verifyAll(); + launchThresholdCounterState.verifyAll(); + }); + test('shouldShowBanner returnes true when Banner is enabled and debug counter is same as threshold', async () => { + showBannerState.setup(s => s.value).returns(() => true) + .verifiable(typemoq.Times.once()); + launchCounterState.setup(l => l.value).returns(() => 10) + .verifiable(typemoq.Times.once()); + launchThresholdCounterState.setup(t => t.value).returns(() => 10) + .verifiable(typemoq.Times.atLeastOnce()); + + expect(await banner.shouldShowBanner()).to.be.equal(true, 'Incorrect value'); + + showBannerState.verifyAll(); + launchCounterState.verifyAll(); + launchThresholdCounterState.verifyAll(); + }); + test('Disabling banner should store value of \'false\' in global store', async () => { + showBannerState.setup(s => s.updateValue(typemoq.It.isValue(false))) + .verifiable(typemoq.Times.once()); + + await banner.disable(); + + showBannerState.verifyAll(); + }); +}); From d356582f3dab89a2ed53824dbb521cf75727bfc7 Mon Sep 17 00:00:00 2001 From: Nathan Gaberel Date: Fri, 15 Jun 2018 20:33:45 +0100 Subject: [PATCH 348/433] Add pipenv syntax highlighting (#1975) --- news/1 Enhancements/995.md | 1 + package.json | 12 ++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 news/1 Enhancements/995.md diff --git a/news/1 Enhancements/995.md b/news/1 Enhancements/995.md new file mode 100644 index 000000000000..28e84ce78994 --- /dev/null +++ b/news/1 Enhancements/995.md @@ -0,0 +1 @@ +Add syntax highlighting for [Pipenv](http://pipenv.readthedocs.io/en/latest/) files (thanks [Nathan Gaberel](https://github.com/n6g7)). diff --git a/package.json b/package.json index b3d56f1cd7ea..854b86447122 100644 --- a/package.json +++ b/package.json @@ -1818,6 +1818,18 @@ ".condarc" ] }, + { + "id": "toml", + "filenames": [ + "Pipfile" + ] + }, + { + "id": "json", + "filenames": [ + "Pipfile.lock" + ] + }, { "id": "jinja", "extensions": [ From 9cdeb2a71232b6a56e029954f5fa693a831e0c0e Mon Sep 17 00:00:00 2001 From: Hugues Valois Date: Fri, 15 Jun 2018 12:38:19 -0700 Subject: [PATCH 349/433] Update French translation. (#1973) --- package.nls.fr.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.nls.fr.json b/package.nls.fr.json index ea3dfd6dd7eb..c764405ca1da 100644 --- a/package.nls.fr.json +++ b/package.nls.fr.json @@ -6,7 +6,7 @@ "python.command.python.runtests.title": "Exécuter tous les tests unitaires", "python.command.python.debugtests.title": "Déboguer tous les tests unitaires", "python.command.python.execInTerminal.title": "Exécuter le script Python dans un terminal", - "python.command.python.setInterpreter.title": "Selectionner l'interpreteur", + "python.command.python.setInterpreter.title": "Sélectionner l'interpreteur", "python.command.python.updateSparkLibrary.title": "Mettre à jour les librairies de l'espace de travail PySpark", "python.command.python.refactorExtractVariable.title": "Extraire la variable", "python.command.python.refactorExtractMethod.title": "Extraire la méthode", @@ -19,7 +19,7 @@ "python.command.python.execSelectionInTerminal.title": "Exécuter la ligne/sélection dans un terminal Python", "python.command.python.execSelectionInDjangoShell.title": "Exécuter la ligne/sélection dans un shell Django", "python.command.python.goToPythonObject.title": "Se rendre à l'objet Python", - "python.command.python.setLinter.title": "Selectionner le linter", + "python.command.python.setLinter.title": "Sélectionner le linter", "python.command.python.enableLinting.title": "Activer le linting", "python.command.python.runLinting.title": "Exécuter le linting", "python.snippet.launch.standard.label": "Python : Fichier actuel", From 04cf096ab6d9c23e5d9e6acaaf8676cd2e561a18 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 18 Jun 2018 11:44:40 -0700 Subject: [PATCH 350/433] Check the Path environment variable when ext loads (#1982) Fixes #1698 --- .../diagnostics/applicationDiagnostics.ts | 48 ++++++ src/client/application/diagnostics/base.ts | 29 ++++ .../diagnostics/checks/envPathVariable.ts | 81 +++++++++ .../application/diagnostics/commands/base.ts | 12 ++ .../diagnostics/commands/factory.ts | 30 ++++ .../diagnostics/commands/ignore.ts | 18 ++ .../diagnostics/commands/launchBrowser.ts | 19 +++ .../application/diagnostics/commands/types.ts | 17 ++ .../application/diagnostics/constants.ts | 8 + src/client/application/diagnostics/filter.ts | 36 ++++ .../application/diagnostics/promptHandler.ts | 52 ++++++ .../diagnostics/serviceRegistry.ts | 19 +++ src/client/application/diagnostics/types.ts | 44 +++++ src/client/application/serviceRegistry.ts | 14 ++ src/client/application/types.ts | 16 ++ .../application/applicationEnvironment.ts | 6 + src/client/common/application/types.ts | 7 + src/client/common/extensions.ts | 12 ++ src/client/common/platform/pathUtils.ts | 8 +- src/client/common/types.ts | 7 + src/client/extension.ts | 6 + .../applicationDiagnostics.unit.test.ts | 116 +++++++++++++ .../checks/envPathVariable.unit.test.ts | 161 ++++++++++++++++++ .../diagnostics/commands/factory.unit.test.ts | 35 ++++ .../diagnostics/commands/ignore.unit.test.ts | 35 ++++ .../commands/launchBrowser.unit.test.ts | 34 ++++ .../diagnostics/filter.unit.test.ts | 119 +++++++++++++ .../diagnostics/promptHandler.unit.test.ts | 131 ++++++++++++++ ...nsions.test.ts => extensions.unit.test.ts} | 32 ++++ typings/extensions.d.ts | 5 + 30 files changed, 1155 insertions(+), 2 deletions(-) create mode 100644 src/client/application/diagnostics/applicationDiagnostics.ts create mode 100644 src/client/application/diagnostics/base.ts create mode 100644 src/client/application/diagnostics/checks/envPathVariable.ts create mode 100644 src/client/application/diagnostics/commands/base.ts create mode 100644 src/client/application/diagnostics/commands/factory.ts create mode 100644 src/client/application/diagnostics/commands/ignore.ts create mode 100644 src/client/application/diagnostics/commands/launchBrowser.ts create mode 100644 src/client/application/diagnostics/commands/types.ts create mode 100644 src/client/application/diagnostics/constants.ts create mode 100644 src/client/application/diagnostics/filter.ts create mode 100644 src/client/application/diagnostics/promptHandler.ts create mode 100644 src/client/application/diagnostics/serviceRegistry.ts create mode 100644 src/client/application/diagnostics/types.ts create mode 100644 src/client/application/serviceRegistry.ts create mode 100644 src/client/application/types.ts create mode 100644 src/test/application/diagnostics/applicationDiagnostics.unit.test.ts create mode 100644 src/test/application/diagnostics/checks/envPathVariable.unit.test.ts create mode 100644 src/test/application/diagnostics/commands/factory.unit.test.ts create mode 100644 src/test/application/diagnostics/commands/ignore.unit.test.ts create mode 100644 src/test/application/diagnostics/commands/launchBrowser.unit.test.ts create mode 100644 src/test/application/diagnostics/filter.unit.test.ts create mode 100644 src/test/application/diagnostics/promptHandler.unit.test.ts rename src/test/common/{extensions.test.ts => extensions.unit.test.ts} (53%) diff --git a/src/client/application/diagnostics/applicationDiagnostics.ts b/src/client/application/diagnostics/applicationDiagnostics.ts new file mode 100644 index 000000000000..84422846a19b --- /dev/null +++ b/src/client/application/diagnostics/applicationDiagnostics.ts @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { DiagnosticSeverity } from 'vscode'; +import { STANDARD_OUTPUT_CHANNEL } from '../../common/constants'; +import { ILogger, IOutputChannel } from '../../common/types'; +import { IServiceContainer } from '../../ioc/types'; +import { IApplicationDiagnostics } from '../types'; +import { EnvironmentPathVariableDiagnosticsServiceId } from './checks/envPathVariable'; +import { IDiagnostic, IDiagnosticsService } from './types'; + +@injectable() +export class ApplicationDiagnostics implements IApplicationDiagnostics { + constructor(@inject(IServiceContainer) private readonly serviceContainer: IServiceContainer) { } + public async performPreStartupHealthCheck(): Promise { + const envHealthCheck = this.serviceContainer.get(IDiagnosticsService, EnvironmentPathVariableDiagnosticsServiceId); + const diagnostics = await envHealthCheck.diagnose(); + this.log(diagnostics); + if (diagnostics.length > 0) { + envHealthCheck.handle(diagnostics); + } + } + private log(diagnostics: IDiagnostic[]): void { + const logger = this.serviceContainer.get(ILogger); + const outputChannel = this.serviceContainer.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); + diagnostics.forEach(item => { + const message = `Diagnostic Code: ${item.code}, Mesage: ${item.message}`; + switch (item.severity) { + case DiagnosticSeverity.Error: { + logger.logError(message); + outputChannel.appendLine(message); + break; + } + case DiagnosticSeverity.Warning: { + logger.logWarning(message); + outputChannel.appendLine(message); + break; + } + default: { + logger.logInformation(message); + } + } + }); + } +} diff --git a/src/client/application/diagnostics/base.ts b/src/client/application/diagnostics/base.ts new file mode 100644 index 000000000000..bafb8e84561c --- /dev/null +++ b/src/client/application/diagnostics/base.ts @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { injectable, unmanaged } from 'inversify'; +import { DiagnosticSeverity } from 'vscode'; +import { IServiceContainer } from '../../ioc/types'; +import { DiagnosticScope, IDiagnostic, IDiagnosticFilterService, IDiagnosticsService } from './types'; + +@injectable() +export abstract class BaseDiagnostic implements IDiagnostic { + constructor(public readonly code: string, public readonly message: string, + public readonly severity: DiagnosticSeverity, public readonly scope: DiagnosticScope) { } +} + +@injectable() +export abstract class BaseDiagnosticsService implements IDiagnosticsService { + protected readonly filterService: IDiagnosticFilterService; + constructor(@unmanaged() private readonly supportedDiagnosticCodes: string[], + @unmanaged() protected serviceContainer: IServiceContainer) { + this.filterService = serviceContainer.get(IDiagnosticFilterService); + } + public abstract diagnose(): Promise; + public abstract handle(diagnostics: IDiagnostic[]): Promise; + public async canHandle(diagnostic: IDiagnostic): Promise { + return this.supportedDiagnosticCodes.filter(item => item === diagnostic.code).length > 0; + } +} diff --git a/src/client/application/diagnostics/checks/envPathVariable.ts b/src/client/application/diagnostics/checks/envPathVariable.ts new file mode 100644 index 000000000000..3aa249ea8923 --- /dev/null +++ b/src/client/application/diagnostics/checks/envPathVariable.ts @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { DiagnosticSeverity } from 'vscode'; +import { IApplicationEnvironment } from '../../../common/application/types'; +import '../../../common/extensions'; +import { IPlatformService } from '../../../common/platform/types'; +import { ICurrentProcess, IPathUtils } from '../../../common/types'; +import { IServiceContainer } from '../../../ioc/types'; +import { BaseDiagnostic, BaseDiagnosticsService } from '../base'; +import { IDiagnosticsCommandFactory } from '../commands/types'; +import { DiagnosticCodes } from '../constants'; +import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../promptHandler'; +import { DiagnosticScope, IDiagnostic, IDiagnosticHandlerService } from '../types'; + +const InvalidEnvPathVariableMessage = 'The environment variable \'{0}\' seems to have some paths containing characters (\';\', \'"\', \'%\' or \';;\').' + + ' The existence of such characters are known to have caused the {1} extension not load.'; + +export class InvalidEnvironmentPathVariableDiagnostic extends BaseDiagnostic { + constructor(message) { + super(DiagnosticCodes.InvalidEnvironmentPathVariableDiagnostic, + message, DiagnosticSeverity.Warning, DiagnosticScope.Global); + } +} + +export const EnvironmentPathVariableDiagnosticsServiceId = 'EnvironmentPathVariableDiagnosticsServiceId'; + +@injectable() +export class EnvironmentPathVariableDiagnosticsService extends BaseDiagnosticsService { + protected readonly messageService: IDiagnosticHandlerService; + private readonly platform: IPlatformService; + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { + super([DiagnosticCodes.InvalidEnvironmentPathVariableDiagnostic], serviceContainer); + this.platform = this.serviceContainer.get(IPlatformService); + this.messageService = serviceContainer.get>(IDiagnosticHandlerService, DiagnosticCommandPromptHandlerServiceId); + } + public async diagnose(): Promise { + if (this.platform.isWindows && + this.doesPathVariableHaveInvalidEntries()) { + const env = this.serviceContainer.get(IApplicationEnvironment); + const message = InvalidEnvPathVariableMessage + .format(this.platform.pathVariableName, env.extensionName); + return [new InvalidEnvironmentPathVariableDiagnostic(message)]; + } else { + return []; + } + } + public async handle(diagnostics: IDiagnostic[]): Promise { + // This class can only handle one type of diagnostic, hence just use first item in list. + if (diagnostics.length === 0 || !this.canHandle(diagnostics[0])) { + return; + } + const diagnostic = diagnostics[0]; + const commandFactory = this.serviceContainer.get(IDiagnosticsCommandFactory); + const options = [ + { + prompt: 'Ignore' + }, + { + prompt: 'Always Ignore', + command: commandFactory.createCommand(diagnostic, { type: 'ignore', options: DiagnosticScope.Global }) + }, + { + prompt: 'More Info', + command: commandFactory.createCommand(diagnostic, { type: 'launch', options: 'https://aka.ms/Niq35h' }) + } + ]; + + await this.messageService.handle(diagnostic, { commandPrompts: options }); + } + private doesPathVariableHaveInvalidEntries() { + const currentProc = this.serviceContainer.get(ICurrentProcess); + const pathValue = currentProc.env[this.platform.pathVariableName]; + const pathSeparator = this.serviceContainer.get(IPathUtils).delimiter; + const paths = pathValue.split(pathSeparator); + return paths.filter(item => item.indexOf('"') >= 0 || item.indexOf(';') >= 0 || item.indexOf('%') >= 0 || item.length === 0).length > 0; + } +} diff --git a/src/client/application/diagnostics/commands/base.ts b/src/client/application/diagnostics/commands/base.ts new file mode 100644 index 000000000000..8bbb4cc5f1e4 --- /dev/null +++ b/src/client/application/diagnostics/commands/base.ts @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { IDiagnostic, IDiagnosticCommand } from '../types'; + +export abstract class BaseDiagnosticCommand implements IDiagnosticCommand { + constructor(public readonly diagnostic: IDiagnostic) { + } + public abstract invoke(): Promise; +} diff --git a/src/client/application/diagnostics/commands/factory.ts b/src/client/application/diagnostics/commands/factory.ts new file mode 100644 index 000000000000..47d2e63aa0d8 --- /dev/null +++ b/src/client/application/diagnostics/commands/factory.ts @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { IServiceContainer } from '../../../ioc/types'; +import { IDiagnostic, IDiagnosticCommand } from '../types'; +import { IgnoreDiagnosticCommand } from './ignore'; +import { LaunchBrowserCommand } from './launchBrowser'; +import { CommandOptions, IDiagnosticsCommandFactory } from './types'; + +@injectable() +export class DiagnosticsCommandFactory implements IDiagnosticsCommandFactory { + constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { } + public createCommand(diagnostic: IDiagnostic, options: CommandOptions): IDiagnosticCommand { + const commandType = options.type; + switch (options.type) { + case 'ignore': { + return new IgnoreDiagnosticCommand(diagnostic, this.serviceContainer, options.options); + } + case 'launch': { + return new LaunchBrowserCommand(diagnostic, this.serviceContainer, options.options); + } + default: { + throw new Error(`Unknown Diagnostic command commandType '${commandType}'`); + } + } + } +} diff --git a/src/client/application/diagnostics/commands/ignore.ts b/src/client/application/diagnostics/commands/ignore.ts new file mode 100644 index 000000000000..18a47bba07ab --- /dev/null +++ b/src/client/application/diagnostics/commands/ignore.ts @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { IServiceContainer } from '../../../ioc/types'; +import { DiagnosticScope, IDiagnostic, IDiagnosticFilterService } from '../types'; +import { BaseDiagnosticCommand } from './base'; + +export class IgnoreDiagnosticCommand extends BaseDiagnosticCommand { + constructor(diagnostic: IDiagnostic, private serviceContainer: IServiceContainer, private readonly scope: DiagnosticScope) { + super(diagnostic); + } + public invoke(): Promise { + const filter = this.serviceContainer.get(IDiagnosticFilterService); + return filter.ignoreDiagnostic(this.diagnostic.code, this.scope); + } +} diff --git a/src/client/application/diagnostics/commands/launchBrowser.ts b/src/client/application/diagnostics/commands/launchBrowser.ts new file mode 100644 index 000000000000..55a78a3c0971 --- /dev/null +++ b/src/client/application/diagnostics/commands/launchBrowser.ts @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { IBrowserService } from '../../../common/types'; +import { IServiceContainer } from '../../../ioc/types'; +import { IDiagnostic } from '../types'; +import { BaseDiagnosticCommand } from './base'; + +export class LaunchBrowserCommand extends BaseDiagnosticCommand { + constructor(diagnostic: IDiagnostic, private serviceContainer: IServiceContainer, private url: string) { + super(diagnostic); + } + public async invoke(): Promise { + const browser = this.serviceContainer.get(IBrowserService); + return browser.launch(this.url); + } +} diff --git a/src/client/application/diagnostics/commands/types.ts b/src/client/application/diagnostics/commands/types.ts new file mode 100644 index 000000000000..b6f95e2bc769 --- /dev/null +++ b/src/client/application/diagnostics/commands/types.ts @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { DiagnosticScope, IDiagnostic, IDiagnosticCommand } from '../types'; + +export type CommandOption = { type: Type; options: Option }; +export type LaunchBrowserOption = CommandOption<'launch', string>; +export type IgnoreDiagnostOption = CommandOption<'ignore', DiagnosticScope>; +export type CommandOptions = LaunchBrowserOption | IgnoreDiagnostOption; + +export const IDiagnosticsCommandFactory = Symbol('IDiagnosticsCommandFactory'); + +export interface IDiagnosticsCommandFactory { + createCommand(diagnostic: IDiagnostic, options: CommandOptions): IDiagnosticCommand; +} diff --git a/src/client/application/diagnostics/constants.ts b/src/client/application/diagnostics/constants.ts new file mode 100644 index 000000000000..88431d4a855b --- /dev/null +++ b/src/client/application/diagnostics/constants.ts @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +export enum DiagnosticCodes { + InvalidEnvironmentPathVariableDiagnostic = 'InvalidEnvironmentPathVariableDiagnostic' +} diff --git a/src/client/application/diagnostics/filter.ts b/src/client/application/diagnostics/filter.ts new file mode 100644 index 000000000000..908c02c61136 --- /dev/null +++ b/src/client/application/diagnostics/filter.ts @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { IPersistentStateFactory } from '../../common/types'; +import { IServiceContainer } from '../../ioc/types'; +import { DiagnosticScope, IDiagnosticFilterService } from './types'; + +export enum FilterKeys { + GlobalDiagnosticFilter = 'GLOBAL_DIAGNOSTICS_FILTER', + WorkspaceDiagnosticFilter = 'WORKSPACE_DIAGNOSTICS_FILTER' +} + +@injectable() +export class DiagnosticFilterService implements IDiagnosticFilterService { + constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { + } + public async shouldIgnoreDiagnostic(code: string): Promise { + const factory = this.serviceContainer.get(IPersistentStateFactory); + const globalState = factory.createGlobalPersistentState(FilterKeys.GlobalDiagnosticFilter, []); + const workspaceState = factory.createWorkspacePersistentState(FilterKeys.WorkspaceDiagnosticFilter, []); + return globalState.value.indexOf(code) >= 0 || + workspaceState.value.indexOf(code) >= 0; + } + public async ignoreDiagnostic(code: string, scope: DiagnosticScope): Promise { + const factory = this.serviceContainer.get(IPersistentStateFactory); + const state = scope === DiagnosticScope.Global ? + factory.createGlobalPersistentState(FilterKeys.GlobalDiagnosticFilter, []) : + factory.createWorkspacePersistentState(FilterKeys.WorkspaceDiagnosticFilter, []); + + const currentValue = state.value.slice(); + await state.updateValue(currentValue.concat(code)); + } +} diff --git a/src/client/application/diagnostics/promptHandler.ts b/src/client/application/diagnostics/promptHandler.ts new file mode 100644 index 000000000000..939f7074ec49 --- /dev/null +++ b/src/client/application/diagnostics/promptHandler.ts @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { DiagnosticSeverity } from 'vscode'; +import { IApplicationShell } from '../../common/application/types'; +import { IServiceContainer } from '../../ioc/types'; +import { IDiagnostic, IDiagnosticCommand, IDiagnosticHandlerService } from './types'; + +export type MessageCommandPrompt = { + commandPrompts: { + prompt: string; + command?: IDiagnosticCommand; + }[]; + message?: string; +}; + +export const DiagnosticCommandPromptHandlerServiceId = 'DiagnosticCommandPromptHandlerServiceId'; + +@injectable() +export class DiagnosticCommandPromptHandlerService implements IDiagnosticHandlerService { + private readonly appShell: IApplicationShell; + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { + this.appShell = serviceContainer.get(IApplicationShell); + } + public async handle(diagnostic: IDiagnostic, options: MessageCommandPrompt = { commandPrompts: [] }): Promise { + const prompts = options.commandPrompts.map(option => option.prompt); + const response = await this.displayMessage(options.message ? options.message : diagnostic.message, diagnostic.severity, prompts); + if (!response) { + return; + } + const selectedOption = options.commandPrompts.find(option => option.prompt === response); + if (selectedOption && selectedOption.command) { + await selectedOption.command.invoke(); + } + } + private async displayMessage(message: string, severity: DiagnosticSeverity, prompts: string[]): Promise { + switch (severity) { + case DiagnosticSeverity.Error: { + return this.appShell.showErrorMessage(message, ...prompts); + } + case DiagnosticSeverity.Warning: { + return this.appShell.showWarningMessage(message, ...prompts); + } + default: { + return this.appShell.showInformationMessage(message, ...prompts); + } + } + } +} diff --git a/src/client/application/diagnostics/serviceRegistry.ts b/src/client/application/diagnostics/serviceRegistry.ts new file mode 100644 index 000000000000..6554a6ddf37b --- /dev/null +++ b/src/client/application/diagnostics/serviceRegistry.ts @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { IServiceManager } from '../../ioc/types'; +import { EnvironmentPathVariableDiagnosticsService, EnvironmentPathVariableDiagnosticsServiceId } from './checks/envPathVariable'; +import { DiagnosticsCommandFactory } from './commands/factory'; +import { IDiagnosticsCommandFactory } from './commands/types'; +import { DiagnosticFilterService } from './filter'; +import { DiagnosticCommandPromptHandlerService, DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from './promptHandler'; +import { IDiagnosticFilterService, IDiagnosticHandlerService, IDiagnosticsService } from './types'; + +export function registerTypes(serviceManager: IServiceManager) { + serviceManager.addSingleton(IDiagnosticFilterService, DiagnosticFilterService); + serviceManager.addSingleton>(IDiagnosticHandlerService, DiagnosticCommandPromptHandlerService, DiagnosticCommandPromptHandlerServiceId); + serviceManager.addSingleton(IDiagnosticsService, EnvironmentPathVariableDiagnosticsService, EnvironmentPathVariableDiagnosticsServiceId); + serviceManager.addSingleton(IDiagnosticsCommandFactory, DiagnosticsCommandFactory); +} diff --git a/src/client/application/diagnostics/types.ts b/src/client/application/diagnostics/types.ts new file mode 100644 index 000000000000..41f5940c8c63 --- /dev/null +++ b/src/client/application/diagnostics/types.ts @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { DiagnosticSeverity } from 'vscode'; + +export enum DiagnosticScope { + Global = 'Global', + WorkspaceFolder = 'WorkspaceFolder' +} + +export interface IDiagnostic { + readonly code: string; + readonly message: string; + readonly severity: DiagnosticSeverity; + readonly scope: DiagnosticScope; +} + +export const IDiagnosticsService = Symbol('IDiagnosticsService'); + +export interface IDiagnosticsService { + diagnose(): Promise; + canHandle(diagnostic: IDiagnostic): Promise; + handle(diagnostics: IDiagnostic[]): Promise; +} + +export const IDiagnosticFilterService = Symbol('IDiagnosticFilterService'); + +export interface IDiagnosticFilterService { + shouldIgnoreDiagnostic(code: string): Promise; + ignoreDiagnostic(code: string, scope: DiagnosticScope): Promise; +} + +export const IDiagnosticHandlerService = Symbol('IDiagnosticHandlerService'); + +export interface IDiagnosticHandlerService { + handle(diagnostic: IDiagnostic, options?: T): Promise; +} + +export interface IDiagnosticCommand { + readonly diagnostic: IDiagnostic; + invoke(): Promise; +} diff --git a/src/client/application/serviceRegistry.ts b/src/client/application/serviceRegistry.ts new file mode 100644 index 000000000000..7e4b561b4995 --- /dev/null +++ b/src/client/application/serviceRegistry.ts @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { IServiceManager } from '../ioc/types'; +import { ApplicationDiagnostics } from './diagnostics/applicationDiagnostics'; +import { registerTypes as diagnosticsRegisterTypes } from './diagnostics/serviceRegistry'; +import { IApplicationDiagnostics } from './types'; + +export function registerTypes(serviceManager: IServiceManager) { + serviceManager.addSingleton(IApplicationDiagnostics, ApplicationDiagnostics); + diagnosticsRegisterTypes(serviceManager); +} diff --git a/src/client/application/types.ts b/src/client/application/types.ts new file mode 100644 index 000000000000..7ceaf387f94d --- /dev/null +++ b/src/client/application/types.ts @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +export const IApplicationDiagnostics = Symbol('IApplicationDiagnostics'); + +export interface IApplicationDiagnostics { + /** + * Perform pre-extension activation health checks. + * E.g. validate user environment, etc. + * @returns {Promise} + * @memberof IApplicationDiagnostics + */ + performPreStartupHealthCheck(): Promise; +} diff --git a/src/client/common/application/applicationEnvironment.ts b/src/client/common/application/applicationEnvironment.ts index 95e2c4b2e4dc..a26eb2eede00 100644 --- a/src/client/common/application/applicationEnvironment.ts +++ b/src/client/common/application/applicationEnvironment.ts @@ -4,7 +4,9 @@ 'use strict'; import { injectable } from 'inversify'; +import * as path from 'path'; import * as vscode from 'vscode'; +import { EXTENSION_ROOT_DIR } from '../constants'; import { IApplicationEnvironment } from './types'; @injectable() @@ -24,4 +26,8 @@ export class ApplicationEnvironment implements IApplicationEnvironment { public get machineId(): string { return vscode.env.machineId; } + public get extensionName(): string { + // tslint:disable-next-line:non-literal-require + return require(path.join(EXTENSION_ROOT_DIR, 'package.json')).displayName; + } } diff --git a/src/client/common/application/types.ts b/src/client/common/application/types.ts index f7dab889e065..d3d67245feb2 100644 --- a/src/client/common/application/types.ts +++ b/src/client/common/application/types.ts @@ -561,6 +561,13 @@ export interface IApplicationEnvironment { */ appName: string; + /** + * The extension name. + * + * @readonly + */ + extensionName: string; + /** * The application root folder from which the editor is running. * diff --git a/src/client/common/extensions.ts b/src/client/common/extensions.ts index ea4ec4d1bf3a..7dac59593aa4 100644 --- a/src/client/common/extensions.ts +++ b/src/client/common/extensions.ts @@ -26,6 +26,11 @@ declare interface String { * E.g. if an argument contains a space, then it will be enclosed within double quotes. */ fileToCommandArgument(): string; + /** + * String.format() implementation. + * Tokens such as {0}, {1} will be replaced with corresponding positional arguments. + */ + format(...args: string[]): string; } /** @@ -82,3 +87,10 @@ Promise.prototype.ignoreErrors = function (this: Promise) { // tslint:disable-next-line:no-empty this.catch(() => { }); }; + +if (!String.prototype.format) { + String.prototype.format = function (this: string) { + const args = arguments; + return this.replace(/{(\d+)}/g, (match, number) => args[number] === undefined ? match : args[number]); + }; +} diff --git a/src/client/common/platform/pathUtils.ts b/src/client/common/platform/pathUtils.ts index 3fcd2838d29e..319d523402fb 100644 --- a/src/client/common/platform/pathUtils.ts +++ b/src/client/common/platform/pathUtils.ts @@ -3,14 +3,18 @@ import * as path from 'path'; import { IPathUtils, IsWindows } from '../types'; import { NON_WINDOWS_PATH_VARIABLE_NAME, WINDOWS_PATH_VARIABLE_NAME } from './constants'; -// TO DO: Deprecate in favor of IPlatformService @injectable() export class PathUtils implements IPathUtils { constructor(@inject(IsWindows) private isWindows: boolean) { } + public get delimiter(): string { + return path.delimiter; + } + // TO DO: Deprecate in favor of IPlatformService public getPathVariableName() { return this.isWindows ? WINDOWS_PATH_VARIABLE_NAME : NON_WINDOWS_PATH_VARIABLE_NAME; } - public basename(pathValue: string, ext?: string): string{ + public basename(pathValue: string, ext?: string): string { return path.basename(pathValue, ext); } + } diff --git a/src/client/common/types.ts b/src/client/common/types.ts index a44fba79fbc7..733f0ffbee84 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -36,6 +36,12 @@ export type ExecutionInfo = { product?: Product; }; +export enum LogLevel { + Information = 'Information', + Error = 'Error', + Warning = 'Warning' +} + export const ILogger = Symbol('ILogger'); export interface ILogger { @@ -94,6 +100,7 @@ export interface IInstaller { export const IPathUtils = Symbol('IPathUtils'); export interface IPathUtils { + readonly delimiter: string; getPathVariableName(): 'Path' | 'PATH'; basename(pathValue: string, ext?: string): string; } diff --git a/src/client/extension.ts b/src/client/extension.ts index d4bd8d76cfb8..d3528183e022 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -13,6 +13,8 @@ import { Container } from 'inversify'; import { CodeActionKind, debug, Disposable, ExtensionContext, extensions, IndentAction, languages, Memento, OutputChannel, window } from 'vscode'; import { registerTypes as activationRegisterTypes } from './activation/serviceRegistry'; import { IExtensionActivationService } from './activation/types'; +import { registerTypes as appRegisterTypes } from './application/serviceRegistry'; +import { IApplicationDiagnostics } from './application/types'; import { IWorkspaceService } from './common/application/types'; import { PythonSettings } from './common/configSettings'; import { PYTHON, PYTHON_LANGUAGE, STANDARD_OUTPUT_CHANNEL } from './common/constants'; @@ -69,6 +71,9 @@ export async function activate(context: ExtensionContext) { const serviceContainer = new ServiceContainer(cont); registerServices(context, serviceManager, serviceContainer); + const appDiagnostics = serviceContainer.get(IApplicationDiagnostics); + await appDiagnostics.performPreStartupHealthCheck(); + const interpreterManager = serviceContainer.get(IInterpreterService); // This must be completed before we can continue as language server needs the interpreter path. interpreterManager.initialize(); @@ -182,6 +187,7 @@ function registerServices(context: ExtensionContext, serviceManager: ServiceMana commonRegisterTerminalTypes(serviceManager); debugConfigurationRegisterTypes(serviceManager); debuggerRegisterTypes(serviceManager); + appRegisterTypes(serviceManager); } async function sendStartupTelemetry(activatedPromise: Promise, serviceContainer: IServiceContainer) { diff --git a/src/test/application/diagnostics/applicationDiagnostics.unit.test.ts b/src/test/application/diagnostics/applicationDiagnostics.unit.test.ts new file mode 100644 index 000000000000..03f3c149d153 --- /dev/null +++ b/src/test/application/diagnostics/applicationDiagnostics.unit.test.ts @@ -0,0 +1,116 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:insecure-random + +import * as typemoq from 'typemoq'; +import { DiagnosticSeverity } from 'vscode'; +import { ApplicationDiagnostics } from '../../../client/application/diagnostics/applicationDiagnostics'; +import { EnvironmentPathVariableDiagnosticsServiceId } from '../../../client/application/diagnostics/checks/envPathVariable'; +import { DiagnosticScope, IDiagnostic, IDiagnosticsService } from '../../../client/application/diagnostics/types'; +import { IApplicationDiagnostics } from '../../../client/application/types'; +import { STANDARD_OUTPUT_CHANNEL } from '../../../client/common/constants'; +import { ILogger, IOutputChannel } from '../../../client/common/types'; +import { IServiceContainer } from '../../../client/ioc/types'; + +suite('Application Diagnostics - ApplicationDiagnostics', () => { + let serviceContainer: typemoq.IMock; + let envHealthCheck: typemoq.IMock; + let outputChannel: typemoq.IMock; + let logger: typemoq.IMock; + let appDiagnostics: IApplicationDiagnostics; + + setup(() => { + serviceContainer = typemoq.Mock.ofType(); + envHealthCheck = typemoq.Mock.ofType(); + outputChannel = typemoq.Mock.ofType(); + logger = typemoq.Mock.ofType(); + + serviceContainer.setup(d => d.get(typemoq.It.isValue(IDiagnosticsService), typemoq.It.isValue(EnvironmentPathVariableDiagnosticsServiceId))) + .returns(() => envHealthCheck.object); + serviceContainer.setup(d => d.get(typemoq.It.isValue(IOutputChannel), typemoq.It.isValue(STANDARD_OUTPUT_CHANNEL))) + .returns(() => outputChannel.object); + serviceContainer.setup(d => d.get(typemoq.It.isValue(ILogger))) + .returns(() => logger.object); + + appDiagnostics = new ApplicationDiagnostics(serviceContainer.object); + }); + + test('Performing Pre Startup Health Check must check Path environment variable', async () => { + envHealthCheck.setup(e => e.diagnose()) + .returns(() => Promise.resolve([])) + .verifiable(typemoq.Times.once()); + + await appDiagnostics.performPreStartupHealthCheck(); + + envHealthCheck.verifyAll(); + }); + + test('Diagnostics Returned by Per Startup Health Checks must be logged', async () => { + const diagnostics: IDiagnostic[] = []; + for (let i = 0; i <= (Math.random() * 10); i += 1) { + const diagnostic: IDiagnostic = { + code: `Error${i}`, + message: `Error${i}`, + scope: i % 2 === 0 ? DiagnosticScope.Global : DiagnosticScope.WorkspaceFolder, + severity: DiagnosticSeverity.Error + }; + diagnostics.push(diagnostic); + } + for (let i = 0; i <= (Math.random() * 10); i += 1) { + const diagnostic: IDiagnostic = { + code: `Warning${i}`, + message: `Warning${i}`, + scope: i % 2 === 0 ? DiagnosticScope.Global : DiagnosticScope.WorkspaceFolder, + severity: DiagnosticSeverity.Warning + }; + diagnostics.push(diagnostic); + } + for (let i = 0; i <= (Math.random() * 10); i += 1) { + const diagnostic: IDiagnostic = { + code: `Info${i}`, + message: `Info${i}`, + scope: i % 2 === 0 ? DiagnosticScope.Global : DiagnosticScope.WorkspaceFolder, + severity: DiagnosticSeverity.Information + }; + diagnostics.push(diagnostic); + } + + for (const diagnostic of diagnostics) { + const message = `Diagnostic Code: ${diagnostic.code}, Mesage: ${diagnostic.message}`; + switch (diagnostic.severity) { + case DiagnosticSeverity.Error: { + logger.setup(l => l.logError(message)) + .verifiable(typemoq.Times.once()); + outputChannel.setup(o => o.appendLine(message)) + .verifiable(typemoq.Times.once()); + break; + } + case DiagnosticSeverity.Warning: { + logger.setup(l => l.logWarning(message)) + .verifiable(typemoq.Times.once()); + outputChannel.setup(o => o.appendLine(message)) + .verifiable(typemoq.Times.once()); + break; + } + default: { + logger.setup(l => l.logInformation(message)) + .verifiable(typemoq.Times.once()); + break; + } + } + } + + envHealthCheck.setup(e => e.diagnose()) + .returns(() => Promise.resolve(diagnostics)) + .verifiable(typemoq.Times.once()); + + await appDiagnostics.performPreStartupHealthCheck(); + + envHealthCheck.verifyAll(); + outputChannel.verifyAll(); + logger.verifyAll(); + }); +}); diff --git a/src/test/application/diagnostics/checks/envPathVariable.unit.test.ts b/src/test/application/diagnostics/checks/envPathVariable.unit.test.ts new file mode 100644 index 000000000000..44ce2a0749fb --- /dev/null +++ b/src/test/application/diagnostics/checks/envPathVariable.unit.test.ts @@ -0,0 +1,161 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { expect } from 'chai'; +import * as path from 'path'; +import * as typemoq from 'typemoq'; +import { DiagnosticSeverity } from 'vscode'; +import { EnvironmentPathVariableDiagnosticsService } from '../../../../client/application/diagnostics/checks/envPathVariable'; +import { CommandOption, IDiagnosticsCommandFactory } from '../../../../client/application/diagnostics/commands/types'; +import { DiagnosticCodes } from '../../../../client/application/diagnostics/constants'; +import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../../../../client/application/diagnostics/promptHandler'; +import { DiagnosticScope, IDiagnostic, IDiagnosticCommand, IDiagnosticFilterService, IDiagnosticHandlerService, IDiagnosticsService } from '../../../../client/application/diagnostics/types'; +import { IApplicationEnvironment } from '../../../../client/common/application/types'; +import { IPlatformService } from '../../../../client/common/platform/types'; +import { ICurrentProcess, IPathUtils } from '../../../../client/common/types'; +import { EnvironmentVariables } from '../../../../client/common/variables/types'; +import { IServiceContainer } from '../../../../client/ioc/types'; + +// tslint:disable-next-line:max-func-body-length +suite('Application Diagnostics - Checks Env Path Variable', () => { + let diagnosticService: IDiagnosticsService; + let platformService: typemoq.IMock; + let messageHandler: typemoq.IMock>; + let filterService: typemoq.IMock; + let procEnv: typemoq.IMock; + let appEnv: typemoq.IMock; + let commandFactory: typemoq.IMock; + const pathVariableName = 'Path'; + const pathDelimiter = ';'; + const extensionName = 'Some Extension Name'; + setup(() => { + const serviceContainer = typemoq.Mock.ofType(); + platformService = typemoq.Mock.ofType(); + platformService.setup(p => p.pathVariableName).returns(() => pathVariableName); + serviceContainer.setup(s => s.get(typemoq.It.isValue(IPlatformService))) + .returns(() => platformService.object); + + messageHandler = typemoq.Mock.ofType>(); + serviceContainer.setup(s => s.get(typemoq.It.isValue(IDiagnosticHandlerService), typemoq.It.isValue(DiagnosticCommandPromptHandlerServiceId))) + .returns(() => messageHandler.object); + + appEnv = typemoq.Mock.ofType(); + appEnv.setup(a => a.extensionName).returns(() => extensionName); + serviceContainer.setup(s => s.get(typemoq.It.isValue(IApplicationEnvironment))) + .returns(() => appEnv.object); + + filterService = typemoq.Mock.ofType(); + serviceContainer.setup(s => s.get(typemoq.It.isValue(IDiagnosticFilterService))) + .returns(() => filterService.object); + + commandFactory = typemoq.Mock.ofType(); + serviceContainer.setup(s => s.get(typemoq.It.isValue(IDiagnosticsCommandFactory))) + .returns(() => commandFactory.object); + + const currentProc = typemoq.Mock.ofType(); + procEnv = typemoq.Mock.ofType(); + currentProc.setup(p => p.env).returns(() => procEnv.object); + serviceContainer.setup(s => s.get(typemoq.It.isValue(ICurrentProcess))) + .returns(() => currentProc.object); + + const pathUtils = typemoq.Mock.ofType(); + pathUtils.setup(p => p.delimiter).returns(() => pathDelimiter); + serviceContainer.setup(s => s.get(typemoq.It.isValue(IPathUtils))) + .returns(() => pathUtils.object); + + diagnosticService = new EnvironmentPathVariableDiagnosticsService(serviceContainer.object); + }); + + test('Can handle EnvPathVariable diagnostics', async () => { + const diagnostic = typemoq.Mock.ofType(); + diagnostic.setup(d => d.code) + .returns(() => DiagnosticCodes.InvalidEnvironmentPathVariableDiagnostic) + .verifiable(typemoq.Times.atLeastOnce()); + + const canHandle = await diagnosticService.canHandle(diagnostic.object); + expect(canHandle).to.be.equal(true, 'Invalid value'); + diagnostic.verifyAll(); + }); + test('Can not handle non-EnvPathVariable diagnostics', async () => { + const diagnostic = typemoq.Mock.ofType(); + diagnostic.setup(d => d.code) + .returns(() => 'Something Else') + .verifiable(typemoq.Times.atLeastOnce()); + + const canHandle = await diagnosticService.canHandle(diagnostic.object); + expect(canHandle).to.be.equal(false, 'Invalid value'); + diagnostic.verifyAll(); + }); + test('Should return empty diagnostics for Mac', async () => { + platformService.setup(p => p.isMac).returns(() => true); + platformService.setup(p => p.isLinux).returns(() => false); + platformService.setup(p => p.isWindows).returns(() => false); + const diagnostics = await diagnosticService.diagnose(); + expect(diagnostics).to.be.deep.equal([]); + }); + test('Should return empty diagnostics for Linux', async () => { + platformService.setup(p => p.isMac).returns(() => false); + platformService.setup(p => p.isLinux).returns(() => true); + platformService.setup(p => p.isWindows).returns(() => false); + const diagnostics = await diagnosticService.diagnose(); + expect(diagnostics).to.be.deep.equal([]); + }); + test('Should return empty diagnostics for Windows if path variable is valid', async () => { + platformService.setup(p => p.isWindows).returns(() => true); + const paths = [ + path.join('one', 'two', 'three'), + path.join('one', 'two', 'four') + ].join(pathDelimiter); + procEnv.setup(env => env[pathVariableName]).returns(() => paths); + + const diagnostics = await diagnosticService.diagnose(); + + expect(diagnostics).to.be.deep.equal([]); + }); + [';;', '"', '%'].forEach(invalidCharacter => { + test(`Should return single diagnostics for Windows if path contains ${invalidCharacter}`, async () => { + platformService.setup(p => p.isWindows).returns(() => true); + const paths = [ + path.join('one', 'two', `three${invalidCharacter}`), + path.join('one', 'two', 'four') + ].join(pathDelimiter); + procEnv.setup(env => env[pathVariableName]).returns(() => paths); + + const diagnostics = await diagnosticService.diagnose(); + + expect(diagnostics).to.be.lengthOf(1); + expect(diagnostics[0].code).to.be.equal(DiagnosticCodes.InvalidEnvironmentPathVariableDiagnostic); + expect(diagnostics[0].message).to.contain(extensionName); + expect(diagnostics[0].message).to.contain(pathVariableName); + expect(diagnostics[0].severity).to.be.equal(DiagnosticSeverity.Warning); + expect(diagnostics[0].scope).to.be.equal(DiagnosticScope.Global); + }); + }); + test('Should display three options in message displayed with 2 commands', async () => { + platformService.setup(p => p.isWindows).returns(() => true); + const diagnostic = typemoq.Mock.ofType(); + diagnostic.setup(d => d.code) + .returns(() => DiagnosticCodes.InvalidEnvironmentPathVariableDiagnostic) + .verifiable(typemoq.Times.atLeastOnce()); + const alwaysIgnoreCommand = typemoq.Mock.ofType(); + commandFactory.setup(f => f.createCommand(typemoq.It.isAny(), + typemoq.It.isObjectWith>({ type: 'ignore', options: DiagnosticScope.Global }))) + .returns(() => alwaysIgnoreCommand.object) + .verifiable(typemoq.Times.once()); + const launchBrowserCommand = typemoq.Mock.ofType(); + commandFactory.setup(f => f.createCommand(typemoq.It.isAny(), + typemoq.It.isObjectWith>({ type: 'launch' }))) + .returns(() => launchBrowserCommand.object) + .verifiable(typemoq.Times.once()); + messageHandler.setup(m => m.handle(typemoq.It.isAny(), typemoq.It.isAny())) + .verifiable(typemoq.Times.once()); + + await diagnosticService.handle([diagnostic.object]); + + diagnostic.verifyAll(); + commandFactory.verifyAll(); + messageHandler.verifyAll(); + }); +}); diff --git a/src/test/application/diagnostics/commands/factory.unit.test.ts b/src/test/application/diagnostics/commands/factory.unit.test.ts new file mode 100644 index 000000000000..187499f6d652 --- /dev/null +++ b/src/test/application/diagnostics/commands/factory.unit.test.ts @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { expect } from 'chai'; +import * as typemoq from 'typemoq'; +import { DiagnosticsCommandFactory } from '../../../../client/application/diagnostics/commands/factory'; +import { IgnoreDiagnosticCommand } from '../../../../client/application/diagnostics/commands/ignore'; +import { LaunchBrowserCommand } from '../../../../client/application/diagnostics/commands/launchBrowser'; +import { IDiagnosticsCommandFactory } from '../../../../client/application/diagnostics/commands/types'; +import { DiagnosticScope, IDiagnostic } from '../../../../client/application/diagnostics/types'; +import { IServiceContainer } from '../../../../client/ioc/types'; + +suite('Application Diagnostics - Commands Factory', () => { + let commandFactory: IDiagnosticsCommandFactory; + setup(() => { + const serviceContainer = typemoq.Mock.ofType(); + commandFactory = new DiagnosticsCommandFactory(serviceContainer.object); + }); + + test('Test creation of Ignore Command', async () => { + const diagnostic = typemoq.Mock.ofType(); + + const command = commandFactory.createCommand(diagnostic.object, { type: 'ignore', options: DiagnosticScope.Global }); + expect(command).to.be.instanceOf(IgnoreDiagnosticCommand); + }); + + test('Test creation of Launch Browser Command', async () => { + const diagnostic = typemoq.Mock.ofType(); + + const command = commandFactory.createCommand(diagnostic.object, { type: 'launch', options: 'x' }); + expect(command).to.be.instanceOf(LaunchBrowserCommand); + }); +}); diff --git a/src/test/application/diagnostics/commands/ignore.unit.test.ts b/src/test/application/diagnostics/commands/ignore.unit.test.ts new file mode 100644 index 000000000000..586071b9abe0 --- /dev/null +++ b/src/test/application/diagnostics/commands/ignore.unit.test.ts @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as typemoq from 'typemoq'; +import { IgnoreDiagnosticCommand } from '../../../../client/application/diagnostics/commands/ignore'; +import { DiagnosticScope, IDiagnostic, IDiagnosticCommand, IDiagnosticFilterService } from '../../../../client/application/diagnostics/types'; +import { IServiceContainer } from '../../../../client/ioc/types'; + +suite('Application Diagnostics - Commands Ignore', () => { + let ignoreCommand: IDiagnosticCommand; + let serviceContainer: typemoq.IMock; + let diagnostic: typemoq.IMock; + setup(() => { + serviceContainer = typemoq.Mock.ofType(); + + diagnostic = typemoq.Mock.ofType(); + ignoreCommand = new IgnoreDiagnosticCommand(diagnostic.object, serviceContainer.object, DiagnosticScope.Global); + }); + + test('Invoking Command should invoke the filter Service', async () => { + const filterService = typemoq.Mock.ofType(); + serviceContainer.setup(s => s.get(typemoq.It.isValue(IDiagnosticFilterService))) + .returns(() => filterService.object) + .verifiable(typemoq.Times.once()); + diagnostic.setup(d => d.code).returns(() => 'xyz') + .verifiable(typemoq.Times.once()); + filterService.setup(s => s.ignoreDiagnostic(typemoq.It.isValue('xyz'), typemoq.It.isValue(DiagnosticScope.Global))) + .verifiable(typemoq.Times.once()); + + await ignoreCommand.invoke(); + serviceContainer.verifyAll(); + }); +}); diff --git a/src/test/application/diagnostics/commands/launchBrowser.unit.test.ts b/src/test/application/diagnostics/commands/launchBrowser.unit.test.ts new file mode 100644 index 000000000000..665f7937934a --- /dev/null +++ b/src/test/application/diagnostics/commands/launchBrowser.unit.test.ts @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import * as typemoq from 'typemoq'; +import { LaunchBrowserCommand } from '../../../../client/application/diagnostics/commands/launchBrowser'; +import { IDiagnostic, IDiagnosticCommand } from '../../../../client/application/diagnostics/types'; +import { IBrowserService } from '../../../../client/common/types'; +import { IServiceContainer } from '../../../../client/ioc/types'; + +suite('Application Diagnostics - Commands Launch Browser', () => { + let cmd: IDiagnosticCommand; + let serviceContainer: typemoq.IMock; + let diagnostic: typemoq.IMock; + const url = 'xyz://abc'; + setup(() => { + serviceContainer = typemoq.Mock.ofType(); + diagnostic = typemoq.Mock.ofType(); + cmd = new LaunchBrowserCommand(diagnostic.object, serviceContainer.object, url); + }); + + test('Invoking Command should launch the browser', async () => { + const browser = typemoq.Mock.ofType(); + serviceContainer.setup(s => s.get(typemoq.It.isValue(IBrowserService))) + .returns(() => browser.object) + .verifiable(typemoq.Times.once()); + browser.setup(s => s.launch(typemoq.It.isValue(url))) + .verifiable(typemoq.Times.once()); + + await cmd.invoke(); + serviceContainer.verifyAll(); + }); +}); diff --git a/src/test/application/diagnostics/filter.unit.test.ts b/src/test/application/diagnostics/filter.unit.test.ts new file mode 100644 index 000000000000..b42136fc6d8d --- /dev/null +++ b/src/test/application/diagnostics/filter.unit.test.ts @@ -0,0 +1,119 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:max-func-body-length + +import { expect } from 'chai'; +import * as typemoq from 'typemoq'; +import { DiagnosticFilterService, FilterKeys } from '../../../client/application/diagnostics/filter'; +import { DiagnosticScope, IDiagnosticFilterService } from '../../../client/application/diagnostics/types'; +import { IPersistentState, IPersistentStateFactory } from '../../../client/common/types'; +import { IServiceContainer } from '../../../client/ioc/types'; + +suite('Application Diagnostics - Filter', () => { + let globalState: typemoq.IMock>; + let workspaceState: typemoq.IMock>; + + [ + { name: 'Global', scope: DiagnosticScope.Global, state: () => globalState, otherState: () => workspaceState }, + { name: 'Workspace', scope: DiagnosticScope.WorkspaceFolder, state: () => workspaceState, otherState: () => globalState } + ] + .forEach(item => { + let serviceContainer: typemoq.IMock; + let filterService: IDiagnosticFilterService; + + setup(() => { + globalState = typemoq.Mock.ofType>(); + workspaceState = typemoq.Mock.ofType>(); + + serviceContainer = typemoq.Mock.ofType(); + const stateFactory = typemoq.Mock.ofType(); + + stateFactory.setup(f => f.createGlobalPersistentState(typemoq.It.isValue(FilterKeys.GlobalDiagnosticFilter), typemoq.It.isValue([]))) + .returns(() => globalState.object); + stateFactory.setup(f => f.createWorkspacePersistentState(typemoq.It.isValue(FilterKeys.WorkspaceDiagnosticFilter), typemoq.It.isValue([]))) + .returns(() => workspaceState.object); + serviceContainer.setup(s => s.get(typemoq.It.isValue(IPersistentStateFactory))) + .returns(() => stateFactory.object); + + filterService = new DiagnosticFilterService(serviceContainer.object); + }); + + test(`ignoreDiagnostic must save codes in ${item.name} Persistent State`, async () => { + const code = 'xyz'; + item.state().setup(g => g.value).returns(() => []) + .verifiable(typemoq.Times.once()); + item.state().setup(g => g.updateValue(typemoq.It.isValue([code]))) + .verifiable(typemoq.Times.once()); + + item.otherState().setup(g => g.value) + .verifiable(typemoq.Times.never()); + item.otherState().setup(g => g.updateValue(typemoq.It.isAny())) + .verifiable(typemoq.Times.never()); + + await filterService.ignoreDiagnostic(code, item.scope); + + item.state().verifyAll(); + }); + test('shouldIgnoreDiagnostic should return \'false\' when code does not exist in any State', async () => { + const code = 'xyz'; + item.state().setup(g => g.value).returns(() => []) + .verifiable(typemoq.Times.once()); + item.otherState().setup(g => g.value).returns(() => []) + .verifiable(typemoq.Times.once()); + + const ignore = await filterService.shouldIgnoreDiagnostic(code); + + expect(ignore).to.be.equal(false, 'Incorrect value'); + item.state().verifyAll(); + }); + test(`shouldIgnoreDiagnostic should return \'true\' when code exist in ${item.name} State`, async () => { + const code = 'xyz'; + item.state().setup(g => g.value).returns(() => ['a', 'b', 'c', code]) + .verifiable(typemoq.Times.once()); + item.otherState().setup(g => g.value).returns(() => []) + .verifiable(typemoq.Times.once()); + + const ignore = await filterService.shouldIgnoreDiagnostic(code); + + expect(ignore).to.be.equal(true, 'Incorrect value'); + item.state().verifyAll(); + }); + + test('shouldIgnoreDiagnostic should return \'true\' when code exist in any State', async () => { + const code = 'xyz'; + item.state().setup(g => g.value).returns(() => []) + .verifiable(typemoq.Times.atLeast(0)); + item.otherState().setup(g => g.value).returns(() => ['a', 'b', 'c', code]) + .verifiable(typemoq.Times.atLeast(0)); + + const ignore = await filterService.shouldIgnoreDiagnostic(code); + + expect(ignore).to.be.equal(true, 'Incorrect value'); + item.state().verifyAll(); + }); + + test(`ignoreDiagnostic must append codes in ${item.name} Persistent State`, async () => { + const code = 'xyz'; + const currentState = ['a', 'b', 'c']; + item.state().setup(g => g.value).returns(() => currentState) + .verifiable(typemoq.Times.atLeastOnce()); + item.state().setup(g => g.updateValue(typemoq.It.isAny())) + .callback(value => { + expect(value).to.deep.equal(currentState.concat([code])); + }) + .verifiable(typemoq.Times.atLeastOnce()); + + item.otherState().setup(g => g.value) + .verifiable(typemoq.Times.never()); + item.otherState().setup(g => g.updateValue(typemoq.It.isAny())) + .verifiable(typemoq.Times.never()); + + await filterService.ignoreDiagnostic(code, item.scope); + + item.state().verifyAll(); + }); + }); +}); diff --git a/src/test/application/diagnostics/promptHandler.unit.test.ts b/src/test/application/diagnostics/promptHandler.unit.test.ts new file mode 100644 index 000000000000..ce07d41d91cb --- /dev/null +++ b/src/test/application/diagnostics/promptHandler.unit.test.ts @@ -0,0 +1,131 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:insecure-random max-func-body-length + +import * as typemoq from 'typemoq'; +import { DiagnosticSeverity } from 'vscode'; +import { DiagnosticCommandPromptHandlerService, MessageCommandPrompt } from '../../../client/application/diagnostics/promptHandler'; +import { DiagnosticScope, IDiagnostic, IDiagnosticCommand, IDiagnosticHandlerService } from '../../../client/application/diagnostics/types'; +import { IApplicationShell } from '../../../client/common/application/types'; +import { EnumEx } from '../../../client/common/enumUtils'; +import { IServiceContainer } from '../../../client/ioc/types'; + +suite('Application Diagnostics - PromptHandler', () => { + let serviceContainer: typemoq.IMock; + let appShell: typemoq.IMock; + let promptHandler: IDiagnosticHandlerService; + + setup(() => { + serviceContainer = typemoq.Mock.ofType(); + appShell = typemoq.Mock.ofType(); + + serviceContainer.setup(s => s.get(typemoq.It.isValue(IApplicationShell))) + .returns(() => appShell.object); + + promptHandler = new DiagnosticCommandPromptHandlerService(serviceContainer.object); + }); + + EnumEx.getNamesAndValues(DiagnosticSeverity).forEach(severity => { + test(`Handling a diagnositic of severity '${severity.name}' should display a message without any buttons`, async () => { + const diagnostic: IDiagnostic = { code: '1', message: 'one', scope: DiagnosticScope.Global, severity: severity.value }; + switch (severity.value) { + case DiagnosticSeverity.Error: { + appShell.setup(a => a.showErrorMessage(typemoq.It.isValue(diagnostic.message))) + .verifiable(typemoq.Times.once()); + break; + } + case DiagnosticSeverity.Warning: { + appShell.setup(a => a.showWarningMessage(typemoq.It.isValue(diagnostic.message))) + .verifiable(typemoq.Times.once()); + break; + } + default: { + appShell.setup(a => a.showInformationMessage(typemoq.It.isValue(diagnostic.message))) + .verifiable(typemoq.Times.once()); + break; + } + } + + await promptHandler.handle(diagnostic); + appShell.verifyAll(); + }); + test(`Handling a diagnositic of severity '${severity.name}' should display a custom message with buttons`, async () => { + const diagnostic: IDiagnostic = { code: '1', message: 'one', scope: DiagnosticScope.Global, severity: severity.value }; + const options: MessageCommandPrompt = { + commandPrompts: [ + { prompt: 'Yes' }, + { prompt: 'No' } + ], + message: 'Custom Message' + }; + + switch (severity.value) { + case DiagnosticSeverity.Error: { + appShell.setup(a => a.showErrorMessage(typemoq.It.isValue(options.message!), + typemoq.It.isValue('Yes'), typemoq.It.isValue('No'))) + .verifiable(typemoq.Times.once()); + break; + } + case DiagnosticSeverity.Warning: { + appShell.setup(a => a.showWarningMessage(typemoq.It.isValue(options.message!), + typemoq.It.isValue('Yes'), typemoq.It.isValue('No'))) + .verifiable(typemoq.Times.once()); + break; + } + default: { + appShell.setup(a => a.showInformationMessage(typemoq.It.isValue(options.message!), + typemoq.It.isValue('Yes'), typemoq.It.isValue('No'))) + .verifiable(typemoq.Times.once()); + break; + } + } + + await promptHandler.handle(diagnostic, options); + appShell.verifyAll(); + }); + test(`Handling a diagnositic of severity '${severity.name}' should display a custom message with buttons and invoke selected command`, async () => { + const diagnostic: IDiagnostic = { code: '1', message: 'one', scope: DiagnosticScope.Global, severity: severity.value }; + const command = typemoq.Mock.ofType(); + const options: MessageCommandPrompt = { + commandPrompts: [ + { prompt: 'Yes', command: command.object }, + { prompt: 'No', command: command.object } + ], + message: 'Custom Message' + }; + command.setup(c => c.invoke()) + .verifiable(typemoq.Times.once()); + + switch (severity.value) { + case DiagnosticSeverity.Error: { + appShell.setup(a => a.showErrorMessage(typemoq.It.isValue(options.message!), + typemoq.It.isValue('Yes'), typemoq.It.isValue('No'))) + .returns(() => Promise.resolve('Yes')) + .verifiable(typemoq.Times.once()); + break; + } + case DiagnosticSeverity.Warning: { + appShell.setup(a => a.showWarningMessage(typemoq.It.isValue(options.message!), + typemoq.It.isValue('Yes'), typemoq.It.isValue('No'))) + .returns(() => Promise.resolve('Yes')) + .verifiable(typemoq.Times.once()); + break; + } + default: { + appShell.setup(a => a.showInformationMessage(typemoq.It.isValue(options.message!), + typemoq.It.isValue('Yes'), typemoq.It.isValue('No'))) + .returns(() => Promise.resolve('Yes')) + .verifiable(typemoq.Times.once()); + break; + } + } + + await promptHandler.handle(diagnostic, options); + appShell.verifyAll(); + command.verifyAll(); + }); + }); +}); diff --git a/src/test/common/extensions.test.ts b/src/test/common/extensions.unit.test.ts similarity index 53% rename from src/test/common/extensions.test.ts rename to src/test/common/extensions.unit.test.ts index 5724f3291274..7018b8d002a8 100644 --- a/src/test/common/extensions.test.ts +++ b/src/test/common/extensions.unit.test.ts @@ -39,4 +39,36 @@ suite('String Extensions', () => { const fileToTest = 'c:\\users\\user namne\\conda path\\scripts\\python.exe'; expect(fileToTest.fileToCommandArgument()).to.be.equal(`"${fileToTest.replace(/\\/g, '/')}"`); }); + test('Should replace all back slashes with forward slashes (irrespective of OS) and quoted when file has spaces', () => { + const fileToTest = 'c:\\users\\user namne\\conda path\\scripts\\python.exe'; + expect(fileToTest.fileToCommandArgument()).to.be.equal(`"${fileToTest.replace(/\\/g, '/')}"`); + }); + test('Should leave string unchanged', () => { + expect('something {0}'.format()).to.be.equal('something {0}'); + }); + test('String should be formatted to contain first argument', () => { + const formatString = 'something {0}'; + const expectedString = 'something one'; + expect(formatString.format('one')).to.be.equal(expectedString); + }); + test('String should be formatted to contain first argument even with too many args', () => { + const formatString = 'something {0}'; + const expectedString = 'something one'; + expect(formatString.format('one', 'two')).to.be.equal(expectedString); + }); + test('String should be formatted to contain second argument', () => { + const formatString = 'something {1}'; + const expectedString = 'something two'; + expect(formatString.format('one', 'two')).to.be.equal(expectedString); + }); + test('String should be formatted to contain second argument even with too many args', () => { + const formatString = 'something {1}'; + const expectedString = 'something two'; + expect(formatString.format('one', 'two', 'three')).to.be.equal(expectedString); + }); + test('String should be formatted with multiple args', () => { + const formatString = 'something {1}, {0}'; + const expectedString = 'something two, one'; + expect(formatString.format('one', 'two', 'three')).to.be.equal(expectedString); + }); }); diff --git a/typings/extensions.d.ts b/typings/extensions.d.ts index 046525bebc23..3a5ff4282fa9 100644 --- a/typings/extensions.d.ts +++ b/typings/extensions.d.ts @@ -26,6 +26,11 @@ declare interface String { * E.g. if an argument contains a space, then it will be enclosed within double quotes. */ fileToCommandArgument(): string; + /** + * String.format() implementation. + * Tokens such as {0}, {1} will be replaced with corresponding positional arguments. + */ + format(...args: string[]): string; } // tslint:disable-next-line:interface-name From db4c7b33e4d70f5bac105a5e0875b1a7048aa868 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 18 Jun 2018 12:01:50 -0700 Subject: [PATCH 351/433] Document the French translation (#1998) --- README.md | 1 + news/1 Enhancements/1959.md | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 news/1 Enhancements/1959.md diff --git a/README.md b/README.md index 637469599877..aac7bfd8d044 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ contributors (if you would like to contribute a translation, see the * `en` * `es` +* `fr` * `it` * `ja` * `ko-kr` diff --git a/news/1 Enhancements/1959.md b/news/1 Enhancements/1959.md new file mode 100644 index 000000000000..b24f3458ba05 --- /dev/null +++ b/news/1 Enhancements/1959.md @@ -0,0 +1,4 @@ +Add a French translation (thanks to [Jérémy](https://github.com/PixiBixi) for +the initial patch, and thanks to [Nathan Gaberel](https://github.com/n6g7), +[Bruno Alla](https://github.com/browniebroke), and +[Tarek Ziade](https://github.com/tarekziade) for reviews). From 659db30b47c4f51d5fd923f2530cd54dce82e8e9 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 18 Jun 2018 12:54:30 -0700 Subject: [PATCH 352/433] Add a step to validate fixes --- .github/release_plan.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/release_plan.md b/.github/release_plan.md index f56081dd8c5f..f0de19364fb5 100644 --- a/.github/release_plan.md +++ b/.github/release_plan.md @@ -3,6 +3,7 @@ - [ ] Review the state of the current [milestone](https://github.com/Microsoft/vscode-python/milestones) - [ ] Go through telemetry for GDPR - [ ] Go through all [merged pull requests](https://github.com/Microsoft/vscode-python/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged) and add the [`validate fix` label](https://github.com/Microsoft/vscode-python/labels/validate%20fix) as appropriate as well as checking for news entries +- [ ] Validated [fixed issues](https://github.com/Microsoft/vscode-python/issues?q=label%3A%22validate+fix%22+is%3Aclosed) - [ ] Triage [unverified issues](https://github.com/Microsoft/vscode-python/labels/needs%20verification) ## Planning @@ -17,6 +18,7 @@ - [ ] Review the state of the current [milestone](https://github.com/Microsoft/vscode-python/milestones) - [ ] Go through telemetry for GDPR - [ ] Go through all [merged pull requests](https://github.com/Microsoft/vscode-python/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged) and add the [`validate fix` label](https://github.com/Microsoft/vscode-python/labels/validate%20fix) as appropriate as well as checking for news entries +- [ ] Validated [fixed issues](https://github.com/Microsoft/vscode-python/issues?q=label%3A%22validate+fix%22+is%3Aclosed) - [ ] Triage [unverified issues](https://github.com/Microsoft/vscode-python/labels/needs%20verification) ## Planning @@ -28,6 +30,7 @@ - [ ] Review the state of the current [milestone](https://github.com/Microsoft/vscode-python/milestones) - [ ] Go through telemetry for GDPR - [ ] Go through all [merged pull requests](https://github.com/Microsoft/vscode-python/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged) and add the [`validate fix` label](https://github.com/Microsoft/vscode-python/labels/validate%20fix) as appropriate as well as checking for news entries +- [ ] Validated [fixed issues](https://github.com/Microsoft/vscode-python/issues?q=label%3A%22validate+fix%22+is%3Aclosed) - [ ] Triage [unverified issues](https://github.com/Microsoft/vscode-python/labels/needs%20verification) ## Legal @@ -45,6 +48,7 @@ - [ ] Go through telemetry for GDPR - [ ] Merge any last-minute [pull requests](https://github.com/Microsoft/vscode-python/pulls) - [ ] Go through all [merged pull requests](https://github.com/Microsoft/vscode-python/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged) and add the [`validate fix` label](https://github.com/Microsoft/vscode-python/labels/validate%20fix) as appropriate as well as checking for news entries +- [ ] Validated [fixed issues](https://github.com/Microsoft/vscode-python/issues?q=label%3A%22validate+fix%22+is%3Aclosed) - [ ] Triage [unverified issues](https://github.com/Microsoft/vscode-python/labels/needs%20verification) ## Prep for the release candidate From 6ba5c5ae0a4860f5ed43491fdf16e28c76f6ce6c Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 18 Jun 2018 12:55:34 -0700 Subject: [PATCH 353/433] Fix a grammar mistake --- .github/release_plan.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/release_plan.md b/.github/release_plan.md index f0de19364fb5..249483bb52dd 100644 --- a/.github/release_plan.md +++ b/.github/release_plan.md @@ -3,7 +3,7 @@ - [ ] Review the state of the current [milestone](https://github.com/Microsoft/vscode-python/milestones) - [ ] Go through telemetry for GDPR - [ ] Go through all [merged pull requests](https://github.com/Microsoft/vscode-python/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged) and add the [`validate fix` label](https://github.com/Microsoft/vscode-python/labels/validate%20fix) as appropriate as well as checking for news entries -- [ ] Validated [fixed issues](https://github.com/Microsoft/vscode-python/issues?q=label%3A%22validate+fix%22+is%3Aclosed) +- [ ] Validate [fixed issues](https://github.com/Microsoft/vscode-python/issues?q=label%3A%22validate+fix%22+is%3Aclosed) - [ ] Triage [unverified issues](https://github.com/Microsoft/vscode-python/labels/needs%20verification) ## Planning @@ -18,7 +18,7 @@ - [ ] Review the state of the current [milestone](https://github.com/Microsoft/vscode-python/milestones) - [ ] Go through telemetry for GDPR - [ ] Go through all [merged pull requests](https://github.com/Microsoft/vscode-python/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged) and add the [`validate fix` label](https://github.com/Microsoft/vscode-python/labels/validate%20fix) as appropriate as well as checking for news entries -- [ ] Validated [fixed issues](https://github.com/Microsoft/vscode-python/issues?q=label%3A%22validate+fix%22+is%3Aclosed) +- [ ] Validate [fixed issues](https://github.com/Microsoft/vscode-python/issues?q=label%3A%22validate+fix%22+is%3Aclosed) - [ ] Triage [unverified issues](https://github.com/Microsoft/vscode-python/labels/needs%20verification) ## Planning @@ -30,7 +30,7 @@ - [ ] Review the state of the current [milestone](https://github.com/Microsoft/vscode-python/milestones) - [ ] Go through telemetry for GDPR - [ ] Go through all [merged pull requests](https://github.com/Microsoft/vscode-python/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged) and add the [`validate fix` label](https://github.com/Microsoft/vscode-python/labels/validate%20fix) as appropriate as well as checking for news entries -- [ ] Validated [fixed issues](https://github.com/Microsoft/vscode-python/issues?q=label%3A%22validate+fix%22+is%3Aclosed) +- [ ] Validate [fixed issues](https://github.com/Microsoft/vscode-python/issues?q=label%3A%22validate+fix%22+is%3Aclosed) - [ ] Triage [unverified issues](https://github.com/Microsoft/vscode-python/labels/needs%20verification) ## Legal @@ -48,7 +48,7 @@ - [ ] Go through telemetry for GDPR - [ ] Merge any last-minute [pull requests](https://github.com/Microsoft/vscode-python/pulls) - [ ] Go through all [merged pull requests](https://github.com/Microsoft/vscode-python/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged) and add the [`validate fix` label](https://github.com/Microsoft/vscode-python/labels/validate%20fix) as appropriate as well as checking for news entries -- [ ] Validated [fixed issues](https://github.com/Microsoft/vscode-python/issues?q=label%3A%22validate+fix%22+is%3Aclosed) +- [ ] Validate [fixed issues](https://github.com/Microsoft/vscode-python/issues?q=label%3A%22validate+fix%22+is%3Aclosed) - [ ] Triage [unverified issues](https://github.com/Microsoft/vscode-python/labels/needs%20verification) ## Prep for the release candidate From b471c3f8f8fc30458aae91cd7b92fd9afd095a22 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 18 Jun 2018 14:24:11 -0700 Subject: [PATCH 354/433] Update message displayed in the Experimental Debugger banner (#1996) Fixes #1968 --- .../diagnostics/checks/envPathVariable.ts | 2 +- src/client/debugger/banner.ts | 18 ++-- src/test/debugger/banner.unit.test.ts | 85 ++++++++++++++++--- 3 files changed, 85 insertions(+), 20 deletions(-) diff --git a/src/client/application/diagnostics/checks/envPathVariable.ts b/src/client/application/diagnostics/checks/envPathVariable.ts index 3aa249ea8923..f8387e73d429 100644 --- a/src/client/application/diagnostics/checks/envPathVariable.ts +++ b/src/client/application/diagnostics/checks/envPathVariable.ts @@ -17,7 +17,7 @@ import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '. import { DiagnosticScope, IDiagnostic, IDiagnosticHandlerService } from '../types'; const InvalidEnvPathVariableMessage = 'The environment variable \'{0}\' seems to have some paths containing characters (\';\', \'"\', \'%\' or \';;\').' + - ' The existence of such characters are known to have caused the {1} extension not load.'; + ' The existence of such characters are known to have caused the {1} extension to not load.'; export class InvalidEnvironmentPathVariableDiagnostic extends BaseDiagnostic { constructor(message) { diff --git a/src/client/debugger/banner.ts b/src/client/debugger/banner.ts index 3062ed83cf87..5d45677749c6 100644 --- a/src/client/debugger/banner.ts +++ b/src/client/debugger/banner.ts @@ -22,7 +22,7 @@ export enum PersistentStateKeys { @injectable() export class ExperimentalDebuggerBanner implements IExperimentalDebuggerBanner { private initialized?: boolean; - private disabled?: boolean; + private disabledInCurrentSession?: boolean; public get enabled(): boolean { const factory = this.serviceContainer.get(IPersistentStateFactory); return factory.createGlobalPersistentState(PersistentStateKeys.ShowBanner, true).value; @@ -39,10 +39,10 @@ export class ExperimentalDebuggerBanner implements IExperimentalDebuggerBanner { return; } const debuggerService = this.serviceContainer.get(IDebugService); - const disposable = debuggerService.onDidStartDebugSession(e => { + const disposable = debuggerService.onDidStartDebugSession(async e => { if (e.type === ExperimentalDebuggerType) { const logger = this.serviceContainer.get(ILogger); - this.onDebugSessionStarted() + await this.onDebugSessionStarted() .catch(ex => logger.logError('Error in debugger Banner', ex)); } }); @@ -51,9 +51,9 @@ export class ExperimentalDebuggerBanner implements IExperimentalDebuggerBanner { } public async showBanner(): Promise { const appShell = this.serviceContainer.get(IApplicationShell); - const yes = 'Take Survey'; + const yes = 'Yes, take survey now'; const no = 'No thanks'; - const response = await appShell.showInformationMessage('Can you take 2 minutes to tell us how the Experimental Debugger is working for you?', yes, no); + const response = await appShell.showInformationMessage('Can you please take 2 minutes to tell us how the Experimental Debugger is working for you?', yes, no); switch (response) { case yes: { @@ -66,12 +66,13 @@ export class ExperimentalDebuggerBanner implements IExperimentalDebuggerBanner { break; } default: { - return; + // Disable for the current session. + this.disabledInCurrentSession = true; } } } public async shouldShowBanner(): Promise { - if (!this.enabled) { + if (!this.enabled || this.disabledInCurrentSession) { return false; } const [threshold, debuggerCounter] = await Promise.all([this.getDebuggerLaunchThresholdCounter(), this.getGetDebuggerLaunchCounter()]); @@ -81,7 +82,6 @@ export class ExperimentalDebuggerBanner implements IExperimentalDebuggerBanner { public async disable(): Promise { const factory = this.serviceContainer.get(IPersistentStateFactory); await factory.createGlobalPersistentState(PersistentStateKeys.ShowBanner, false).updateValue(false); - this.disabled = true; } public async launchSurvey(): Promise { const debuggerLaunchCounter = await this.getGetDebuggerLaunchCounter(); @@ -115,7 +115,7 @@ export class ExperimentalDebuggerBanner implements IExperimentalDebuggerBanner { return isNaN(num) ? crypto.randomBytes(1).toString('hex').slice(-1) : lastHexValue; } private async onDebugSessionStarted(): Promise { - if (this.disabled) { + if (!this.enabled) { return; } await this.incrementDebuggerLaunchCounter(); diff --git a/src/test/debugger/banner.unit.test.ts b/src/test/debugger/banner.unit.test.ts index f2c13cf21bd5..886d2ff16eba 100644 --- a/src/test/debugger/banner.unit.test.ts +++ b/src/test/debugger/banner.unit.test.ts @@ -8,7 +8,7 @@ import { expect } from 'chai'; import * as typemoq from 'typemoq'; import { DebugSession } from 'vscode'; -import { IDebugService } from '../../client/common/application/types'; +import { IApplicationShell, IDebugService } from '../../client/common/application/types'; import { IBrowserService, IDisposableRegistry, ILogger, IPersistentState, IPersistentStateFactory } from '../../client/common/types'; import { ExperimentalDebuggerBanner, PersistentStateKeys } from '../../client/debugger/banner'; import { ExperimentalDebuggerType } from '../../client/debugger/Common/constants'; @@ -22,7 +22,12 @@ suite('Debugging - Banner', () => { let launchThresholdCounterState: typemoq.IMock>; let showBannerState: typemoq.IMock>; let debugService: typemoq.IMock; + let appShell: typemoq.IMock; let banner: IExperimentalDebuggerBanner; + const message = 'Can you please take 2 minutes to tell us how the Experimental Debugger is working for you?'; + const yes = 'Yes, take survey now'; + const no = 'No thanks'; + setup(() => { serviceContainer = typemoq.Mock.ofType(); browser = typemoq.Mock.ofType(); @@ -31,6 +36,7 @@ suite('Debugging - Banner', () => { launchCounterState = typemoq.Mock.ofType>(); showBannerState = typemoq.Mock.ofType>(); + appShell = typemoq.Mock.ofType(); launchThresholdCounterState = typemoq.Mock.ofType>(); const factory = typemoq.Mock.ofType(); factory @@ -48,6 +54,7 @@ suite('Debugging - Banner', () => { serviceContainer.setup(s => s.get(typemoq.It.isValue(IDebugService))).returns(() => debugService.object); serviceContainer.setup(s => s.get(typemoq.It.isValue(ILogger))).returns(() => logger.object); serviceContainer.setup(s => s.get(typemoq.It.isValue(IDisposableRegistry))).returns(() => []); + serviceContainer.setup(s => s.get(typemoq.It.isValue(IApplicationShell))).returns(() => appShell.object); banner = new ExperimentalDebuggerBanner(serviceContainer.object); }); @@ -63,28 +70,28 @@ suite('Debugging - Banner', () => { browser.verifyAll(); }); test('Increment Debugger Launch Counter when debug session starts', async () => { - let onDidStartDebugSessionCb: (e: DebugSession) => void; + let onDidStartDebugSessionCb: (e: DebugSession) => Promise; debugService.setup(d => d.onDidStartDebugSession(typemoq.It.isAny())) .callback(cb => onDidStartDebugSessionCb = cb) .verifiable(typemoq.Times.once()); const debuggerLaunchCounter = 1234; launchCounterState.setup(l => l.value).returns(() => debuggerLaunchCounter) - .verifiable(typemoq.Times.once()); + .verifiable(typemoq.Times.atLeastOnce()); launchCounterState.setup(l => l.updateValue(typemoq.It.isValue(debuggerLaunchCounter + 1))) .verifiable(typemoq.Times.once()); showBannerState.setup(s => s.value).returns(() => true) - .verifiable(typemoq.Times.once()); + .verifiable(typemoq.Times.atLeastOnce()); banner.initialize(); - onDidStartDebugSessionCb!({ type: ExperimentalDebuggerType } as any); + await onDidStartDebugSessionCb!({ type: ExperimentalDebuggerType } as any); launchCounterState.verifyAll(); browser.verifyAll(); debugService.verifyAll(); showBannerState.verifyAll(); }); - test('Do not Increment Debugger Launch Counter when debug session starts when Banner is disabled', async () => { + test('Do not Increment Debugger Launch Counter when debug session starts and Banner is disabled', async () => { debugService.setup(d => d.onDidStartDebugSession(typemoq.It.isAny())) .verifiable(typemoq.Times.never()); @@ -103,7 +110,7 @@ suite('Debugging - Banner', () => { debugService.verifyAll(); showBannerState.verifyAll(); }); - test('shouldShowBanner returnes false when Banner is disabled', async () => { + test('shouldShowBanner must return false when Banner is disabled', async () => { showBannerState.setup(s => s.value).returns(() => false) .verifiable(typemoq.Times.once()); @@ -111,7 +118,7 @@ suite('Debugging - Banner', () => { showBannerState.verifyAll(); }); - test('shouldShowBanner returnes false when Banner is enabled and debug counter is not same as threshold', async () => { + test('shouldShowBanner must return false when Banner is enabled and debug counter is not same as threshold', async () => { showBannerState.setup(s => s.value).returns(() => true) .verifiable(typemoq.Times.once()); launchCounterState.setup(l => l.value).returns(() => 1) @@ -125,7 +132,7 @@ suite('Debugging - Banner', () => { launchCounterState.verifyAll(); launchThresholdCounterState.verifyAll(); }); - test('shouldShowBanner returnes true when Banner is enabled and debug counter is same as threshold', async () => { + test('shouldShowBanner must return true when Banner is enabled and debug counter is same as threshold', async () => { showBannerState.setup(s => s.value).returns(() => true) .verifiable(typemoq.Times.once()); launchCounterState.setup(l => l.value).returns(() => 10) @@ -139,7 +146,65 @@ suite('Debugging - Banner', () => { launchCounterState.verifyAll(); launchThresholdCounterState.verifyAll(); }); - test('Disabling banner should store value of \'false\' in global store', async () => { + test('showBanner must be invoked when shouldShowBanner returns true', async () => { + let onDidStartDebugSessionCb: (e: DebugSession) => Promise; + const currentLaunchCounter = 50; + + debugService.setup(d => d.onDidStartDebugSession(typemoq.It.isAny())) + .callback(cb => onDidStartDebugSessionCb = cb) + .verifiable(typemoq.Times.atLeastOnce()); + showBannerState.setup(s => s.value).returns(() => true) + .verifiable(typemoq.Times.atLeastOnce()); + launchCounterState.setup(l => l.value).returns(() => currentLaunchCounter) + .verifiable(typemoq.Times.atLeastOnce()); + launchThresholdCounterState.setup(t => t.value).returns(() => 10) + .verifiable(typemoq.Times.atLeastOnce()); + launchCounterState.setup(l => l.updateValue(typemoq.It.isValue(currentLaunchCounter + 1))) + .returns(() => Promise.resolve()) + .verifiable(typemoq.Times.atLeastOnce()); + + appShell.setup(a => a.showInformationMessage(typemoq.It.isValue(message), typemoq.It.isValue(yes), typemoq.It.isValue(no))) + .verifiable(typemoq.Times.once()); + banner.initialize(); + await onDidStartDebugSessionCb!({ type: ExperimentalDebuggerType } as any); + + appShell.verifyAll(); + showBannerState.verifyAll(); + launchCounterState.verifyAll(); + launchThresholdCounterState.verifyAll(); + }); + test('showBanner must not be invoked the second time after dismissing the message', async () => { + let onDidStartDebugSessionCb: (e: DebugSession) => Promise; + let currentLaunchCounter = 50; + + debugService.setup(d => d.onDidStartDebugSession(typemoq.It.isAny())) + .callback(cb => onDidStartDebugSessionCb = cb) + .verifiable(typemoq.Times.atLeastOnce()); + showBannerState.setup(s => s.value).returns(() => true) + .verifiable(typemoq.Times.atLeastOnce()); + launchCounterState.setup(l => l.value).returns(() => currentLaunchCounter) + .verifiable(typemoq.Times.atLeastOnce()); + launchThresholdCounterState.setup(t => t.value).returns(() => 10) + .verifiable(typemoq.Times.atLeastOnce()); + launchCounterState.setup(l => l.updateValue(typemoq.It.isAny())) + .callback(() => currentLaunchCounter = currentLaunchCounter + 1); + + appShell.setup(a => a.showInformationMessage(typemoq.It.isValue(message), typemoq.It.isValue(yes), typemoq.It.isValue(no))) + .returns(() => Promise.resolve(undefined)) + .verifiable(typemoq.Times.once()); + banner.initialize(); + await onDidStartDebugSessionCb!({ type: ExperimentalDebuggerType } as any); + await onDidStartDebugSessionCb!({ type: ExperimentalDebuggerType } as any); + await onDidStartDebugSessionCb!({ type: ExperimentalDebuggerType } as any); + await onDidStartDebugSessionCb!({ type: ExperimentalDebuggerType } as any); + + appShell.verifyAll(); + showBannerState.verifyAll(); + launchCounterState.verifyAll(); + launchThresholdCounterState.verifyAll(); + expect(currentLaunchCounter).to.be.equal(54); + }); + test('Disabling banner must store value of \'false\' in global store', async () => { showBannerState.setup(s => s.updateValue(typemoq.It.isValue(false))) .verifiable(typemoq.Times.once()); From 319fd72fa81e8d94aedfb844790fd538ac3f19e5 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 18 Jun 2018 14:32:28 -0700 Subject: [PATCH 355/433] Add example of how to turn on a feature --- news/1 Enhancements/156.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/news/1 Enhancements/156.md b/news/1 Enhancements/156.md index da404f67686e..c5c4258764b7 100644 --- a/news/1 Enhancements/156.md +++ b/news/1 Enhancements/156.md @@ -1 +1,8 @@ -Add support for the `editor.codeActionsOnSave.source.organizeImports` setting (thanks [Nathan Gaberel](https://github.com/n6g7)). +Add support for the `"source.organizeImports"` setting for `"editor.codeActionsOnSave"` (thanks [Nathan Gaberel](https://github.com/n6g7)); you can turn this on just for Python using: +```json +"[python]": { + "editor.codeActionsOnSave": { + "source.organizeImports": true + } +} +``` From 42764f71d073588f9e361e55ecf773c886f92f99 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Mon, 18 Jun 2018 14:54:50 -0700 Subject: [PATCH 356/433] Propagate files exclusion to the language server (#1977) * LS symbol providers * Provide exclude files option to LS * Document Jedi flag * Casing * Add more exclusions * File exclusion test * PR feedback * Comment * Keep container around * Test --- CONTRIBUTING - PYTHON_ANALYSIS.md | 1 + src/client/activation/analysis.ts | 118 ++++++++++++------ src/client/activation/downloader.ts | 6 +- src/test/activation/excludeFiles.ptvs.test.ts | 95 ++++++++++++++ .../pythonFiles/exclusions/Lib/fileLib.py | 1 + .../Lib/site-packages/sitePackages.py | 1 + .../pythonFiles/exclusions/dir1/dir1file.py | 1 + .../exclusions/dir1/dir2/dir2file.py | 1 + src/test/pythonFiles/exclusions/one.py | 1 + 9 files changed, 183 insertions(+), 42 deletions(-) create mode 100644 src/test/activation/excludeFiles.ptvs.test.ts create mode 100644 src/test/pythonFiles/exclusions/Lib/fileLib.py create mode 100644 src/test/pythonFiles/exclusions/Lib/site-packages/sitePackages.py create mode 100644 src/test/pythonFiles/exclusions/dir1/dir1file.py create mode 100644 src/test/pythonFiles/exclusions/dir1/dir2/dir2file.py create mode 100644 src/test/pythonFiles/exclusions/one.py diff --git a/CONTRIBUTING - PYTHON_ANALYSIS.md b/CONTRIBUTING - PYTHON_ANALYSIS.md index d90e684e5083..1b67cd884e6a 100644 --- a/CONTRIBUTING - PYTHON_ANALYSIS.md +++ b/CONTRIBUTING - PYTHON_ANALYSIS.md @@ -33,6 +33,7 @@ Visual Studio 2017: 4. Delete contents of the *analysis* folder in the Python Extension folder 5. Copy *.dll, *.pdb, *.json fron *Python/BuildOutput/VsCode/raw* to *analysis* 6. In VS Code set setting *python.downloadCodeAnalysis* to *false* +7. In VS Code set setting *python.jediEnabled* to *false* ### Debugging code in Python Extension to VS Code Folow regular TypeScript debugging steps diff --git a/src/client/activation/analysis.ts b/src/client/activation/analysis.ts index 57494a338a5d..536530d74050 100644 --- a/src/client/activation/analysis.ts +++ b/src/client/activation/analysis.ts @@ -3,15 +3,14 @@ import { inject, injectable } from 'inversify'; import * as path from 'path'; -import { ExtensionContext, OutputChannel } from 'vscode'; +import { OutputChannel, Uri } from 'vscode'; import { Disposable, LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient'; -import { IApplicationShell, ICommandManager } from '../common/application/types'; +import { IApplicationShell, ICommandManager, IWorkspaceService } from '../common/application/types'; import { isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; import { createDeferred, Deferred } from '../common/helpers'; import { IFileSystem, IPlatformService } from '../common/platform/types'; import { StopWatch } from '../common/stopWatch'; import { IConfigurationService, IExtensionContext, IOutputChannel } from '../common/types'; -import { IEnvironmentVariablesProvider } from '../common/variables/types'; import { IInterpreterService } from '../interpreter/contracts'; import { IServiceContainer } from '../ioc/types'; import { @@ -21,7 +20,7 @@ import { } from '../telemetry/constants'; import { getTelemetryReporter } from '../telemetry/telemetry'; import { AnalysisEngineDownloader } from './downloader'; -import { InterpreterDataService } from './interpreterDataService'; +import { InterpreterData, InterpreterDataService } from './interpreterDataService'; import { PlatformData } from './platformData'; import { IExtensionActivator } from './types'; @@ -42,10 +41,13 @@ export class AnalysisExtensionActivator implements IExtensionActivator { private readonly interpreterService: IInterpreterService; private readonly startupCompleted: Deferred; private readonly disposables: Disposable[] = []; + private readonly context: IExtensionContext; + private readonly workspace: IWorkspaceService; + private readonly root: Uri | undefined; private languageClient: LanguageClient | undefined; - private readonly context: ExtensionContext; private interpreterHash: string = ''; + private loadExtensionArgs: {} | undefined; constructor(@inject(IServiceContainer) private readonly services: IServiceContainer) { this.context = this.services.get(IExtensionContext); @@ -55,14 +57,22 @@ export class AnalysisExtensionActivator implements IExtensionActivator { this.fs = this.services.get(IFileSystem); this.platformData = new PlatformData(services.get(IPlatformService), this.fs); this.interpreterService = this.services.get(IInterpreterService); + this.workspace = this.services.get(IWorkspaceService); + + // Currently only a single root. Multi-root support is future. + this.root = this.workspace && this.workspace.hasWorkspaceFolders + ? this.workspace.workspaceFolders![0]!.uri : undefined; this.startupCompleted = createDeferred(); const commandManager = this.services.get(ICommandManager); + this.disposables.push(commandManager.registerCommand(loadExtensionCommand, async (args) => { if (this.languageClient) { await this.startupCompleted.promise; this.languageClient.sendRequest('python/loadExtension', args); + } else { + this.loadExtensionArgs = args; } } )); @@ -70,17 +80,18 @@ export class AnalysisExtensionActivator implements IExtensionActivator { public async activate(): Promise { this.sw.reset(); - const clientOptions = await this.getAnalysisOptions(this.context); + const clientOptions = await this.getAnalysisOptions(); if (!clientOptions) { return false; } this.disposables.push(this.interpreterService.onDidChangeInterpreter(() => this.restartLanguageServer())); - return this.startLanguageServer(this.context, clientOptions); + return this.startLanguageServer(clientOptions); } public async deactivate(): Promise { if (this.languageClient) { - await this.languageClient.stop(); + // Do not await on this + this.languageClient.stop(); } for (const d of this.disposables) { d.dispose(); @@ -100,7 +111,7 @@ export class AnalysisExtensionActivator implements IExtensionActivator { } } - private async startLanguageServer(context: ExtensionContext, clientOptions: LanguageClientOptions): Promise { + private async startLanguageServer(clientOptions: LanguageClientOptions): Promise { // Determine if we are running MSIL/Universal via dotnet or self-contained app. const reporter = getTelemetryReporter(); @@ -109,22 +120,22 @@ export class AnalysisExtensionActivator implements IExtensionActivator { const settings = this.configuration.getSettings(); if (!settings.downloadCodeAnalysis) { // Depends on .NET Runtime or SDK. Typically development-only case. - this.languageClient = this.createSimpleLanguageClient(context, clientOptions); - await this.startLanguageClient(context); + this.languageClient = this.createSimpleLanguageClient(clientOptions); + await this.startLanguageClient(); return true; } - const mscorlib = path.join(context.extensionPath, analysisEngineFolder, 'mscorlib.dll'); + const mscorlib = path.join(this.context.extensionPath, analysisEngineFolder, 'mscorlib.dll'); if (!await this.fs.fileExists(mscorlib)) { const downloader = new AnalysisEngineDownloader(this.services, analysisEngineFolder); - await downloader.downloadAnalysisEngine(context); + await downloader.downloadAnalysisEngine(this.context); reporter.sendTelemetryEvent(PYTHON_ANALYSIS_ENGINE_DOWNLOADED); } - const serverModule = path.join(context.extensionPath, analysisEngineFolder, this.platformData.getEngineExecutableName()); - this.languageClient = this.createSelfContainedLanguageClient(context, serverModule, clientOptions); + const serverModule = path.join(this.context.extensionPath, analysisEngineFolder, this.platformData.getEngineExecutableName()); + this.languageClient = this.createSelfContainedLanguageClient(serverModule, clientOptions); try { - await this.startLanguageClient(context); + await this.startLanguageClient(); return true; } catch (ex) { this.appShell.showErrorMessage(`Language server failed to start. Error ${ex}`); @@ -133,22 +144,26 @@ export class AnalysisExtensionActivator implements IExtensionActivator { } } - private async startLanguageClient(context: ExtensionContext): Promise { + private async startLanguageClient(): Promise { this.languageClient!.onReady() .then(() => { this.startupCompleted.resolve(); + if (this.loadExtensionArgs) { + this.languageClient!.sendRequest('python/loadExtension', this.loadExtensionArgs); + this.loadExtensionArgs = undefined; + } }) .catch(error => this.startupCompleted.reject(error)); - context.subscriptions.push(this.languageClient!.start()); + this.context.subscriptions.push(this.languageClient!.start()); if (isTestExecution()) { await this.startupCompleted.promise; } } - private createSimpleLanguageClient(context: ExtensionContext, clientOptions: LanguageClientOptions): LanguageClient { + private createSimpleLanguageClient(clientOptions: LanguageClientOptions): LanguageClient { const commandOptions = { stdio: 'pipe' }; - const serverModule = path.join(context.extensionPath, analysisEngineFolder, this.platformData.getEngineDllName()); + const serverModule = path.join(this.context.extensionPath, analysisEngineFolder, this.platformData.getEngineDllName()); const serverOptions: ServerOptions = { run: { command: dotNetCommand, args: [serverModule], options: commandOptions }, debug: { command: dotNetCommand, args: [serverModule, '--debug'], options: commandOptions } @@ -156,7 +171,7 @@ export class AnalysisExtensionActivator implements IExtensionActivator { return new LanguageClient(PYTHON, languageClientName, serverOptions, clientOptions); } - private createSelfContainedLanguageClient(context: ExtensionContext, serverModule: string, clientOptions: LanguageClientOptions): LanguageClient { + private createSelfContainedLanguageClient(serverModule: string, clientOptions: LanguageClientOptions): LanguageClient { const options = { stdio: 'pipe' }; const serverOptions: ServerOptions = { run: { command: serverModule, rgs: [], options: options }, @@ -165,19 +180,22 @@ export class AnalysisExtensionActivator implements IExtensionActivator { return new LanguageClient(PYTHON, languageClientName, serverOptions, clientOptions); } - private async getAnalysisOptions(context: ExtensionContext): Promise { + private async getAnalysisOptions(): Promise { // tslint:disable-next-line:no-any const properties = new Map(); + let interpreterData: InterpreterData | undefined; + let pythonPath = ''; - // Microsoft Python code analysis engine needs full path to the interpreter - const interpreterDataService = new InterpreterDataService(context, this.services); - const interpreterData = await interpreterDataService.getInterpreterData(); - if (!interpreterData) { - const appShell = this.services.get(IApplicationShell); - appShell.showWarningMessage('Unable to determine path to Python interpreter. IntelliSense will be limited.'); + try { + const interpreterDataService = new InterpreterDataService(this.context, this.services); + interpreterData = await interpreterDataService.getInterpreterData(); + } catch (ex) { + this.appShell.showErrorMessage('Unable to determine path to the Python interpreter. IntelliSense will be limited.'); } + this.interpreterHash = interpreterData ? interpreterData.hash : ''; if (interpreterData) { + pythonPath = path.dirname(interpreterData.path); // tslint:disable-next-line:no-string-literal properties['InterpreterPath'] = interpreterData.path; // tslint:disable-next-line:no-string-literal @@ -196,25 +214,17 @@ export class AnalysisExtensionActivator implements IExtensionActivator { } // tslint:disable-next-line:no-string-literal - properties['DatabasePath'] = path.join(context.extensionPath, analysisEngineFolder); - - const envProvider = this.services.get(IEnvironmentVariablesProvider); - let pythonPath = (await envProvider.getEnvironmentVariables()).PYTHONPATH; - this.interpreterHash = interpreterData ? interpreterData.hash : ''; + properties['DatabasePath'] = path.join(this.context.extensionPath, analysisEngineFolder); // Make sure paths do not contain multiple slashes so file URIs // in VS Code (Node.js) and in the language server (.NET) match. // Note: for the language server paths separator is always ; searchPaths = searchPaths.split(path.delimiter).map(p => path.normalize(p)).join(';'); - pythonPath = pythonPath ? path.normalize(pythonPath) : ''; - // tslint:disable-next-line:no-string-literal properties['SearchPaths'] = `${searchPaths};${pythonPath}`; - const selector: string[] = [PYTHON]; - // const searchExcludes = workspace.getConfiguration('search').get('exclude', null); - // const filesExcludes = workspace.getConfiguration('files').get('exclude', null); - // const watcherExcludes = workspace.getConfiguration('files').get('watcherExclude', null); + const selector = [{ language: PYTHON, scheme: 'file' }]; + const excludeFiles = this.getExcludedFiles(); // Options to control the language client return { @@ -236,8 +246,38 @@ export class AnalysisExtensionActivator implements IExtensionActivator { maxDocumentationTextLength: 0 }, asyncStartup: true, + excludeFiles: excludeFiles, testEnvironment: isTestExecution() } }; } + + private getExcludedFiles(): string[] { + const list: string[] = ['**/Lib/**', '**/site-packages/**']; + this.getVsCodeExcludeSection('search.exclude', list); + this.getVsCodeExcludeSection('files.exclude', list); + this.getVsCodeExcludeSection('files.watcherExclude', list); + this.getPythonExcludeSection('linting.ignorePatterns', list); + this.getPythonExcludeSection('workspaceSymbols.exclusionPattern', list); + return list; + } + + private getVsCodeExcludeSection(setting: string, list: string[]): void { + const states = this.workspace.getConfiguration(setting, this.root); + if (states) { + Object.keys(states) + .filter(k => (k.indexOf('*') >= 0 || k.indexOf('/') >= 0) && states[k]) + .forEach(p => list.push(p)); + } + } + + private getPythonExcludeSection(setting: string, list: string[]): void { + const pythonSettings = this.configuration.getSettings(this.root); + const paths = pythonSettings && pythonSettings.linting ? pythonSettings.linting.ignorePatterns : undefined; + if (paths && Array.isArray(paths)) { + paths + .filter(p => p && p.length > 0) + .forEach(p => list.push(p)); + } + } } diff --git a/src/client/activation/downloader.ts b/src/client/activation/downloader.ts index 3fee93e418f5..62e063c83da8 100644 --- a/src/client/activation/downloader.ts +++ b/src/client/activation/downloader.ts @@ -5,11 +5,11 @@ import * as fileSystem from 'fs'; import * as path from 'path'; import * as request from 'request'; import * as requestProgress from 'request-progress'; -import { ExtensionContext, OutputChannel, ProgressLocation, window } from 'vscode'; +import { OutputChannel, ProgressLocation, window } from 'vscode'; import { STANDARD_OUTPUT_CHANNEL } from '../common/constants'; import { createDeferred, createTemporaryFile } from '../common/helpers'; import { IFileSystem, IPlatformService } from '../common/platform/types'; -import { IOutputChannel } from '../common/types'; +import { IExtensionContext, IOutputChannel } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { HashVerifier } from './hashVerifier'; import { PlatformData } from './platformData'; @@ -35,7 +35,7 @@ export class AnalysisEngineDownloader { this.platformData = new PlatformData(this.platform, this.fs); } - public async downloadAnalysisEngine(context: ExtensionContext): Promise { + public async downloadAnalysisEngine(context: IExtensionContext): Promise { const platformString = await this.platformData.getPlatformName(); const enginePackageFileName = `${downloadBaseFileName}-${platformString}.${downloadVersion}${downloadFileExtension}`; diff --git a/src/test/activation/excludeFiles.ptvs.test.ts b/src/test/activation/excludeFiles.ptvs.test.ts new file mode 100644 index 000000000000..88c844866aa6 --- /dev/null +++ b/src/test/activation/excludeFiles.ptvs.test.ts @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as assert from 'assert'; +import { Container } from 'inversify'; +import * as path from 'path'; +import { commands, ConfigurationTarget, languages, Position, TextDocument, window, workspace } from 'vscode'; +import { ConfigurationService } from '../../client/common/configuration/service'; +import '../../client/common/extensions'; +import { IConfigurationService } from '../../client/common/types'; +import { activated } from '../../client/extension'; +import { ServiceContainer } from '../../client/ioc/container'; +import { ServiceManager } from '../../client/ioc/serviceManager'; +import { IServiceContainer, IServiceManager } from '../../client/ioc/types'; +import { IsAnalysisEngineTest } from '../constants'; +import { closeActiveWindows } from '../initialize'; + +const wksPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'exclusions'); +const fileOne = path.join(wksPath, 'one.py'); + +// tslint:disable-next-line:max-func-body-length +suite('Exclude files (Analysis Engine)', () => { + let textDocument: TextDocument; + let serviceManager: IServiceManager; + let serviceContainer: IServiceContainer; + let configService: IConfigurationService; + + suiteSetup(async function () { + if (!IsAnalysisEngineTest()) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + } + }); + setup(async () => { + const cont = new Container(); + serviceContainer = new ServiceContainer(cont); + serviceManager = new ServiceManager(cont); + + serviceManager.addSingleton(IConfigurationService, ConfigurationService); + configService = serviceManager.get(IConfigurationService); + }); + suiteTeardown(closeActiveWindows); + teardown(closeActiveWindows); + + async function openFile(file: string): Promise { + textDocument = await workspace.openTextDocument(file); + await activated; + await window.showTextDocument(textDocument); + // Make sure LS completes file loading and analysis. + // In test mode it awaits for the completion before trying + // to fetch data for completion, hover.etc. + await commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, new Position(0, 0)); + } + + async function setSetting(name: string, value: {} | undefined): Promise { + await configService.updateSettingAsync(name, value, undefined, ConfigurationTarget.Global); + } + + test('Default exclusions', async () => { + await openFile(fileOne); + const diag = languages.getDiagnostics(); + + const main = diag.filter(d => d[0].fsPath.indexOf('one.py') >= 0); + assert.equal(main.length > 0, true); + + const subdir = diag.filter(d => d[0].fsPath.indexOf('three.py') >= 0); + assert.equal(subdir.length > 0, true); + + const node_modules = diag.filter(d => d[0].fsPath.indexOf('node.py') >= 0); + assert.equal(node_modules.length, 0); + + const lib = diag.filter(d => d[0].fsPath.indexOf('fileLib.py') >= 0); + assert.equal(lib.length, 0); + + const sitePackages = diag.filter(d => d[0].fsPath.indexOf('sitePackages.py') >= 0); + assert.equal(sitePackages.length, 0); + }); + test('Exclude subfolder', async () => { + await setSetting('linting.ignorePatterns', ['**/dir1/**']); + + await openFile(fileOne); + const diag = languages.getDiagnostics(); + + const main = diag.filter(d => d[0].fsPath.indexOf('one.py') >= 0); + assert.equal(main.length > 0, true); + + const subdir1 = diag.filter(d => d[0].fsPath.indexOf('dir1file.py') >= 0); + assert.equal(subdir1.length, 0); + + const subdir2 = diag.filter(d => d[0].fsPath.indexOf('dir2file.py') >= 0); + assert.equal(subdir2.length, 0); + + await setSetting('linting.ignorePatterns', undefined); + }); +}); diff --git a/src/test/pythonFiles/exclusions/Lib/fileLib.py b/src/test/pythonFiles/exclusions/Lib/fileLib.py new file mode 100644 index 000000000000..50000adeda40 --- /dev/null +++ b/src/test/pythonFiles/exclusions/Lib/fileLib.py @@ -0,0 +1 @@ + a \ No newline at end of file diff --git a/src/test/pythonFiles/exclusions/Lib/site-packages/sitePackages.py b/src/test/pythonFiles/exclusions/Lib/site-packages/sitePackages.py new file mode 100644 index 000000000000..dad1af98c7f5 --- /dev/null +++ b/src/test/pythonFiles/exclusions/Lib/site-packages/sitePackages.py @@ -0,0 +1 @@ + b \ No newline at end of file diff --git a/src/test/pythonFiles/exclusions/dir1/dir1file.py b/src/test/pythonFiles/exclusions/dir1/dir1file.py new file mode 100644 index 000000000000..fe453b3fcc6a --- /dev/null +++ b/src/test/pythonFiles/exclusions/dir1/dir1file.py @@ -0,0 +1 @@ + for \ No newline at end of file diff --git a/src/test/pythonFiles/exclusions/dir1/dir2/dir2file.py b/src/test/pythonFiles/exclusions/dir1/dir2/dir2file.py new file mode 100644 index 000000000000..fe453b3fcc6a --- /dev/null +++ b/src/test/pythonFiles/exclusions/dir1/dir2/dir2file.py @@ -0,0 +1 @@ + for \ No newline at end of file diff --git a/src/test/pythonFiles/exclusions/one.py b/src/test/pythonFiles/exclusions/one.py new file mode 100644 index 000000000000..8c68a1c1fee2 --- /dev/null +++ b/src/test/pythonFiles/exclusions/one.py @@ -0,0 +1 @@ + if \ No newline at end of file From 6ecd60d36967a9edd2783ec8719b55779db873d7 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 18 Jun 2018 17:30:07 -0700 Subject: [PATCH 357/433] Bug fix, check if code has been ignored (#2001) Fixes #1698 --- .../diagnostics/checks/envPathVariable.ts | 3 +++ .../checks/envPathVariable.unit.test.ts | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/client/application/diagnostics/checks/envPathVariable.ts b/src/client/application/diagnostics/checks/envPathVariable.ts index f8387e73d429..ee8712cfa4f4 100644 --- a/src/client/application/diagnostics/checks/envPathVariable.ts +++ b/src/client/application/diagnostics/checks/envPathVariable.ts @@ -54,6 +54,9 @@ export class EnvironmentPathVariableDiagnosticsService extends BaseDiagnosticsSe return; } const diagnostic = diagnostics[0]; + if (this.filterService.shouldIgnoreDiagnostic(diagnostic.code)) { + return; + } const commandFactory = this.serviceContainer.get(IDiagnosticsCommandFactory); const options = [ { diff --git a/src/test/application/diagnostics/checks/envPathVariable.unit.test.ts b/src/test/application/diagnostics/checks/envPathVariable.unit.test.ts index 44ce2a0749fb..c82802791b0b 100644 --- a/src/test/application/diagnostics/checks/envPathVariable.unit.test.ts +++ b/src/test/application/diagnostics/checks/envPathVariable.unit.test.ts @@ -158,4 +158,26 @@ suite('Application Diagnostics - Checks Env Path Variable', () => { commandFactory.verifyAll(); messageHandler.verifyAll(); }); + test('Should not display a message if the diagnostic code has been ignored', async () => { + platformService.setup(p => p.isWindows).returns(() => true); + const diagnostic = typemoq.Mock.ofType(); + + filterService.setup(f => f.shouldIgnoreDiagnostic(typemoq.It.isValue(DiagnosticCodes.InvalidEnvironmentPathVariableDiagnostic))) + .returns(() => Promise.resolve(true)) + .verifiable(typemoq.Times.once()); + diagnostic.setup(d => d.code) + .returns(() => DiagnosticCodes.InvalidEnvironmentPathVariableDiagnostic) + .verifiable(typemoq.Times.atLeastOnce()); + commandFactory.setup(f => f.createCommand(typemoq.It.isAny(), typemoq.It.isAny())) + .verifiable(typemoq.Times.never()); + messageHandler.setup(m => m.handle(typemoq.It.isAny(), typemoq.It.isAny())) + .verifiable(typemoq.Times.never()); + + await diagnosticService.handle([diagnostic.object]); + + filterService.verifyAll(); + diagnostic.verifyAll(); + commandFactory.verifyAll(); + messageHandler.verifyAll(); + }); }); From e3d0e04b85595861438986475c4417d48978d508 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 18 Jun 2018 17:31:32 -0700 Subject: [PATCH 358/433] Add a "no debugging" debugging scenario --- .github/test_plan.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/test_plan.md b/.github/test_plan.md index 99b26b971f09..c3d6ef79a7cf 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -191,6 +191,7 @@ def foo():pass - [ ] `Scrapy` - [ ] `PySpark` - [ ] `All debug Options` with [appropriate values](https://code.visualstudio.com/docs/python/debugging#_standard-configuration-and-options) edited to make values valid +- [ ] Running code from start to finish w/ no special debugging options (e.g. no breakpoints) - [ ] Breakpoints - [ ] Set - [ ] Hit From 18081b112bb5265b09d22ae415ccf83998741d1d Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 19 Jun 2018 10:44:06 -0700 Subject: [PATCH 359/433] Update logic used to parse the args passed into the test frameworks (#1917) * Refactor unit test discovery * test * Unit tests * Update logic used to parse the args passed into the test frameworks * fix tests * Fix tests * Fix path to test files * Fix tests * Fix more tests * Fix pytest tests * Fix tests * More fixes * Bug fix * fix code review comments * add missing awaits * Create tests for filtering of arguments --- news/2 Fixes/1070.md | 1 + package-lock.json | 6 + package.json | 1 + src/client/activation/downloader.ts | 6 +- .../diagnostics/applicationDiagnostics.ts | 2 +- .../diagnostics/checks/envPathVariable.ts | 2 +- src/client/common/application/types.ts | 2 +- src/client/common/platform/fileSystem.ts | 14 +- src/client/common/platform/types.ts | 4 + .../unittests/common/argumentsHelper.ts | 105 ++++++ .../common/managers/baseTestManager.ts | 21 +- src/client/unittests/common/runner.ts | 18 +- .../common/services/storageService.ts | 2 +- src/client/unittests/common/testUtils.ts | 35 ++ src/client/unittests/common/types.ts | 25 ++ src/client/unittests/common/xUnitParser.ts | 25 +- src/client/unittests/nosetest/main.ts | 35 +- src/client/unittests/nosetest/runner.ts | 162 +++++----- .../nosetest/services/argsService.ts | 111 +++++++ .../nosetest/services/discoveryService.ts | 38 +-- .../nosetest/services/parserService.ts | 9 +- src/client/unittests/pytest/main.ts | 33 +- src/client/unittests/pytest/runner.ts | 138 ++++---- .../unittests/pytest/services/argsService.ts | 156 +++++++++ .../pytest/services/discoveryService.ts | 75 +++-- .../pytest/services/parserService.ts | 12 +- src/client/unittests/serviceRegistry.ts | 26 +- src/client/unittests/types.ts | 37 ++- src/client/unittests/unittest/helper.ts | 52 +++ src/client/unittests/unittest/main.ts | 31 +- src/client/unittests/unittest/runner.ts | 252 ++++++--------- .../unittest/services/argsService.ts | 69 ++++ .../unittest/services/discoveryService.ts | 69 ++-- .../unittest/services/parserService.ts | 31 +- src/test/common.ts | 5 +- ...system.test.ts => filesystem.unit.test.ts} | 5 +- src/test/index.ts | 8 +- src/test/unittests/argsService.unit.test.ts | 304 ++++++++++++++++++ .../unittests/common/argsHelper.unit.test.ts | 111 +++++++ src/test/unittests/debugger.test.ts | 33 +- .../nosetest/nosetest.discovery.unit.test.ts | 111 +++++++ .../{ => nosetest}/nosetest.disovery.test.ts | 23 +- .../{ => nosetest}/nosetest.run.test.ts | 21 +- .../unittests/{ => nosetest}/nosetest.test.ts | 17 +- .../{ => pytest}/pytest.discovery.test.ts | 23 +- .../pytest/pytest.discovery.unit.test.ts | 180 +++++++++++ .../unittests/{ => pytest}/pytest.run.test.ts | 21 +- .../unittests/{ => pytest}/pytest.test.ts | 13 +- .../{ => unittest}/unittest.discovery.test.ts | 17 +- .../unittest/unittest.discovery.unit.test.ts | 303 +++++++++++++++++ .../{ => unittest}/unittest.run.test.ts | 30 +- .../unittests/{ => unittest}/unittest.test.ts | 13 +- 52 files changed, 2281 insertions(+), 562 deletions(-) create mode 100644 news/2 Fixes/1070.md create mode 100644 src/client/unittests/common/argumentsHelper.ts create mode 100644 src/client/unittests/nosetest/services/argsService.ts create mode 100644 src/client/unittests/pytest/services/argsService.ts create mode 100644 src/client/unittests/unittest/helper.ts create mode 100644 src/client/unittests/unittest/services/argsService.ts rename src/test/common/platform/{filesystem.test.ts => filesystem.unit.test.ts} (96%) create mode 100644 src/test/unittests/argsService.unit.test.ts create mode 100644 src/test/unittests/common/argsHelper.unit.test.ts create mode 100644 src/test/unittests/nosetest/nosetest.discovery.unit.test.ts rename src/test/unittests/{ => nosetest}/nosetest.disovery.test.ts (88%) rename src/test/unittests/{ => nosetest}/nosetest.run.test.ts (90%) rename src/test/unittests/{ => nosetest}/nosetest.test.ts (77%) rename src/test/unittests/{ => pytest}/pytest.discovery.test.ts (92%) create mode 100644 src/test/unittests/pytest/pytest.discovery.unit.test.ts rename src/test/unittests/{ => pytest}/pytest.run.test.ts (89%) rename src/test/unittests/{ => pytest}/pytest.test.ts (79%) rename src/test/unittests/{ => unittest}/unittest.discovery.test.ts (92%) create mode 100644 src/test/unittests/unittest/unittest.discovery.unit.test.ts rename src/test/unittests/{ => unittest}/unittest.run.test.ts (90%) rename src/test/unittests/{ => unittest}/unittest.test.ts (83%) diff --git a/news/2 Fixes/1070.md b/news/2 Fixes/1070.md new file mode 100644 index 000000000000..5f9100ebc192 --- /dev/null +++ b/news/2 Fixes/1070.md @@ -0,0 +1 @@ +Improvements to the logic used to parse the arguments passed into the test frameworks. diff --git a/package-lock.json b/package-lock.json index 060f25faeccc..12cd5948e3f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -256,6 +256,12 @@ "integrity": "sha512-Tt7w/ylBS/OEAlSCwzB0Db1KbxnkycP/1UkQpbvKFYoUuRn4uYsC3xh5TRPrOjTy0i8TIkSz1JdNL4GPVdf3KQ==", "dev": true }, + "@types/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha1-EHPEvIJHVK49EM+riKsCN7qWTk0=", + "dev": true + }, "@types/tough-cookie": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.3.tgz", diff --git a/package.json b/package.json index 854b86447122..4f6382ef049d 100644 --- a/package.json +++ b/package.json @@ -1955,6 +1955,7 @@ "@types/semver": "^5.5.0", "@types/shortid": "^0.0.29", "@types/sinon": "^4.3.0", + "@types/tmp": "0.0.33", "@types/untildify": "^3.0.0", "@types/uuid": "^3.4.3", "@types/winreg": "^1.2.30", diff --git a/src/client/activation/downloader.ts b/src/client/activation/downloader.ts index 62e063c83da8..adf5b81afbf5 100644 --- a/src/client/activation/downloader.ts +++ b/src/client/activation/downloader.ts @@ -7,7 +7,7 @@ import * as request from 'request'; import * as requestProgress from 'request-progress'; import { OutputChannel, ProgressLocation, window } from 'vscode'; import { STANDARD_OUTPUT_CHANNEL } from '../common/constants'; -import { createDeferred, createTemporaryFile } from '../common/helpers'; +import { createDeferred } from '../common/helpers'; import { IFileSystem, IPlatformService } from '../common/platform/types'; import { IExtensionContext, IOutputChannel } from '../common/types'; import { IServiceContainer } from '../ioc/types'; @@ -58,14 +58,14 @@ export class AnalysisEngineDownloader { private async downloadFile(location: string, fileName: string, title: string): Promise { const uri = `${location}/${fileName}`; this.output.append(`Downloading ${uri}... `); - const tempFile = await createTemporaryFile(downloadFileExtension); + const tempFile = await this.fs.createTemporaryFile(downloadFileExtension); const deferred = createDeferred(); const fileStream = fileSystem.createWriteStream(tempFile.filePath); fileStream.on('finish', () => { fileStream.close(); }).on('error', (err) => { - tempFile.cleanupCallback(); + tempFile.dispose(); deferred.reject(err); }); diff --git a/src/client/application/diagnostics/applicationDiagnostics.ts b/src/client/application/diagnostics/applicationDiagnostics.ts index 84422846a19b..c3eee0d55fe3 100644 --- a/src/client/application/diagnostics/applicationDiagnostics.ts +++ b/src/client/application/diagnostics/applicationDiagnostics.ts @@ -20,7 +20,7 @@ export class ApplicationDiagnostics implements IApplicationDiagnostics { const diagnostics = await envHealthCheck.diagnose(); this.log(diagnostics); if (diagnostics.length > 0) { - envHealthCheck.handle(diagnostics); + await envHealthCheck.handle(diagnostics); } } private log(diagnostics: IDiagnostic[]): void { diff --git a/src/client/application/diagnostics/checks/envPathVariable.ts b/src/client/application/diagnostics/checks/envPathVariable.ts index ee8712cfa4f4..c2b09b70c0dc 100644 --- a/src/client/application/diagnostics/checks/envPathVariable.ts +++ b/src/client/application/diagnostics/checks/envPathVariable.ts @@ -54,7 +54,7 @@ export class EnvironmentPathVariableDiagnosticsService extends BaseDiagnosticsSe return; } const diagnostic = diagnostics[0]; - if (this.filterService.shouldIgnoreDiagnostic(diagnostic.code)) { + if (await this.filterService.shouldIgnoreDiagnostic(diagnostic.code)) { return; } const commandFactory = this.serviceContainer.get(IDiagnosticsCommandFactory); diff --git a/src/client/common/application/types.ts b/src/client/common/application/types.ts index d3d67245feb2..c6546ce056fe 100644 --- a/src/client/common/application/types.ts +++ b/src/client/common/application/types.ts @@ -407,7 +407,7 @@ export interface IDocumentManager { showTextDocument(uri: Uri, options?: TextDocumentShowOptions): Thenable; } -export const IWorkspaceService = Symbol('IWorkspace'); +export const IWorkspaceService = Symbol('IWorkspaceService'); export interface IWorkspaceService { /** diff --git a/src/client/common/platform/fileSystem.ts b/src/client/common/platform/fileSystem.ts index 645aad5a2577..ede57f3290e0 100644 --- a/src/client/common/platform/fileSystem.ts +++ b/src/client/common/platform/fileSystem.ts @@ -7,8 +7,9 @@ import * as fs from 'fs-extra'; import * as glob from 'glob'; import { inject, injectable } from 'inversify'; import * as path from 'path'; +import * as tmp from 'tmp'; import { createDeferred } from '../helpers'; -import { IFileSystem, IPlatformService } from './types'; +import { IFileSystem, IPlatformService, TemporaryFile } from './types'; @injectable() export class FileSystem implements IFileSystem { @@ -141,4 +142,15 @@ export class FileSystem implements IFileSystem { }); }); } + public createTemporaryFile(extension: string): Promise { + return new Promise((resolve, reject) => { + tmp.file({ postfix: extension }, (err, tmpFile, _, cleanupCallback) => { + if (err) { + return reject(err); + } + resolve({ filePath: tmpFile, dispose: cleanupCallback }); + }); + }); + + } } diff --git a/src/client/common/platform/types.ts b/src/client/common/platform/types.ts index 28b2af9ce5b2..ec08fd7d284c 100644 --- a/src/client/common/platform/types.ts +++ b/src/client/common/platform/types.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. import * as fs from 'fs'; +import { Disposable } from 'vscode'; export enum Architecture { Unknown = 1, @@ -28,6 +29,8 @@ export interface IPlatformService { virtualEnvBinName: 'bin' | 'scripts'; } +export type TemporaryFile = { filePath: string } & Disposable; + export const IFileSystem = Symbol('IFileSystem'); export interface IFileSystem { directorySeparatorChar: string; @@ -48,4 +51,5 @@ export interface IFileSystem { deleteFile(filename: string): Promise; getFileHash(filePath: string): Promise; search(globPattern: string): Promise; + createTemporaryFile(extension: string): Promise; } diff --git a/src/client/unittests/common/argumentsHelper.ts b/src/client/unittests/common/argumentsHelper.ts new file mode 100644 index 000000000000..3842a9a6874e --- /dev/null +++ b/src/client/unittests/common/argumentsHelper.ts @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { ILogger } from '../../common/types'; +import { IServiceContainer } from '../../ioc/types'; +import { IArgumentsHelper } from '../types'; + +@injectable() +export class ArgumentsHelper implements IArgumentsHelper { + private readonly logger: ILogger; + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { + this.logger = serviceContainer.get(ILogger); + } + public getOptionValues(args: string[], option: string): string | string[] | undefined { + const values: string[] = []; + let returnNextValue = false; + for (const arg of args) { + if (returnNextValue) { + values.push(arg); + returnNextValue = false; + continue; + } + if (arg.startsWith(`${option}=`)) { + values.push(arg.substring(`${option}=`.length)); + continue; + } + if (arg === option) { + returnNextValue = true; + } + } + switch (values.length) { + case 0: { + return; + } + case 1: { + return values[0]; + } + default: { + return values; + } + } + } + public getPositionalArguments(args: string[], optionsWithArguments: string[] = [], optionsWithoutArguments: string[] = []): string[] { + let lastIndexOfOption = -1; + args.forEach((arg, index) => { + if (optionsWithoutArguments.indexOf(arg) !== -1) { + lastIndexOfOption = index; + return; + } else if (optionsWithArguments.indexOf(arg) !== -1) { + // Cuz the next item is the value. + lastIndexOfOption = index + 1; + } else if (optionsWithArguments.findIndex(item => arg.startsWith(`${item}=`)) !== -1) { + lastIndexOfOption = index; + return; + } else if (arg.startsWith('-')) { + // Ok this is an unknown option, lets treat this as one without values. + this.logger.logWarning(`Unknown command line option passed into args parser for tests '${arg}'. Please report on https://github.com/Microsoft/vscode-python/issues/new`); + lastIndexOfOption = index; + return; + } else if (args.indexOf('=') > 0) { + // Ok this is an unknown option with a value + this.logger.logWarning(`Unknown command line option passed into args parser for tests '${arg}'. Please report on https://github.com/Microsoft/vscode-python/issues/new`); + lastIndexOfOption = index; + } + }); + return args.slice(lastIndexOfOption + 1); + } + public filterArguments(args: string[], optionsWithArguments: string[] = [], optionsWithoutArguments: string[] = []): string[] { + let ignoreIndex = -1; + return args.filter((arg, index) => { + if (ignoreIndex === index) { + return false; + } + // Options can use willd cards (with trailing '*') + if (optionsWithoutArguments.indexOf(arg) >= 0 || + optionsWithoutArguments.filter(option => option.endsWith('*') && arg.startsWith(option.slice(0, -1))).length > 0) { + return false; + } + // Ignore args that match exactly. + if (optionsWithArguments.indexOf(arg) >= 0) { + ignoreIndex = index + 1; + return false; + } + // Ignore args that match exactly with wild cards & do not have inline values. + if (optionsWithArguments.filter(option => arg.startsWith(`${option}=`)).length > 0) { + return false; + } + // Ignore args that match a wild card (ending with *) and no ineline values. + // Eg. arg='--log-cli-level' and optionsArguments=['--log-*'] + if (arg.indexOf('=') === -1 && optionsWithoutArguments.filter(option => option.endsWith('*') && arg.startsWith(option.slice(0, -1))).length > 0) { + ignoreIndex = index + 1; + return false; + } + // Ignore args that match a wild card (ending with *) and have ineline values. + // Eg. arg='--log-cli-level=XYZ' and optionsArguments=['--log-*'] + if (arg.indexOf('=') >= 0 && optionsWithoutArguments.filter(option => option.endsWith('*') && arg.startsWith(option.slice(0, -1))).length > 0) { + return false; + } + return true; + }); + } +} diff --git a/src/client/unittests/common/managers/baseTestManager.ts b/src/client/unittests/common/managers/baseTestManager.ts index 437044aa94e3..89a54b12e40e 100644 --- a/src/client/unittests/common/managers/baseTestManager.ts +++ b/src/client/unittests/common/managers/baseTestManager.ts @@ -1,7 +1,7 @@ -import { CancellationToken, CancellationTokenSource, Disposable, OutputChannel, Uri, workspace } from 'vscode'; -import { PythonSettings } from '../../../common/configSettings'; +import { CancellationToken, CancellationTokenSource, Disposable, OutputChannel, Uri } from 'vscode'; +import { IWorkspaceService } from '../../../common/application/types'; import { isNotInstalledError } from '../../../common/helpers'; -import { IDisposableRegistry, IInstaller, IOutputChannel, IPythonSettings, Product } from '../../../common/types'; +import { IConfigurationService, IDisposableRegistry, IInstaller, IOutputChannel, IPythonSettings, Product } from '../../../common/types'; import { IServiceContainer } from '../../../ioc/types'; import { UNITTEST_DISCOVER, UNITTEST_RUN } from '../../../telemetry/constants'; import { sendTelemetryEvent } from '../../../telemetry/index'; @@ -25,9 +25,9 @@ export abstract class BaseTestManager implements ITestManager { } private testCollectionStorage: ITestCollectionStorageService; private _testResultsService: ITestResultsService; + private workspaceService: IWorkspaceService; private _outputChannel: OutputChannel; private tests?: Tests; - // tslint:disable-next-line:variable-name private _status: TestStatus = TestStatus.Unknown; private testDiscoveryCancellationTokenSource?: CancellationTokenSource; private testRunnerCancellationTokenSource?: CancellationTokenSource; @@ -42,12 +42,14 @@ export abstract class BaseTestManager implements ITestManager { constructor(public readonly testProvider: TestProvider, private product: Product, public readonly workspaceFolder: Uri, protected rootDirectory: string, protected serviceContainer: IServiceContainer) { this._status = TestStatus.Unknown; - this.settings = PythonSettings.getInstance(this.rootDirectory ? Uri.file(this.rootDirectory) : undefined); + const configService = serviceContainer.get(IConfigurationService); + this.settings = configService.getSettings(this.rootDirectory ? Uri.file(this.rootDirectory) : undefined); const disposables = serviceContainer.get(IDisposableRegistry); - disposables.push(this); this._outputChannel = this.serviceContainer.get(IOutputChannel, TEST_OUTPUT_CHANNEL); this.testCollectionStorage = this.serviceContainer.get(ITestCollectionStorageService); this._testResultsService = this.serviceContainer.get(ITestResultsService); + this.workspaceService = this.serviceContainer.get(IWorkspaceService); + disposables.push(this); } protected get testDiscoveryCancellationToken(): CancellationToken | undefined { return this.testDiscoveryCancellationTokenSource ? this.testDiscoveryCancellationTokenSource.token : undefined; @@ -62,8 +64,7 @@ export abstract class BaseTestManager implements ITestManager { return this._status; } public get workingDirectory(): string { - const settings = PythonSettings.getInstance(Uri.file(this.rootDirectory)); - return settings.unitTest.cwd && settings.unitTest.cwd.length > 0 ? settings.unitTest.cwd : this.rootDirectory; + return this.settings.unitTest.cwd && this.settings.unitTest.cwd.length > 0 ? this.settings.unitTest.cwd : this.rootDirectory; } public stop() { if (this.testDiscoveryCancellationTokenSource) { @@ -132,7 +133,7 @@ export abstract class BaseTestManager implements ITestManager { const testsHelper = this.serviceContainer.get(ITestsHelper); testsHelper.displayTestErrorMessage('There were some errors in discovering unit tests'); } - const wkspace = workspace.getWorkspaceFolder(Uri.file(this.rootDirectory))!.uri; + const wkspace = this.workspaceService.getWorkspaceFolder(Uri.file(this.rootDirectory))!.uri; this.testCollectionStorage.storeTests(wkspace, tests); this.disposeCancellationToken(CancellationTokenType.testDiscovery); sendTelemetryEvent(UNITTEST_DISCOVER, undefined, telementryProperties); @@ -156,7 +157,7 @@ export abstract class BaseTestManager implements ITestManager { // tslint:disable-next-line:prefer-template this.outputChannel.appendLine(reason.toString()); } - const wkspace = workspace.getWorkspaceFolder(Uri.file(this.rootDirectory))!.uri; + const wkspace = this.workspaceService.getWorkspaceFolder(Uri.file(this.rootDirectory))!.uri; this.testCollectionStorage.storeTests(wkspace, null); this.disposeCancellationToken(CancellationTokenType.testDiscovery); return Promise.reject(reason); diff --git a/src/client/unittests/common/runner.ts b/src/client/unittests/common/runner.ts index 9bf612a33b58..a8b1d216423e 100644 --- a/src/client/unittests/common/runner.ts +++ b/src/client/unittests/common/runner.ts @@ -1,3 +1,4 @@ +import { inject, injectable } from 'inversify'; import * as path from 'path'; import { CancellationToken, OutputChannel, Uri } from 'vscode'; import { PythonSettings } from '../../common/configSettings'; @@ -13,15 +14,16 @@ import { import { ExecutionInfo, IPythonSettings } from '../../common/types'; import { IServiceContainer } from '../../ioc/types'; import { NOSETEST_PROVIDER, PYTEST_PROVIDER, UNITTEST_PROVIDER } from './constants'; -import { ITestsHelper, TestProvider } from './types'; +import { ITestRunner, ITestsHelper, Options, TestProvider } from './types'; +export { Options } from './types'; -export type Options = { - workspaceFolder: Uri; - cwd: string; - args: string[]; - outChannel?: OutputChannel; - token: CancellationToken; -}; +@injectable() +export class TestRunner implements ITestRunner { + constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { } + public run(testProvider: TestProvider, options: Options): Promise { + return run(this.serviceContainer, testProvider, options); + } +} export async function run(serviceContainer: IServiceContainer, testProvider: TestProvider, options: Options): Promise { const testExecutablePath = getExecutablePath(testProvider, PythonSettings.getInstance(options.workspaceFolder)); diff --git a/src/client/unittests/common/services/storageService.ts b/src/client/unittests/common/services/storageService.ts index 14945216bf78..859cf2939cbf 100644 --- a/src/client/unittests/common/services/storageService.ts +++ b/src/client/unittests/common/services/storageService.ts @@ -6,7 +6,7 @@ import { ITestCollectionStorageService, Tests } from './../types'; @injectable() export class TestCollectionStorageService implements ITestCollectionStorageService { private testsIndexedByWorkspaceUri = new Map(); - constructor( @inject(IDisposableRegistry) disposables: Disposable[]) { + constructor(@inject(IDisposableRegistry) disposables: Disposable[]) { disposables.push(this); } public getTests(wkspace: Uri): Tests | undefined { diff --git a/src/client/unittests/common/testUtils.ts b/src/client/unittests/common/testUtils.ts index 535f6c28c1a7..feb4d7c9be50 100644 --- a/src/client/unittests/common/testUtils.ts +++ b/src/client/unittests/common/testUtils.ts @@ -171,4 +171,39 @@ export class TestsHelper implements ITestsHelper { } }); } + public mergeTests(items: Tests[]): Tests { + return items.reduce((tests, otherTests, index) => { + if (index === 0) { + return tests; + } + + tests.summary.errors += otherTests.summary.errors; + tests.summary.failures += otherTests.summary.failures; + tests.summary.passed += otherTests.summary.passed; + tests.summary.skipped += otherTests.summary.skipped; + tests.rootTestFolders.push(...otherTests.rootTestFolders); + tests.testFiles.push(...otherTests.testFiles); + tests.testFolders.push(...otherTests.testFolders); + tests.testFunctions.push(...otherTests.testFunctions); + tests.testSuites.push(...otherTests.testSuites); + + return tests; + }, items[0]); + } + + public shouldRunAllTests(testsToRun?: TestsToRun) { + if (!testsToRun) { + return true; + } + if ( + (Array.isArray(testsToRun.testFile) && testsToRun.testFile.length > 0) || + (Array.isArray(testsToRun.testFolder) && testsToRun.testFolder.length > 0) || + (Array.isArray(testsToRun.testFunction) && testsToRun.testFunction.length > 0) || + (Array.isArray(testsToRun.testSuite) && testsToRun.testSuite.length > 0) + ) { + return false; + } + + return true; + } } diff --git a/src/client/unittests/common/types.ts b/src/client/unittests/common/types.ts index e5ad14fa6fd7..67e2663efab9 100644 --- a/src/client/unittests/common/types.ts +++ b/src/client/unittests/common/types.ts @@ -161,6 +161,8 @@ export interface ITestsHelper { flattenTestFiles(testFiles: TestFile[]): Tests; placeTestFilesIntoFolders(tests: Tests): void; displayTestErrorMessage(message: string): void; + shouldRunAllTests(testsToRun?: TestsToRun): boolean; + mergeTests(items: Tests[]): Tests; } export const ITestVisitor = Symbol('ITestVisitor'); @@ -244,3 +246,26 @@ export interface IUnitTestSocketServer extends Disposable { start(options?: { port?: number; host?: string }): Promise; stop(): void; } + +export type Options = { + workspaceFolder: Uri; + cwd: string; + args: string[]; + outChannel?: OutputChannel; + token: CancellationToken; +}; + +export const ITestRunner = Symbol('ITestRunner'); +export interface ITestRunner { + run(testProvider: TestProvider, options: Options): Promise; +} + +export enum PassCalculationFormulae { + pytest, + nosetests +} + +export const IXUnitParser = Symbol('IXUnitParser'); +export interface IXUnitParser { + updateResultsFromXmlLogFile(tests: Tests, outputXmlFile: string, passCalculationFormulae: PassCalculationFormulae): Promise; +} diff --git a/src/client/unittests/common/xUnitParser.ts b/src/client/unittests/common/xUnitParser.ts index 6318060a94f7..091ad9e87db2 100644 --- a/src/client/unittests/common/xUnitParser.ts +++ b/src/client/unittests/common/xUnitParser.ts @@ -1,11 +1,7 @@ import * as fs from 'fs'; +import { injectable } from 'inversify'; import * as xml2js from 'xml2js'; -import { Tests, TestStatus } from './types'; - -export enum PassCalculationFormulae { - pytest, - nosetests -} +import { IXUnitParser, PassCalculationFormulae, Tests, TestStatus } from './types'; type TestSuiteResult = { $: { errors: string; @@ -28,15 +24,15 @@ type TestCaseResult = { }; failure: { _: string; - $: { message: string, type: string } + $: { message: string; type: string }; }[]; error: { _: string; - $: { message: string, type: string } + $: { message: string; type: string }; }[]; skipped: { _: string; - $: { message: string, type: string } + $: { message: string; type: string }; }[]; }; @@ -46,7 +42,14 @@ function getSafeInt(value: string, defaultValue: any = 0): number { if (isNaN(num)) { return defaultValue; } return num; } -export function updateResultsFromXmlLogFile(tests: Tests, outputXmlFile: string, passCalculationFormulae: PassCalculationFormulae): Promise<{}> { + +@injectable() +export class XUnitParser implements IXUnitParser { + public updateResultsFromXmlLogFile(tests: Tests, outputXmlFile: string, passCalculationFormulae: PassCalculationFormulae): Promise { + return updateResultsFromXmlLogFile(tests, outputXmlFile, passCalculationFormulae); + } +} +export function updateResultsFromXmlLogFile(tests: Tests, outputXmlFile: string, passCalculationFormulae: PassCalculationFormulae): Promise { // tslint:disable-next-line:no-any return new Promise((resolve, reject) => { fs.readFile(outputXmlFile, 'utf8', (err, data) => { @@ -127,7 +130,7 @@ export function updateResultsFromXmlLogFile(tests: Tests, outputXmlFile: string, if (testcase.skipped) { result.testFunction.status = TestStatus.Skipped; - result.testFunction.passed = null; + result.testFunction.passed = undefined; result.testFunction.message = testcase.skipped[0].$.message; result.testFunction.traceback = ''; } diff --git a/src/client/unittests/nosetest/main.ts b/src/client/unittests/nosetest/main.ts index 5ffae0b80cd3..19aa8b125e9a 100644 --- a/src/client/unittests/nosetest/main.ts +++ b/src/client/unittests/nosetest/main.ts @@ -1,20 +1,26 @@ import { inject, injectable } from 'inversify'; import { Uri } from 'vscode'; -import { PythonSettings } from '../../common/configSettings'; import { Product } from '../../common/types'; import { IServiceContainer } from '../../ioc/types'; +import { NOSETEST_PROVIDER } from '../common/constants'; import { BaseTestManager } from '../common/managers/baseTestManager'; -import { TestDiscoveryOptions, TestRunOptions, Tests, TestsToRun } from '../common/types'; -import { runTest } from './runner'; +import { ITestsHelper, TestDiscoveryOptions, TestRunOptions, Tests, TestsToRun } from '../common/types'; +import { IArgumentsService, ITestManagerRunner, TestFilter } from '../types'; @injectable() export class TestManager extends BaseTestManager { + private readonly argsService: IArgumentsService; + private readonly helper: ITestsHelper; + private readonly runner: ITestManagerRunner; public get enabled() { - return PythonSettings.getInstance(this.workspaceFolder).unitTest.nosetestsEnabled; + return this.settings.unitTest.nosetestsEnabled; } constructor(workspaceFolder: Uri, rootDirectory: string, @inject(IServiceContainer) serviceContainer: IServiceContainer) { - super('nosetest', Product.nosetest, workspaceFolder, rootDirectory, serviceContainer); + super(NOSETEST_PROVIDER, Product.nosetest, workspaceFolder, rootDirectory, serviceContainer); + this.argsService = this.serviceContainer.get(IArgumentsService, this.testProvider); + this.helper = this.serviceContainer.get(ITestsHelper); + this.runner = this.serviceContainer.get(ITestManagerRunner, this.testProvider); } public getDiscoveryOptions(ignoreCache: boolean): TestDiscoveryOptions { const args = this.settings.unitTest.nosetestArgs.slice(0); @@ -25,14 +31,21 @@ export class TestManager extends BaseTestManager { outChannel: this.outputChannel }; } - // tslint:disable-next-line:no-any - public runTestImpl(tests: Tests, testsToRun?: TestsToRun, runFailedTests?: boolean, debug?: boolean): Promise { - const args = this.settings.unitTest.nosetestArgs.slice(0); + public runTestImpl(tests: Tests, testsToRun?: TestsToRun, runFailedTests?: boolean, debug?: boolean): Promise { + let args: string[]; + + const runAllTests = this.helper.shouldRunAllTests(testsToRun); + if (debug) { + args = this.argsService.filterArguments(this.settings.unitTest.nosetestArgs, runAllTests ? TestFilter.debugAll : TestFilter.debugSpecific); + } else { + args = this.argsService.filterArguments(this.settings.unitTest.nosetestArgs, runAllTests ? TestFilter.runAll : TestFilter.runSpecific); + } + if (runFailedTests === true && args.indexOf('--failed') === -1) { - args.push('--failed'); + args.splice(0, 0, '--failed'); } if (!runFailedTests && args.indexOf('--with-id') === -1) { - args.push('--with-id'); + args.splice(0, 0, '--with-id'); } const options: TestRunOptions = { workspaceFolder: Uri.file(this.rootDirectory), @@ -42,6 +55,6 @@ export class TestManager extends BaseTestManager { outChannel: this.outputChannel, debug }; - return runTest(this.serviceContainer, this.testResultsService, options); + return this.runner.runTest(this.testResultsService, options, this); } } diff --git a/src/client/unittests/nosetest/runner.ts b/src/client/unittests/nosetest/runner.ts index 64a170dd5150..a6ba3cc148e0 100644 --- a/src/client/unittests/nosetest/runner.ts +++ b/src/client/unittests/nosetest/runner.ts @@ -1,99 +1,99 @@ 'use strict'; -import { createTemporaryFile } from '../../common/helpers'; + +import { inject, injectable } from 'inversify'; +import { noop } from '../../common/core.utils'; +import { IFileSystem, TemporaryFile } from '../../common/platform/types'; import { IServiceContainer } from '../../ioc/types'; -import { Options, run } from '../common/runner'; -import { ITestDebugLauncher, ITestResultsService, LaunchOptions, TestRunOptions, Tests } from '../common/types'; -import { PassCalculationFormulae, updateResultsFromXmlLogFile } from '../common/xUnitParser'; +import { NOSETEST_PROVIDER } from '../common/constants'; +import { Options } from '../common/runner'; +import { ITestDebugLauncher, ITestManager, ITestResultsService, ITestRunner, IXUnitParser, LaunchOptions, PassCalculationFormulae, TestRunOptions, Tests } from '../common/types'; +import { IArgumentsHelper, IArgumentsService, ITestManagerRunner } from '../types'; const WITH_XUNIT = '--with-xunit'; const XUNIT_FILE = '--xunit-file'; -// tslint:disable-next-line:no-any -export function runTest(serviceContainer: IServiceContainer, testResultsService: ITestResultsService, options: TestRunOptions): Promise { - let testPaths: string[] = []; - if (options.testsToRun && options.testsToRun.testFolder) { - testPaths = testPaths.concat(options.testsToRun.testFolder.map(f => f.nameToRun)); - } - if (options.testsToRun && options.testsToRun.testFile) { - testPaths = testPaths.concat(options.testsToRun.testFile.map(f => f.nameToRun)); - } - if (options.testsToRun && options.testsToRun.testSuite) { - testPaths = testPaths.concat(options.testsToRun.testSuite.map(f => f.nameToRun)); - } - if (options.testsToRun && options.testsToRun.testFunction) { - testPaths = testPaths.concat(options.testsToRun.testFunction.map(f => f.nameToRun)); - } - - let xmlLogFile = ''; - // tslint:disable-next-line:no-empty - let xmlLogFileCleanup: Function = () => { }; - - // Check if '--with-xunit' is in args list - const noseTestArgs = options.args.slice(); - if (noseTestArgs.indexOf(WITH_XUNIT) === -1) { - noseTestArgs.push(WITH_XUNIT); +@injectable() +export class TestManagerRunner implements ITestManagerRunner { + private readonly argsService: IArgumentsService; + private readonly argsHelper: IArgumentsHelper; + private readonly testRunner: ITestRunner; + private readonly xUnitParser: IXUnitParser; + private readonly fs: IFileSystem; + constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { + this.argsService = serviceContainer.get(IArgumentsService, NOSETEST_PROVIDER); + this.argsHelper = serviceContainer.get(IArgumentsHelper); + this.testRunner = serviceContainer.get(ITestRunner); + this.xUnitParser = this.serviceContainer.get(IXUnitParser); + this.fs = this.serviceContainer.get(IFileSystem); } + public async runTest(testResultsService: ITestResultsService, options: TestRunOptions, _: ITestManager): Promise { + let testPaths: string[] = []; + if (options.testsToRun && options.testsToRun.testFolder) { + testPaths = testPaths.concat(options.testsToRun.testFolder.map(f => f.nameToRun)); + } + if (options.testsToRun && options.testsToRun.testFile) { + testPaths = testPaths.concat(options.testsToRun.testFile.map(f => f.nameToRun)); + } + if (options.testsToRun && options.testsToRun.testSuite) { + testPaths = testPaths.concat(options.testsToRun.testSuite.map(f => f.nameToRun)); + } + if (options.testsToRun && options.testsToRun.testFunction) { + testPaths = testPaths.concat(options.testsToRun.testFunction.map(f => f.nameToRun)); + } - // Check if '--xunit-file' exists, if not generate random xml file - const indexOfXUnitFile = noseTestArgs.findIndex(value => value.indexOf(XUNIT_FILE) === 0); - let promiseToGetXmlLogFile: Promise; - if (indexOfXUnitFile === -1) { - promiseToGetXmlLogFile = createTemporaryFile('.xml').then(xmlLogResult => { - xmlLogFileCleanup = xmlLogResult.cleanupCallback; - xmlLogFile = xmlLogResult.filePath; - - noseTestArgs.push(`${XUNIT_FILE}=${xmlLogFile}`); - return xmlLogResult.filePath; - }); - } else { - if (noseTestArgs[indexOfXUnitFile].indexOf('=') === -1) { - xmlLogFile = noseTestArgs[indexOfXUnitFile + 1]; - } else { - xmlLogFile = noseTestArgs[indexOfXUnitFile].substring(noseTestArgs[indexOfXUnitFile].indexOf('=') + 1).trim(); + let deleteJUnitXmlFile: Function = noop; + const args = options.args; + // Check if '--with-xunit' is in args list + if (args.indexOf(WITH_XUNIT) === -1) { + args.splice(0, 0, WITH_XUNIT); } - promiseToGetXmlLogFile = Promise.resolve(xmlLogFile); - } + try { + const xmlLogResult = await this.getUnitXmlFile(args); + const xmlLogFile = xmlLogResult.filePath; + deleteJUnitXmlFile = xmlLogResult.dispose; + // Remove the '--unixml' if it exists, and add it with our path. + const testArgs = this.argsService.filterArguments(args, [XUNIT_FILE]); + testArgs.splice(0, 0, `${XUNIT_FILE}=${xmlLogFile}`); - return promiseToGetXmlLogFile.then(() => { - if (options.debug === true) { - const debugLauncher = serviceContainer.get(ITestDebugLauncher); - const nosetestlauncherargs = [options.cwd, 'nose']; - const debuggerArgs = nosetestlauncherargs.concat(noseTestArgs.concat(testPaths)); - const launchOptions: LaunchOptions = { cwd: options.cwd, args: debuggerArgs, token: options.token, outChannel: options.outChannel, testProvider: 'nosetest' }; - // tslint:disable-next-line:prefer-type-cast no-any - return debugLauncher.launchDebugger(launchOptions) as Promise; - } else { - // tslint:disable-next-line:prefer-type-cast no-any - const runOptions: Options = { - args: noseTestArgs.concat(testPaths), - cwd: options.cwd, - outChannel: options.outChannel, - token: options.token, - workspaceFolder: options.workspaceFolder - }; + // Positional arguments control the tests to be run. + testArgs.push(...testPaths); - // Remove the directory argument, as we'll provide tests to be run. - if (testPaths.length > 0 && runOptions.args.length > 0 && !runOptions.args[0].trim().startsWith('-')) { - runOptions.args.shift(); + if (options.debug === true) { + const debugLauncher = this.serviceContainer.get(ITestDebugLauncher); + const debuggerArgs = [options.cwd, 'nose'].concat(testArgs); + const launchOptions: LaunchOptions = { cwd: options.cwd, args: debuggerArgs, token: options.token, outChannel: options.outChannel, testProvider: NOSETEST_PROVIDER }; + await debugLauncher.launchDebugger(launchOptions); + } else { + const runOptions: Options = { + args: testArgs.concat(testPaths), + cwd: options.cwd, + outChannel: options.outChannel, + token: options.token, + workspaceFolder: options.workspaceFolder + }; + await this.testRunner.run(NOSETEST_PROVIDER, runOptions); } - return run(serviceContainer, 'nosetest', runOptions); + + return options.debug ? options.tests : await this.updateResultsFromLogFiles(options.tests, xmlLogFile, testResultsService); + } catch (ex) { + return Promise.reject(ex); + } finally { + deleteJUnitXmlFile(); } - }).then(() => { - return options.debug ? options.tests : updateResultsFromLogFiles(options.tests, xmlLogFile, testResultsService); - }).then(result => { - xmlLogFileCleanup(); - return result; - }).catch(reason => { - xmlLogFileCleanup(); - return Promise.reject(reason); - }); -} + } -// tslint:disable-next-line:no-any -export function updateResultsFromLogFiles(tests: Tests, outputXmlFile: string, testResultsService: ITestResultsService): Promise { - return updateResultsFromXmlLogFile(tests, outputXmlFile, PassCalculationFormulae.nosetests).then(() => { + private async updateResultsFromLogFiles(tests: Tests, outputXmlFile: string, testResultsService: ITestResultsService): Promise { + await this.xUnitParser.updateResultsFromXmlLogFile(tests, outputXmlFile, PassCalculationFormulae.nosetests); testResultsService.updateResults(tests); return tests; - }); + } + private async getUnitXmlFile(args: string[]): Promise { + const xmlFile = this.argsHelper.getOptionValues(args, XUNIT_FILE); + if (typeof xmlFile === 'string') { + return { filePath: xmlFile, dispose: noop }; + } + + return this.fs.createTemporaryFile('.xml'); + } } diff --git a/src/client/unittests/nosetest/services/argsService.ts b/src/client/unittests/nosetest/services/argsService.ts new file mode 100644 index 000000000000..8fbe92f41006 --- /dev/null +++ b/src/client/unittests/nosetest/services/argsService.ts @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { IServiceContainer } from '../../../ioc/types'; +import { IArgumentsHelper, IArgumentsService, TestFilter } from '../../types'; + +const OptionsWithArguments = ['--attr', '--config', '--cover-html-dir', '--cover-min-percentage', + '--cover-package', '--cover-xml-file', '--debug', '--debug-log', '--doctest-extension', + '--doctest-fixtures', '--doctest-options', '--doctest-result-variable', '--eval-attr', + '--exclude', '--id-file', '--ignore-files', '--include', '--log-config', '--logging-config', + '--logging-datefmt', '--logging-filter', '--logging-format', '--logging-level', '--match', + '--process-timeout', '--processes', '--py3where', '--testmatch', '--tests', '--verbosity', + '--where', '--xunit-file', '--xunit-testsuite-name', + '-A', '-a', '-c', '-e', '-i', '-I', '-l', '-m', '-w', + '--profile-restrict', '--profile-sort', '--profile-stats-file']; + +const OptionsWithoutArguments = ['-h', '--help', '-V', '--version', '-p', '--plugins', + '-v', '--verbose', '--quiet', '-x', '--stop', '-P', '--no-path-adjustment', + '--exe', '--noexe', '--traverse-namespace', '--first-package-wins', '--first-pkg-wins', + '--1st-pkg-wins', '--no-byte-compile', '-s', '--nocapture', '--nologcapture', + '--logging-clear-handlers', '--with-coverage', '--cover-erase', '--cover-tests', + '--cover-inclusive', '--cover-html', '--cover-branches', '--cover-xml', '--pdb', + '--pdb-failures', '--pdb-errors', '--no-deprecated', '--with-doctest', '--doctest-tests', + '--with-isolation', '-d', '--detailed-errors', '--failure-detail', '--no-skip', + '--with-id', '--failed', '--process-restartworker', '--with-xunit', + '--all-modules', '--collect-only', '--with-profile']; + +@injectable() +export class ArgumentsService implements IArgumentsService { + private readonly helper: IArgumentsHelper; + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { + this.helper = serviceContainer.get(IArgumentsHelper); + } + public getKnownOptions(): { withArgs: string[]; withoutArgs: string[] } { + return { + withArgs: OptionsWithArguments, + withoutArgs: OptionsWithoutArguments + }; + } + public getOptionValue(args: string[], option: string): string | string[] | undefined { + return this.helper.getOptionValues(args, option); + } + // tslint:disable-next-line:max-func-body-length + public filterArguments(args: string[], argumentToRemoveOrFilter: string[] | TestFilter): string[] { + const optionsWithoutArgsToRemove: string[] = []; + const optionsWithArgsToRemove: string[] = []; + // Positional arguments in nosetest are test directories and files. + // So if we want to run a specific test, then remove positional args. + let removePositionalArgs = false; + if (Array.isArray(argumentToRemoveOrFilter)) { + argumentToRemoveOrFilter.forEach(item => { + if (OptionsWithArguments.indexOf(item) >= 0) { + optionsWithArgsToRemove.push(item); + } + if (OptionsWithoutArguments.indexOf(item) >= 0) { + optionsWithoutArgsToRemove.push(item); + } + }); + } else { + switch (argumentToRemoveOrFilter) { + case TestFilter.removeTests: { + removePositionalArgs = true; + break; + } + case TestFilter.discovery: { + optionsWithoutArgsToRemove.push(...[ + '-v', '--verbose', '-q', '--quiet', + '-x', '--stop', + '--with-coverage', + ...OptionsWithoutArguments.filter(item => item.startsWith('--cover')), + ...OptionsWithoutArguments.filter(item => item.startsWith('--logging')), + ...OptionsWithoutArguments.filter(item => item.startsWith('--pdb')), + ...OptionsWithoutArguments.filter(item => item.indexOf('xunit') >= 0) + ]); + optionsWithArgsToRemove.push(...[ + '--verbosity', '-l', '--debug', '--cover-package', + ...OptionsWithoutArguments.filter(item => item.startsWith('--cover')), + ...OptionsWithArguments.filter(item => item.startsWith('--logging')), + ...OptionsWithoutArguments.filter(item => item.indexOf('xunit') >= 0) + ]); + break; + } + case TestFilter.debugAll: + case TestFilter.runAll: { + break; + } + case TestFilter.debugSpecific: + case TestFilter.runSpecific: { + removePositionalArgs = true; + break; + } + default: { + throw new Error(`Unsupported Filter '${argumentToRemoveOrFilter}'`); + } + } + } + + let filteredArgs = args.slice(); + if (removePositionalArgs) { + const positionalArgs = this.helper.getPositionalArguments(filteredArgs, OptionsWithArguments, OptionsWithoutArguments); + filteredArgs = filteredArgs.filter(item => positionalArgs.indexOf(item) === -1); + } + return this.helper.filterArguments(filteredArgs, optionsWithArgsToRemove, optionsWithoutArgsToRemove); + } + public getTestFolders(args: string[]): string[] { + return this.helper.getPositionalArguments(args, OptionsWithArguments, OptionsWithoutArguments); + } +} diff --git a/src/client/unittests/nosetest/services/discoveryService.ts b/src/client/unittests/nosetest/services/discoveryService.ts index acd651189b6d..157b24d11257 100644 --- a/src/client/unittests/nosetest/services/discoveryService.ts +++ b/src/client/unittests/nosetest/services/discoveryService.ts @@ -5,43 +5,33 @@ import { inject, injectable, named } from 'inversify'; import { CancellationTokenSource } from 'vscode'; import { IServiceContainer } from '../../../ioc/types'; import { NOSETEST_PROVIDER } from '../../common/constants'; -import { Options, run } from '../../common/runner'; -import { ITestDiscoveryService, ITestsParser, TestDiscoveryOptions, Tests } from '../../common/types'; - -const argsToExcludeForDiscovery = ['-v', '--verbose', - '-q', '--quiet', '-x', '--stop', - '--with-coverage', '--cover-erase', '--cover-tests', - '--cover-inclusive', '--cover-html', '--cover-branches', '--cover-xml', - '--pdb', '--pdb-failures', '--pdb-errors', - '--failed', '--process-restartworker', '--with-xunit']; -const settingsInArgsToExcludeForDiscovery = ['--verbosity']; +import { Options } from '../../common/runner'; +import { ITestDiscoveryService, ITestRunner, ITestsParser, TestDiscoveryOptions, Tests } from '../../common/types'; +import { IArgumentsService, TestFilter } from '../../types'; @injectable() export class TestDiscoveryService implements ITestDiscoveryService { - constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer, - @inject(ITestsParser) @named(NOSETEST_PROVIDER) private testParser: ITestsParser) { } + private argsService: IArgumentsService; + private runner: ITestRunner; + constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer, + @inject(ITestsParser) @named(NOSETEST_PROVIDER) private testParser: ITestsParser) { + this.argsService = this.serviceContainer.get(IArgumentsService, NOSETEST_PROVIDER); + this.runner = this.serviceContainer.get(ITestRunner); + } public async discoverTests(options: TestDiscoveryOptions): Promise { - // Remove unwanted arguments - const args = options.args.filter(arg => { - if (argsToExcludeForDiscovery.indexOf(arg.trim()) !== -1) { - return false; - } - if (settingsInArgsToExcludeForDiscovery.some(setting => setting.indexOf(arg.trim()) === 0)) { - return false; - } - return true; - }); + // Remove unwanted arguments. + const args = this.argsService.filterArguments(options.args, TestFilter.discovery); const token = options.token ? options.token : new CancellationTokenSource().token; const runOptions: Options = { - args: args.concat(['--collect-only', '-vvv']), + args: ['--collect-only', '-vvv'].concat(args), cwd: options.cwd, workspaceFolder: options.workspaceFolder, token, outChannel: options.outChannel }; - const data = await run(this.serviceContainer, NOSETEST_PROVIDER, runOptions); + const data = await this.runner.run(NOSETEST_PROVIDER, runOptions); if (options.token && options.token.isCancellationRequested) { return Promise.reject('cancelled'); } diff --git a/src/client/unittests/nosetest/services/parserService.ts b/src/client/unittests/nosetest/services/parserService.ts index fc706ec67606..634369db83c7 100644 --- a/src/client/unittests/nosetest/services/parserService.ts +++ b/src/client/unittests/nosetest/services/parserService.ts @@ -13,7 +13,7 @@ const NOSE_WANT_FILE_SUFFIX_WITHOUT_EXT = '? True'; @injectable() export class TestsParser implements ITestsParser { - constructor( @inject(ITestsHelper) private testsHelper: ITestsHelper) { } + constructor(@inject(ITestsHelper) private testsHelper: ITestsHelper) { } public parse(content: string, options: ParserOptions): Tests { let testFiles = this.getTestFiles(content, options); // Exclude tests that don't have any functions or test suites. @@ -121,9 +121,10 @@ export class TestsParser implements ITestsParser { time: 0, functionsFailed: 0, functionsPassed: 0 }; - // tslint:disable-next-line:no-non-null-assertion - const cls = testFile.suites.find(suite => suite.name === clsName)!; - cls.functions.push(fn); + const cls = testFile.suites.find(suite => suite.name === clsName); + if (cls) { + cls.functions.push(fn); + } return; } if (line.startsWith('nose.selector: DEBUG: wantFunction (IArgumentsService, this.testProvider); + this.helper = this.serviceContainer.get(ITestsHelper); + this.runner = this.serviceContainer.get(ITestManagerRunner, this.testProvider); } public getDiscoveryOptions(ignoreCache: boolean): TestDiscoveryOptions { const args = this.settings.unitTest.pyTestArgs.slice(0); @@ -24,10 +31,18 @@ export class TestManager extends BaseTestManager { outChannel: this.outputChannel }; } - public async runTestImpl(tests: Tests, testsToRun?: TestsToRun, runFailedTests?: boolean, debug?: boolean): Promise<{}> { - const args = this.settings.unitTest.pyTestArgs.slice(0); + public async runTestImpl(tests: Tests, testsToRun?: TestsToRun, runFailedTests?: boolean, debug?: boolean): Promise { + let args: string[]; + + const runAllTests = this.helper.shouldRunAllTests(testsToRun); + if (debug) { + args = this.argsService.filterArguments(this.settings.unitTest.pyTestArgs, runAllTests ? TestFilter.debugAll : TestFilter.debugSpecific); + } else { + args = this.argsService.filterArguments(this.settings.unitTest.pyTestArgs, runAllTests ? TestFilter.runAll : TestFilter.runSpecific); + } + if (runFailedTests === true && args.indexOf('--lf') === -1 && args.indexOf('--last-failed') === -1) { - args.push('--last-failed'); + args.splice(0, 0, '--last-failed'); } const options: TestRunOptions = { workspaceFolder: this.workspaceFolder, @@ -36,6 +51,6 @@ export class TestManager extends BaseTestManager { token: this.testRunnerCancellationToken!, outChannel: this.outputChannel }; - return runTest(this.serviceContainer, this.testResultsService, options); + return this.runner.runTest(this.testResultsService, options, this); } } diff --git a/src/client/unittests/pytest/runner.ts b/src/client/unittests/pytest/runner.ts index fee6b1e86619..8bff79d5a9b3 100644 --- a/src/client/unittests/pytest/runner.ts +++ b/src/client/unittests/pytest/runner.ts @@ -1,68 +1,92 @@ 'use strict'; -import { createTemporaryFile } from '../../common/helpers'; +import { inject, injectable } from 'inversify'; +import { noop } from '../../common/core.utils'; +import { IFileSystem, TemporaryFile } from '../../common/platform/types'; import { IServiceContainer } from '../../ioc/types'; -import { Options, run } from '../common/runner'; -import { ITestDebugLauncher, ITestResultsService, LaunchOptions, TestRunOptions, Tests } from '../common/types'; -import { PassCalculationFormulae, updateResultsFromXmlLogFile } from '../common/xUnitParser'; +import { PYTEST_PROVIDER } from '../common/constants'; +import { Options } from '../common/runner'; +import { ITestDebugLauncher, ITestManager, ITestResultsService, ITestRunner, IXUnitParser, LaunchOptions, PassCalculationFormulae, TestRunOptions, Tests } from '../common/types'; +import { IArgumentsHelper, IArgumentsService, ITestManagerRunner } from '../types'; -export function runTest(serviceContainer: IServiceContainer, testResultsService: ITestResultsService, options: TestRunOptions): Promise { - let testPaths: string[] = []; - if (options.testsToRun && options.testsToRun.testFolder) { - testPaths = testPaths.concat(options.testsToRun.testFolder.map(f => f.nameToRun)); - } - if (options.testsToRun && options.testsToRun.testFile) { - testPaths = testPaths.concat(options.testsToRun.testFile.map(f => f.nameToRun)); - } - if (options.testsToRun && options.testsToRun.testSuite) { - testPaths = testPaths.concat(options.testsToRun.testSuite.map(f => f.nameToRun)); - } - if (options.testsToRun && options.testsToRun.testFunction) { - testPaths = testPaths.concat(options.testsToRun.testFunction.map(f => f.nameToRun)); +const JunitXmlArg = '--junitxml'; +@injectable() +export class TestManagerRunner implements ITestManagerRunner { + private readonly argsService: IArgumentsService; + private readonly argsHelper: IArgumentsHelper; + private readonly testRunner: ITestRunner; + private readonly xUnitParser: IXUnitParser; + private readonly fs: IFileSystem; + constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { + this.argsService = serviceContainer.get(IArgumentsService, PYTEST_PROVIDER); + this.argsHelper = serviceContainer.get(IArgumentsHelper); + this.testRunner = serviceContainer.get(ITestRunner); + this.xUnitParser = this.serviceContainer.get(IXUnitParser); + this.fs = this.serviceContainer.get(IFileSystem); } + public async runTest(testResultsService: ITestResultsService, options: TestRunOptions, _: ITestManager): Promise { + let testPaths: string[] = []; + if (options.testsToRun && options.testsToRun.testFolder) { + testPaths = testPaths.concat(options.testsToRun.testFolder.map(f => f.nameToRun)); + } + if (options.testsToRun && options.testsToRun.testFile) { + testPaths = testPaths.concat(options.testsToRun.testFile.map(f => f.nameToRun)); + } + if (options.testsToRun && options.testsToRun.testSuite) { + testPaths = testPaths.concat(options.testsToRun.testSuite.map(f => f.nameToRun)); + } + if (options.testsToRun && options.testsToRun.testFunction) { + testPaths = testPaths.concat(options.testsToRun.testFunction.map(f => f.nameToRun)); + } - let xmlLogFile = ''; - let xmlLogFileCleanup: Function; - let args = options.args; + let deleteJUnitXmlFile: Function = noop; + const args = options.args; + try { + const xmlLogResult = await this.getJUnitXmlFile(args); + const xmlLogFile = xmlLogResult.filePath; + deleteJUnitXmlFile = xmlLogResult.dispose; + // Remove the '--junixml' if it exists, and add it with our path. + const testArgs = this.argsService.filterArguments(args, [JunitXmlArg]); + testArgs.splice(0, 0, `${JunitXmlArg}=${xmlLogFile}`); - return createTemporaryFile('.xml').then(xmlLogResult => { - xmlLogFile = xmlLogResult.filePath; - xmlLogFileCleanup = xmlLogResult.cleanupCallback; - if (testPaths.length > 0) { - // Ignore the test directories, as we're running a specific test - args = args.filter(arg => arg.trim().startsWith('-')); - } - const testArgs = testPaths.concat(args, [`--junitxml=${xmlLogFile}`]); - if (options.debug) { - const debugLauncher = serviceContainer.get(ITestDebugLauncher); - const pytestlauncherargs = [options.cwd, 'pytest']; - const debuggerArgs = pytestlauncherargs.concat(testArgs); - const launchOptions: LaunchOptions = { cwd: options.cwd, args: debuggerArgs, token: options.token, outChannel: options.outChannel, testProvider: 'pytest' }; - // tslint:disable-next-line:prefer-type-cast no-any - return debugLauncher.launchDebugger(launchOptions) as Promise; - } else { - const runOptions: Options = { - args: testArgs, - cwd: options.cwd, - outChannel: options.outChannel, - token: options.token, - workspaceFolder: options.workspaceFolder - }; - return run(serviceContainer, 'pytest', runOptions); + // Positional arguments control the tests to be run. + testArgs.push(...testPaths); + + if (options.debug) { + const debugLauncher = this.serviceContainer.get(ITestDebugLauncher); + const debuggerArgs = [options.cwd, 'pytest'].concat(testArgs); + const launchOptions: LaunchOptions = { cwd: options.cwd, args: debuggerArgs, token: options.token, outChannel: options.outChannel, testProvider: PYTEST_PROVIDER }; + await debugLauncher.launchDebugger(launchOptions); + } else { + const runOptions: Options = { + args: testArgs, + cwd: options.cwd, + outChannel: options.outChannel, + token: options.token, + workspaceFolder: options.workspaceFolder + }; + await this.testRunner.run(PYTEST_PROVIDER, runOptions); + } + + return options.debug ? options.tests : await this.updateResultsFromLogFiles(options.tests, xmlLogFile, testResultsService); + } catch (ex) { + return Promise.reject(ex); + } finally { + deleteJUnitXmlFile(); } - }).then(() => { - return options.debug ? options.tests : updateResultsFromLogFiles(options.tests, xmlLogFile, testResultsService); - }).then(result => { - xmlLogFileCleanup(); - return result; - }).catch(reason => { - xmlLogFileCleanup(); - return Promise.reject(reason); - }); -} + } -export function updateResultsFromLogFiles(tests: Tests, outputXmlFile: string, testResultsService: ITestResultsService): Promise { - return updateResultsFromXmlLogFile(tests, outputXmlFile, PassCalculationFormulae.pytest).then(() => { + private async updateResultsFromLogFiles(tests: Tests, outputXmlFile: string, testResultsService: ITestResultsService): Promise { + await this.xUnitParser.updateResultsFromXmlLogFile(tests, outputXmlFile, PassCalculationFormulae.pytest); testResultsService.updateResults(tests); return tests; - }); + } + + private async getJUnitXmlFile(args: string[]): Promise { + const xmlFile = this.argsHelper.getOptionValues(args, JunitXmlArg); + if (typeof xmlFile === 'string') { + return { filePath: xmlFile, dispose: noop }; + } + return this.fs.createTemporaryFile('.xml'); + } + } diff --git a/src/client/unittests/pytest/services/argsService.ts b/src/client/unittests/pytest/services/argsService.ts new file mode 100644 index 000000000000..c539fdbae265 --- /dev/null +++ b/src/client/unittests/pytest/services/argsService.ts @@ -0,0 +1,156 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { IServiceContainer } from '../../../ioc/types'; +import { IArgumentsHelper, IArgumentsService, TestFilter } from '../../types'; + +const OptionsWithArguments = ['-c', '-k', '-m', '-o', '-p', '-r', '-W', + '--assert', '--basetemp', '--capture', '--color', '--confcutdir', + '--deselect', '--dist', '--doctest-glob', + '--doctest-report', '--durations', '--ignore', '--import-mode', + '--junit-prefix', '--junit-xml', '--last-failed-no-failures', + '--lfnf', '--log-cli-date-format', '--log-cli-format', + '--log-cli-level', '--log-date-format', '--log-file', + '--log-file-date-format', '--log-file-format', '--log-file-level', + '--log-format', '--log-level', '--maxfail', '--override-ini', + '--pastebin', '--pdbcls', '--pythonwarnings', '--result-log', + '--rootdir', '--show-capture', '--tb', '--verbosity', '--max-slave-restart', + '--numprocesses', '--rsyncdir', '--rsyncignore', '--tx']; + +const OptionsWithoutArguments = ['--cache-clear', '--cache-show', '--collect-in-virtualenv', + '--collect-only', '--continue-on-collection-errors', '--debug', '--disable-pytest-warnings', + '--disable-warnings', '--doctest-continue-on-failure', '--doctest-ignore-import-errors', + '--doctest-modules', '--exitfirst', '--failed-first', '--ff', '--fixtures', + '--fixtures-per-test', '--force-sugar', '--full-trace', '--funcargs', '--help', + '--keep-duplicates', '--last-failed', '--lf', '--markers', '--new-first', '--nf', + '--no-print-logs', '--noconftest', '--old-summary', '--pdb', '--pyargs', + '--quiet', '--runxfail', '--setup-only', '--setup-plan', '--setup-show', '--showlocals', + '--strict', '--trace-config', '--verbose', '--version', '-h', '-l', '-q', '-s', '-v', '-x', + '--boxed', '--forked', '--looponfail', '--tx', '-d']; + +@injectable() +export class ArgumentsService implements IArgumentsService { + private readonly helper: IArgumentsHelper; + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { + this.helper = serviceContainer.get(IArgumentsHelper); + } + public getKnownOptions(): { withArgs: string[]; withoutArgs: string[] } { + return { + withArgs: OptionsWithArguments, + withoutArgs: OptionsWithoutArguments + }; + } + public getOptionValue(args: string[], option: string): string | string[] | undefined { + return this.helper.getOptionValues(args, option); + } + public filterArguments(args: string[], argumentToRemoveOrFilter: string[] | TestFilter): string[] { + const optionsWithoutArgsToRemove: string[] = []; + const optionsWithArgsToRemove: string[] = []; + // Positional arguments in pytest are test directories and files. + // So if we want to run a specific test, then remove positional args. + let removePositionalArgs = false; + if (Array.isArray(argumentToRemoveOrFilter)) { + argumentToRemoveOrFilter.forEach(item => { + if (OptionsWithArguments.indexOf(item) >= 0) { + optionsWithArgsToRemove.push(item); + } + if (OptionsWithoutArguments.indexOf(item) >= 0) { + optionsWithoutArgsToRemove.push(item); + } + }); + } else { + switch (argumentToRemoveOrFilter) { + case TestFilter.removeTests: { + optionsWithoutArgsToRemove.push(...[ + '--lf', '--last-failed', + '--ff', '--failed-first', + '--nf', '--new-first' + ]); + optionsWithArgsToRemove.push(...[ + '-k', '-m', + '--lfnf', '--last-failed-no-failures' + ]); + removePositionalArgs = true; + break; + } + case TestFilter.discovery: { + optionsWithoutArgsToRemove.push(...[ + '-x', '--exitfirst', + '--fixtures', '--funcargs', + '--fixtures-per-test', '--pdb', + '--lf', '--last-failed', + '--ff', '--failed-first', + '--nf', '--new-first', + '--cache-show', + '-v', '--verbose', '-q', '-quiet', + '-l', '--showlocals', + '--no-print-logs', + '--debug', + '--setup-only', '--setup-show', '--setup-plan' + ]); + optionsWithArgsToRemove.push(...[ + '-m', '--maxfail', + '--pdbcls', '--capture', + '--lfnf', '--last-failed-no-failures', + '--verbosity', '-r', + '--tb', + '--rootdir', '--show-capture', + '--durations', + '--junit-xml', '--junit-prefix', '--result-log', + '-W', '--pythonwarnings', + '--log-*' + ]); + removePositionalArgs = true; + break; + } + case TestFilter.debugAll: + case TestFilter.runAll: { + optionsWithoutArgsToRemove.push('--collect-only'); + break; + } + case TestFilter.debugSpecific: + case TestFilter.runSpecific: { + optionsWithoutArgsToRemove.push(...[ + '--collect-only', + '--lf', '--last-failed', + '--ff', '--failed-first', + '--nf', '--new-first' + ]); + optionsWithArgsToRemove.push(...[ + '-k', '-m', + '--lfnf', '--last-failed-no-failures' + ]); + removePositionalArgs = true; + break; + } + default: { + throw new Error(`Unsupported Filter '${argumentToRemoveOrFilter}'`); + } + } + } + + let filteredArgs = args.slice(); + if (removePositionalArgs) { + const positionalArgs = this.helper.getPositionalArguments(filteredArgs, OptionsWithArguments, OptionsWithoutArguments); + filteredArgs = filteredArgs.filter(item => positionalArgs.indexOf(item) === -1); + } + return this.helper.filterArguments(filteredArgs, optionsWithArgsToRemove, optionsWithoutArgsToRemove); + } + public getTestFolders(args: string[]): string[] { + const testDirs = this.helper.getOptionValues(args, '--rootdir'); + if (typeof testDirs === 'string') { + return [testDirs]; + } + if (Array.isArray(testDirs) && testDirs.length > 0) { + return testDirs; + } + const positionalArgs = this.helper.getPositionalArguments(args, OptionsWithArguments, OptionsWithoutArguments); + // Positional args in pytest are files or directories. + // Remove files from the args, and what's left are test directories. + // If users enter test modules/methods, then its not supported. + return positionalArgs.filter(arg => !arg.toUpperCase().endsWith('.PY')); + } +} diff --git a/src/client/unittests/pytest/services/discoveryService.ts b/src/client/unittests/pytest/services/discoveryService.ts index c3fc02e131be..57d9a3902e5d 100644 --- a/src/client/unittests/pytest/services/discoveryService.ts +++ b/src/client/unittests/pytest/services/discoveryService.ts @@ -4,48 +4,67 @@ import { inject, injectable, named } from 'inversify'; import { CancellationTokenSource } from 'vscode'; import { IServiceContainer } from '../../../ioc/types'; -import { PYTEST_PROVIDER, UNITTEST_PROVIDER } from '../../common/constants'; -import { Options, run } from '../../common/runner'; -import { ITestDiscoveryService, ITestsParser, TestDiscoveryOptions, Tests } from '../../common/types'; - -const argsToExcludeForDiscovery = ['-x', '--exitfirst', - '--fixtures-per-test', '--pdb', '--runxfail', - '--lf', '--last-failed', '--ff', '--failed-first', - '--cache-show', '--cache-clear', - '-v', '--verbose', '-q', '-quiet', - '--disable-pytest-warnings', '-l', '--showlocals']; - -type PytestDiscoveryOptions = TestDiscoveryOptions & { - startDirectory: string; - pattern: string; -}; +import { PYTEST_PROVIDER } from '../../common/constants'; +import { ITestDiscoveryService, ITestRunner, ITestsHelper, ITestsParser, Options, TestDiscoveryOptions, Tests } from '../../common/types'; +import { IArgumentsService, TestFilter } from '../../types'; @injectable() export class TestDiscoveryService implements ITestDiscoveryService { - constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer, - @inject(ITestsParser) @named(PYTEST_PROVIDER) private testParser: ITestsParser) { } + private argsService: IArgumentsService; + private helper: ITestsHelper; + private runner: ITestRunner; + constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer, + @inject(ITestsParser) @named(PYTEST_PROVIDER) private testParser: ITestsParser) { + this.argsService = this.serviceContainer.get(IArgumentsService, PYTEST_PROVIDER); + this.helper = this.serviceContainer.get(ITestsHelper); + this.runner = this.serviceContainer.get(ITestRunner); + } public async discoverTests(options: TestDiscoveryOptions): Promise { - // Remove unwanted arguments - const args = options.args.filter(arg => { - if (argsToExcludeForDiscovery.indexOf(arg.trim()) !== -1) { - return false; - } - return true; - }); - if (options.ignoreCache && args.indexOf('--cache-clear') === -1) { - args.push('--cache-clear'); + const args = this.buildTestCollectionArgs(options); + + // Collect tests for each test directory separately and merge. + const testDirectories = this.argsService.getTestFolders(options.args); + if (testDirectories.length === 0) { + const opts = { + ...options, + args + }; + return this.discoverTestsInTestDirectory(opts); } + const results = await Promise.all(testDirectories.map(testDir => { + // Add test directory as a positional argument. + const opts = { + ...options, + args: [...args, testDir] + }; + return this.discoverTestsInTestDirectory(opts); + })); + return this.helper.mergeTests(results); + } + private buildTestCollectionArgs(options: TestDiscoveryOptions) { + // Remove unwnted arguments (which happen to be test directories & test specific args). + const args = this.argsService.filterArguments(options.args, TestFilter.discovery); + if (options.ignoreCache && args.indexOf('--cache-clear') === -1) { + args.splice(0, 0, '--cache-clear'); + } + if (args.indexOf('-s') === -1) { + args.splice(0, 0, '-s'); + } + args.splice(0, 0, '--collect-only'); + return args; + } + private async discoverTestsInTestDirectory(options: TestDiscoveryOptions): Promise { const token = options.token ? options.token : new CancellationTokenSource().token; const runOptions: Options = { - args: args.concat(['--collect-only']), + args: options.args, cwd: options.cwd, workspaceFolder: options.workspaceFolder, token, outChannel: options.outChannel }; - const data = await run(this.serviceContainer, PYTEST_PROVIDER, runOptions); + const data = await this.runner.run(PYTEST_PROVIDER, runOptions); if (options.token && options.token.isCancellationRequested) { return Promise.reject('cancelled'); } diff --git a/src/client/unittests/pytest/services/parserService.ts b/src/client/unittests/pytest/services/parserService.ts index f0fad3b88b12..822ab930ae04 100644 --- a/src/client/unittests/pytest/services/parserService.ts +++ b/src/client/unittests/pytest/services/parserService.ts @@ -5,13 +5,13 @@ import { inject, injectable } from 'inversify'; import * as os from 'os'; import * as path from 'path'; import { convertFileToPackage, extractBetweenDelimiters } from '../../common/testUtils'; -import { ITestsHelper, ITestsParser, ParserOptions, TestDiscoveryOptions, TestFile, TestFunction, Tests, TestStatus, TestSuite } from '../../common/types'; +import { ITestsHelper, ITestsParser, ParserOptions, TestFile, TestFunction, Tests, TestSuite } from '../../common/types'; const DELIMITER = '\''; @injectable() export class TestsParser implements ITestsParser { - constructor( @inject(ITestsHelper) private testsHelper: ITestsHelper) { } + constructor(@inject(ITestsHelper) private testsHelper: ITestsHelper) { } public parse(content: string, options: ParserOptions): Tests { const testFiles = this.getTestFiles(content, options); return this.testsHelper.flattenTestFiles(testFiles); @@ -20,7 +20,7 @@ export class TestsParser implements ITestsParser { private getTestFiles(content: string, options: ParserOptions) { let logOutputLines: string[] = ['']; const testFiles: TestFile[] = []; - const parentNodes: { indent: number, item: TestFile | TestSuite }[] = []; + const parentNodes: { indent: number; item: TestFile | TestSuite }[] = []; const errorLine = /==*( *)ERRORS( *)=*/; const errorFileLine = /__*( *)ERROR collecting (.*)/; @@ -75,7 +75,7 @@ export class TestsParser implements ITestsParser { } private parsePyTestModuleCollectionError(rootDirectory: string, lines: string[], testFiles: TestFile[], - parentNodes: { indent: number, item: TestFile | TestSuite }[]) { + parentNodes: { indent: number; item: TestFile | TestSuite }[]) { lines = lines.filter(line => line.trim().length > 0); if (lines.length <= 1) { @@ -98,7 +98,7 @@ export class TestsParser implements ITestsParser { return; } - private parsePyTestModuleCollectionResult(rootDirectory: string, lines: string[], testFiles: TestFile[], parentNodes: { indent: number, item: TestFile | TestSuite }[]) { + private parsePyTestModuleCollectionResult(rootDirectory: string, lines: string[], testFiles: TestFile[], parentNodes: { indent: number; item: TestFile | TestSuite }[]) { let currentPackage: string = ''; lines.forEach(line => { @@ -146,7 +146,7 @@ export class TestsParser implements ITestsParser { }); } - private findParentOfCurrentItem(indentOfCurrentItem: number, parentNodes: { indent: number, item: TestFile | TestSuite }[]): { indent: number, item: TestFile | TestSuite } | undefined { + private findParentOfCurrentItem(indentOfCurrentItem: number, parentNodes: { indent: number; item: TestFile | TestSuite }[]): { indent: number; item: TestFile | TestSuite } | undefined { while (parentNodes.length > 0) { const parentNode = parentNodes[parentNodes.length - 1]; if (parentNode.indent < indentOfCurrentItem) { diff --git a/src/client/unittests/serviceRegistry.ts b/src/client/unittests/serviceRegistry.ts index 92a4f1b5f6e7..10b0a22cf140 100644 --- a/src/client/unittests/serviceRegistry.ts +++ b/src/client/unittests/serviceRegistry.ts @@ -3,8 +3,10 @@ import { Uri } from 'vscode'; import { IServiceContainer, IServiceManager } from '../ioc/types'; +import { ArgumentsHelper } from './common/argumentsHelper'; import { NOSETEST_PROVIDER, PYTEST_PROVIDER, UNITTEST_PROVIDER } from './common/constants'; import { DebugLauncher } from './common/debugLauncher'; +import { TestRunner } from './common/runner'; import { TestConfigSettingsService } from './common/services/configSettingService'; import { TestCollectionStorageService } from './common/services/storageService'; import { TestManagerService } from './common/services/testManagerService'; @@ -16,21 +18,29 @@ import { TestFolderGenerationVisitor } from './common/testVisitors/folderGenerat import { TestResultResetVisitor } from './common/testVisitors/resultResetVisitor'; import { ITestCollectionStorageService, ITestConfigSettingsService, ITestDebugLauncher, ITestDiscoveryService, ITestManager, ITestManagerFactory, ITestManagerService, ITestManagerServiceFactory, - ITestResultsService, ITestsHelper, ITestsParser, ITestVisitor, IUnitTestSocketServer, IWorkspaceTestManagerService, TestProvider + ITestResultsService, ITestRunner, ITestsHelper, ITestsParser, ITestVisitor, IUnitTestSocketServer, IWorkspaceTestManagerService, IXUnitParser, TestProvider } from './common/types'; +import { XUnitParser } from './common/xUnitParser'; import { UnitTestConfigurationService } from './configuration'; import { TestConfigurationManagerFactory } from './configurationFactory'; import { TestResultDisplay } from './display/main'; import { TestDisplay } from './display/picker'; import { UnitTestManagementService } from './main'; import { TestManager as NoseTestManager } from './nosetest/main'; +import { TestManagerRunner as NoseTestManagerRunner } from './nosetest/runner'; +import { ArgumentsService as NoseTestArgumentsService } from './nosetest/services/argsService'; import { TestDiscoveryService as NoseTestDiscoveryService } from './nosetest/services/discoveryService'; import { TestsParser as NoseTestTestsParser } from './nosetest/services/parserService'; import { TestManager as PyTestTestManager } from './pytest/main'; +import { TestManagerRunner as PytestManagerRunner } from './pytest/runner'; +import { ArgumentsService as PyTestArgumentsService } from './pytest/services/argsService'; import { TestDiscoveryService as PytestTestDiscoveryService } from './pytest/services/discoveryService'; import { TestsParser as PytestTestsParser } from './pytest/services/parserService'; -import { ITestConfigurationManagerFactory, ITestDisplay, ITestResultDisplay, IUnitTestConfigurationService, IUnitTestManagementService } from './types'; +import { IArgumentsHelper, IArgumentsService, ITestConfigurationManagerFactory, ITestDisplay, ITestManagerRunner, ITestResultDisplay, IUnitTestConfigurationService, IUnitTestHelper, IUnitTestManagementService } from './types'; +import { UnitTestHelper } from './unittest/helper'; import { TestManager as UnitTestTestManager } from './unittest/main'; +import { TestManagerRunner as UnitTestTestManagerRunner } from './unittest/runner'; +import { ArgumentsService as UnitTestArgumentsService } from './unittest/services/argsService'; import { TestDiscoveryService as UnitTestTestDiscoveryService } from './unittest/services/discoveryService'; import { TestsParser as UnitTestTestsParser } from './unittest/services/parserService'; import { UnitTestSocketServer } from './unittest/socketServer'; @@ -57,6 +67,18 @@ export function registerTypes(serviceManager: IServiceManager) { serviceManager.add(ITestDiscoveryService, PytestTestDiscoveryService, PYTEST_PROVIDER); serviceManager.add(ITestDiscoveryService, NoseTestDiscoveryService, NOSETEST_PROVIDER); + serviceManager.add(IArgumentsHelper, ArgumentsHelper); + serviceManager.add(ITestRunner, TestRunner); + serviceManager.add(IXUnitParser, XUnitParser); + serviceManager.add(IUnitTestHelper, UnitTestHelper); + + serviceManager.add(IArgumentsService, PyTestArgumentsService, PYTEST_PROVIDER); + serviceManager.add(IArgumentsService, NoseTestArgumentsService, NOSETEST_PROVIDER); + serviceManager.add(IArgumentsService, UnitTestArgumentsService, UNITTEST_PROVIDER); + serviceManager.add(ITestManagerRunner, PytestManagerRunner, PYTEST_PROVIDER); + serviceManager.add(ITestManagerRunner, NoseTestManagerRunner, NOSETEST_PROVIDER); + serviceManager.add(ITestManagerRunner, UnitTestTestManagerRunner, UNITTEST_PROVIDER); + serviceManager.addSingleton(IUnitTestConfigurationService, UnitTestConfigurationService); serviceManager.addSingleton(IUnitTestManagementService, UnitTestManagementService); serviceManager.addSingleton(ITestResultDisplay, TestResultDisplay); diff --git a/src/client/unittests/types.ts b/src/client/unittests/types.ts index ebf063581354..b03bda2856eb 100644 --- a/src/client/unittests/types.ts +++ b/src/client/unittests/types.ts @@ -7,7 +7,7 @@ import { Disposable, Event, TextDocument, Uri } from 'vscode'; import { Product } from '../common/types'; import { PythonSymbolProvider } from '../providers/symbolProvider'; import { CommandSource } from './common/constants'; -import { FlattenedTestFunction, ITestManager, TestFile, TestFunction, Tests, TestsToRun, UnitTestProduct } from './common/types'; +import { FlattenedTestFunction, ITestManager, ITestResultsService, TestFile, TestFunction, TestRunOptions, Tests, TestsToRun, UnitTestProduct } from './common/types'; export const IUnitTestConfigurationService = Symbol('IUnitTestConfigurationService'); export interface IUnitTestConfigurationService { @@ -67,3 +67,38 @@ export const ITestConfigurationManagerFactory = Symbol('ITestConfigurationManage export interface ITestConfigurationManagerFactory { create(wkspace: Uri, product: Product): ITestConfigurationManager; } + +export enum TestFilter { + removeTests = 'removeTests', + discovery = 'discovery', + runAll = 'runAll', + runSpecific = 'runSpecific', + debugAll = 'debugAll', + debugSpecific = 'debugSpecific' +} +export const IArgumentsService = Symbol('IArgumentsService'); +export interface IArgumentsService { + getKnownOptions(): { withArgs: string[]; withoutArgs: string[] }; + getOptionValue(args: string[], option: string): string | string[] | undefined; + filterArguments(args: string[], argumentToRemove: string[]): string[]; + // tslint:disable-next-line:unified-signatures + filterArguments(args: string[], filter: TestFilter): string[]; + getTestFolders(args: string[]): string[]; +} +export const IArgumentsHelper = Symbol('IArgumentsHelper'); +export interface IArgumentsHelper { + getOptionValues(args: string[], option: string): string | string[] | undefined; + filterArguments(args: string[], optionsWithArguments?: string[], optionsWithoutArguments?: string[]): string[]; + getPositionalArguments(args: string[], optionsWithArguments?: string[], optionsWithoutArguments?: string[]): string[]; +} + +export const ITestManagerRunner = Symbol('ITestManagerRunner'); +export interface ITestManagerRunner { + runTest(testResultsService: ITestResultsService, options: TestRunOptions, testManager: ITestManager): Promise; +} + +export const IUnitTestHelper = Symbol('IUnitTestHelper'); +export interface IUnitTestHelper { + getStartDirectory(args: string[]): string; + getIdsOfTestsToRun(tests: Tests, testsToRun: TestsToRun): string[]; +} diff --git a/src/client/unittests/unittest/helper.ts b/src/client/unittests/unittest/helper.ts new file mode 100644 index 000000000000..a97aed3102cf --- /dev/null +++ b/src/client/unittests/unittest/helper.ts @@ -0,0 +1,52 @@ + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { IServiceContainer } from '../../ioc/types'; +import { Tests, TestsToRun } from '../common/types'; +import { IArgumentsHelper, IUnitTestHelper } from '../types'; + +@injectable() +export class UnitTestHelper implements IUnitTestHelper { + private readonly argsHelper: IArgumentsHelper; + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { + this.argsHelper = serviceContainer.get(IArgumentsHelper); + } + public getStartDirectory(args: string[]): string { + const shortValue = this.argsHelper.getOptionValues(args, '-s'); + if (typeof shortValue === 'string') { + return shortValue; + } + const longValue = this.argsHelper.getOptionValues(args, '--start-directory'); + if (typeof longValue === 'string') { + return longValue; + } + return '.'; + } + public getIdsOfTestsToRun(tests: Tests, testsToRun: TestsToRun): string[] { + const testIds: string[] = []; + if (testsToRun && testsToRun.testFolder) { + // Get test ids of files in these folders. + testsToRun.testFolder.map(folder => { + tests.testFiles.forEach(f => { + if (f.fullPath.startsWith(folder.name)) { + testIds.push(f.nameToRun); + } + }); + }); + } + if (testsToRun && testsToRun.testFile) { + testIds.push(...testsToRun.testFile.map(f => f.nameToRun)); + } + if (testsToRun && testsToRun.testSuite) { + testIds.push(...testsToRun.testSuite.map(f => f.nameToRun)); + } + if (testsToRun && testsToRun.testFunction) { + testIds.push(...testsToRun.testFunction.map(f => f.nameToRun)); + } + return testIds; + } +} diff --git a/src/client/unittests/unittest/main.ts b/src/client/unittests/unittest/main.ts index ef9a8f134879..b9056579ea0b 100644 --- a/src/client/unittests/unittest/main.ts +++ b/src/client/unittests/unittest/main.ts @@ -1,20 +1,27 @@ import { Uri } from 'vscode'; -import { PythonSettings } from '../../common/configSettings'; +import { noop } from '../../common/core.utils'; import { Product } from '../../common/types'; import { IServiceContainer } from '../../ioc/types'; +import { UNITTEST_PROVIDER } from '../common/constants'; import { BaseTestManager } from '../common/managers/baseTestManager'; -import { TestDiscoveryOptions, TestRunOptions, Tests, TestStatus, TestsToRun } from '../common/types'; -import { runTest } from './runner'; +import { ITestsHelper, TestDiscoveryOptions, TestRunOptions, Tests, TestStatus, TestsToRun } from '../common/types'; +import { IArgumentsService, ITestManagerRunner, TestFilter } from '../types'; export class TestManager extends BaseTestManager { + private readonly argsService: IArgumentsService; + private readonly helper: ITestsHelper; + private readonly runner: ITestManagerRunner; public get enabled() { - return PythonSettings.getInstance(this.workspaceFolder).unitTest.unittestEnabled; + return this.settings.unitTest.unittestEnabled; } constructor(workspaceFolder: Uri, rootDirectory: string, serviceContainer: IServiceContainer) { - super('unittest', Product.unittest, workspaceFolder, rootDirectory, serviceContainer); + super(UNITTEST_PROVIDER, Product.unittest, workspaceFolder, rootDirectory, serviceContainer); + this.argsService = this.serviceContainer.get(IArgumentsService, this.testProvider); + this.helper = this.serviceContainer.get(ITestsHelper); + this.runner = this.serviceContainer.get(ITestManagerRunner, this.testProvider); } - // tslint:disable-next-line:no-empty public configure() { + noop(); } public getDiscoveryOptions(ignoreCache: boolean): TestDiscoveryOptions { const args = this.settings.unitTest.unittestArgs.slice(0); @@ -26,7 +33,15 @@ export class TestManager extends BaseTestManager { }; } public async runTestImpl(tests: Tests, testsToRun?: TestsToRun, runFailedTests?: boolean, debug?: boolean): Promise<{}> { - const args = this.settings.unitTest.unittestArgs.slice(0); + let args: string[]; + + const runAllTests = this.helper.shouldRunAllTests(testsToRun); + if (debug) { + args = this.argsService.filterArguments(this.settings.unitTest.unittestArgs, runAllTests ? TestFilter.debugAll : TestFilter.debugSpecific); + } else { + args = this.argsService.filterArguments(this.settings.unitTest.unittestArgs, runAllTests ? TestFilter.runAll : TestFilter.runSpecific); + } + if (runFailedTests === true) { testsToRun = { testFile: [], testFolder: [], testSuite: [], testFunction: [] }; testsToRun.testFunction = tests.testFunctions.filter(fn => { @@ -40,6 +55,6 @@ export class TestManager extends BaseTestManager { token: this.testRunnerCancellationToken!, outChannel: this.outputChannel }; - return runTest(this.serviceContainer, this, this.testResultsService, options); + return this.runner.runTest(this.testResultsService, options, this); } } diff --git a/src/client/unittests/unittest/runner.ts b/src/client/unittests/unittest/runner.ts index 472ba315137f..214eab94a93a 100644 --- a/src/client/unittests/unittest/runner.ts +++ b/src/client/unittests/unittest/runner.ts @@ -1,9 +1,14 @@ 'use strict'; +import { inject, injectable } from 'inversify'; import * as path from 'path'; +import { EXTENSION_ROOT_DIR } from '../../common/constants'; +import { noop } from '../../common/core.utils'; +import { ILogger } from '../../common/types'; import { IServiceContainer } from '../../ioc/types'; -import { BaseTestManager } from '../common/managers/baseTestManager'; -import { Options, run } from '../common/runner'; -import { ITestDebugLauncher, ITestResultsService, IUnitTestSocketServer, LaunchOptions, TestRunOptions, Tests, TestStatus, TestsToRun } from '../common/types'; +import { UNITTEST_PROVIDER } from '../common/constants'; +import { Options } from '../common/runner'; +import { ITestDebugLauncher, ITestManager, ITestResultsService, ITestRunner, IUnitTestSocketServer, LaunchOptions, TestRunOptions, Tests, TestStatus, TestsToRun } from '../common/types'; +import { IArgumentsHelper, ITestManagerRunner, IUnitTestHelper } from '../types'; type TestStatusMap = { status: TestStatus; @@ -11,13 +16,9 @@ type TestStatusMap = { }; const outcomeMapping = new Map(); -// tslint:disable-next-line:no-backbone-get-set-outside-model outcomeMapping.set('passed', { status: TestStatus.Pass, summaryProperty: 'passed' }); -// tslint:disable-next-line:no-backbone-get-set-outside-model outcomeMapping.set('failed', { status: TestStatus.Fail, summaryProperty: 'failures' }); -// tslint:disable-next-line:no-backbone-get-set-outside-model outcomeMapping.set('error', { status: TestStatus.Error, summaryProperty: 'errors' }); -// tslint:disable-next-line:no-backbone-get-set-outside-model outcomeMapping.set('skipped', { status: TestStatus.Skipped, summaryProperty: 'skipped' }); interface ITestData { @@ -27,64 +28,63 @@ interface ITestData { traceback: string; } -// tslint:disable-next-line:max-func-body-length -export async function runTest(serviceContainer: IServiceContainer, testManager: BaseTestManager, testResultsService: ITestResultsService, options: TestRunOptions): Promise { - options.tests.summary.errors = 0; - options.tests.summary.failures = 0; - options.tests.summary.passed = 0; - options.tests.summary.skipped = 0; - let failFast = false; - const testLauncherFile = path.join(__dirname, '..', '..', '..', '..', 'pythonFiles', 'PythonTools', 'visualstudio_py_testlauncher.py'); - const server = serviceContainer.get(IUnitTestSocketServer); - server.on('error', (message: string, ...data: string[]) => { - // tslint:disable-next-line:no-console - console.log(`${message} ${data.join(' ')}`); - }); - // tslint:disable-next-line:no-empty - server.on('log', (message: string, ...data: string[]) => { - }); - // tslint:disable-next-line:no-empty no-any - server.on('connect', (data: any) => { - }); - // tslint:disable-next-line:no-empty - server.on('start', (data: { test: string }) => { - }); - server.on('result', (data: ITestData) => { - const test = options.tests.testFunctions.find(t => t.testFunction.nameToRun === data.test); - const statusDetails = outcomeMapping.get(data.outcome)!; - if (test) { - test.testFunction.status = statusDetails.status; - test.testFunction.message = data.message; - test.testFunction.traceback = data.traceback; - options.tests.summary[statusDetails.summaryProperty] += 1; - - if (failFast && (statusDetails.summaryProperty === 'failures' || statusDetails.summaryProperty === 'errors')) { - testManager.stop(); - } - } else { - if (statusDetails) { +@injectable() +export class TestManagerRunner implements ITestManagerRunner { + private readonly argsHelper: IArgumentsHelper; + private readonly helper: IUnitTestHelper; + private readonly testRunner: ITestRunner; + private readonly server: IUnitTestSocketServer; + private readonly logger: ILogger; + constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { + this.argsHelper = serviceContainer.get(IArgumentsHelper); + this.testRunner = serviceContainer.get(ITestRunner); + this.server = this.serviceContainer.get(IUnitTestSocketServer); + this.logger = this.serviceContainer.get(ILogger); + this.helper = this.serviceContainer.get(IUnitTestHelper); + } + public async runTest(testResultsService: ITestResultsService, options: TestRunOptions, testManager: ITestManager): Promise { + options.tests.summary.errors = 0; + options.tests.summary.failures = 0; + options.tests.summary.passed = 0; + options.tests.summary.skipped = 0; + let failFast = false; + const testLauncherFile = path.join(EXTENSION_ROOT_DIR, 'pythonFiles', 'PythonTools', 'visualstudio_py_testlauncher.py'); + this.server.on('error', (message: string, ...data: string[]) => this.logger.logError(`${message} ${data.join(' ')}`)); + this.server.on('log', noop); + this.server.on('connect', noop); + this.server.on('start', noop); + this.server.on('socket.disconnected', noop); + this.server.on('result', (data: ITestData) => { + const test = options.tests.testFunctions.find(t => t.testFunction.nameToRun === data.test); + const statusDetails = outcomeMapping.get(data.outcome)!; + if (test) { + test.testFunction.status = statusDetails.status; + test.testFunction.message = data.message; + test.testFunction.traceback = data.traceback; options.tests.summary[statusDetails.summaryProperty] += 1; + + if (failFast && (statusDetails.summaryProperty === 'failures' || statusDetails.summaryProperty === 'errors')) { + testManager.stop(); + } + } else { + if (statusDetails) { + options.tests.summary[statusDetails.summaryProperty] += 1; + } } - } - }); - // tslint:disable-next-line:no-empty no-any - server.on('socket.disconnected', (data: any) => { - }); + }); - return server.start().then(port => { - const testPaths: string[] = getIdsOfTestsToRun(options.tests, options.testsToRun!); + const port = await this.server.start(); + const testPaths: string[] = this.helper.getIdsOfTestsToRun(options.tests, options.testsToRun!); for (let counter = 0; counter < testPaths.length; counter += 1) { testPaths[counter] = `-t${testPaths[counter].trim()}`; } - const startTestDiscoveryDirectory = getStartDirectory(options.args); - function runTestInternal(testFile: string = '', testId: string = '') { - let testArgs = buildTestArgs(options.args); + const runTestInternal = async (testFile: string = '', testId: string = '') => { + let testArgs = this.buildTestArgs(options.args); failFast = testArgs.indexOf('--uf') >= 0; testArgs = testArgs.filter(arg => arg !== '--uf'); testArgs.push(`--result-port=${port}`); - testArgs.push(`--us=${startTestDiscoveryDirectory}`); if (testId.length > 0) { testArgs.push(`-t${testId}`); } @@ -92,13 +92,11 @@ export async function runTest(serviceContainer: IServiceContainer, testManager: testArgs.push(`--testFile=${testFile}`); } if (options.debug === true) { - const debugLauncher = serviceContainer.get(ITestDebugLauncher); - testArgs.push(...['--debug']); - const launchOptions: LaunchOptions = { cwd: options.cwd, args: testArgs, token: options.token, outChannel: options.outChannel, testProvider: 'unittest' }; - // tslint:disable-next-line:prefer-type-cast no-any + const debugLauncher = this.serviceContainer.get(ITestDebugLauncher); + testArgs.push('--debug'); + const launchOptions: LaunchOptions = { cwd: options.cwd, args: testArgs, token: options.token, outChannel: options.outChannel, testProvider: UNITTEST_PROVIDER }; return debugLauncher.launchDebugger(launchOptions); } else { - // tslint:disable-next-line:prefer-type-cast no-any const runOptions: Options = { args: [testLauncherFile].concat(testArgs), cwd: options.cwd, @@ -106,109 +104,57 @@ export async function runTest(serviceContainer: IServiceContainer, testManager: token: options.token, workspaceFolder: options.workspaceFolder }; - return run(serviceContainer, 'unittest', runOptions); + await this.testRunner.run(UNITTEST_PROVIDER, runOptions); } - } + }; - // Test everything + // Test everything. if (testPaths.length === 0) { - return runTestInternal(); + await runTestInternal(); } - // Ok, the ptvs test runner can only work with one test at a time - let promise = Promise.resolve(''); - if (Array.isArray(options.testsToRun!.testFile)) { - options.testsToRun!.testFile!.forEach(testFile => { - // tslint:disable-next-line:prefer-type-cast no-any - promise = promise.then(() => runTestInternal(testFile.fullPath, testFile.nameToRun) as Promise); - }); - } - if (Array.isArray(options.testsToRun!.testSuite)) { - options.testsToRun!.testSuite!.forEach(testSuite => { - const testFileName = options.tests.testSuites.find(t => t.testSuite === testSuite)!.parentTestFile.fullPath; - // tslint:disable-next-line:prefer-type-cast no-any - promise = promise.then(() => runTestInternal(testFileName, testSuite.nameToRun) as Promise); - }); - } - if (Array.isArray(options.testsToRun!.testFunction)) { - options.testsToRun!.testFunction!.forEach(testFn => { - const testFileName = options.tests.testFunctions.find(t => t.testFunction === testFn)!.parentTestFile.fullPath; - // tslint:disable-next-line:prefer-type-cast no-any - promise = promise.then(() => runTestInternal(testFileName, testFn.nameToRun) as Promise); - }); + // Ok, the test runner can only work with one test at a time. + if (options.testsToRun) { + let promise = Promise.resolve(undefined); + if (Array.isArray(options.testsToRun.testFile)) { + options.testsToRun.testFile.forEach(testFile => { + promise = promise.then(() => runTestInternal(testFile.fullPath, testFile.nameToRun)); + }); + } + if (Array.isArray(options.testsToRun.testSuite)) { + options.testsToRun.testSuite.forEach(testSuite => { + const testFileName = options.tests.testSuites.find(t => t.testSuite === testSuite)!.parentTestFile.fullPath; + promise = promise.then(() => runTestInternal(testFileName, testSuite.nameToRun)); + }); + } + if (Array.isArray(options.testsToRun.testFunction)) { + options.testsToRun.testFunction.forEach(testFn => { + const testFileName = options.tests.testFunctions.find(t => t.testFunction === testFn)!.parentTestFile.fullPath; + promise = promise.then(() => runTestInternal(testFileName, testFn.nameToRun)); + }); + } + await promise; } - // tslint:disable-next-line:prefer-type-cast no-any - return promise as Promise; - }).then(() => { + testResultsService.updateResults(options.tests); return options.tests; - }).catch(reason => { - return Promise.reject(reason); - }); -} -function getStartDirectory(args: string[]): string { - let startDirectory = '.'; - const indexOfStartDir = args.findIndex(arg => arg.indexOf('-s') === 0 || arg.indexOf('--start-directory') === 0); - if (indexOfStartDir >= 0) { - const startDir = args[indexOfStartDir].trim(); - if ((startDir.trim() === '-s' || startDir.trim() === '--start-directory') && args.length >= indexOfStartDir) { - // Assume the next items is the directory - startDirectory = args[indexOfStartDir + 1]; - } else { - const lenToStartFrom = startDir.startsWith('-s') ? '-s'.length : '--start-directory'.length; - startDirectory = startDir.substring(lenToStartFrom).trim(); - if (startDirectory.startsWith('=')) { - startDirectory = startDirectory.substring(1); - } - } } - return startDirectory; -} -function buildTestArgs(args: string[]): string[] { - const startTestDiscoveryDirectory = getStartDirectory(args); - let pattern = 'test*.py'; - const indexOfPattern = args.findIndex(arg => arg.indexOf('-p') === 0 || arg.indexOf('--pattern') === 0); - if (indexOfPattern >= 0) { - const patternValue = args[indexOfPattern].trim(); - if ((patternValue.trim() === '-p' || patternValue.trim() === '--pattern') && args.length >= indexOfPattern) { - // Assume the next items is the directory - pattern = args[indexOfPattern + 1]; - } else { - const lenToStartFrom = patternValue.startsWith('-p') ? '-p'.length : '--pattern'.length; - pattern = patternValue.substring(lenToStartFrom).trim(); - if (pattern.startsWith('=')) { - pattern = pattern.substring(1); - } + private buildTestArgs(args: string[]): string[] { + const startTestDiscoveryDirectory = this.helper.getStartDirectory(args); + let pattern = 'test*.py'; + const shortValue = this.argsHelper.getOptionValues(args, '-p'); + const longValueValue = this.argsHelper.getOptionValues(args, '-pattern'); + if (typeof shortValue === 'string') { + pattern = shortValue; + } else if (typeof longValueValue === 'string') { + pattern = longValueValue; } + const failFast = args.some(arg => arg.trim() === '-f' || arg.trim() === '--failfast'); + const verbosity = args.some(arg => arg.trim().indexOf('-v') === 0) ? 2 : 1; + const testArgs = [`--us=${startTestDiscoveryDirectory}`, `--up=${pattern}`, `--uvInt=${verbosity}`]; + if (failFast) { + testArgs.push('--uf'); + } + return testArgs; } - const failFast = args.some(arg => arg.trim() === '-f' || arg.trim() === '--failfast'); - const verbosity = args.some(arg => arg.trim().indexOf('-v') === 0) ? 2 : 1; - const testArgs = [`--us=${startTestDiscoveryDirectory}`, `--up=${pattern}`, `--uvInt=${verbosity}`]; - if (failFast) { - testArgs.push('--uf'); - } - return testArgs; -} -function getIdsOfTestsToRun(tests: Tests, testsToRun: TestsToRun): string[] { - const testIds: string[] = []; - if (testsToRun && testsToRun.testFolder) { - // Get test ids of files in these folders - testsToRun.testFolder.map(folder => { - tests.testFiles.forEach(f => { - if (f.fullPath.startsWith(folder.name)) { - testIds.push(f.nameToRun); - } - }); - }); - } - if (testsToRun && testsToRun.testFile) { - testIds.push(...testsToRun.testFile.map(f => f.nameToRun)); - } - if (testsToRun && testsToRun.testSuite) { - testIds.push(...testsToRun.testSuite.map(f => f.nameToRun)); - } - if (testsToRun && testsToRun.testFunction) { - testIds.push(...testsToRun.testFunction.map(f => f.nameToRun)); - } - return testIds; } diff --git a/src/client/unittests/unittest/services/argsService.ts b/src/client/unittests/unittest/services/argsService.ts new file mode 100644 index 000000000000..6a8cf7b1d625 --- /dev/null +++ b/src/client/unittests/unittest/services/argsService.ts @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { IServiceContainer } from '../../../ioc/types'; +import { IArgumentsHelper, IArgumentsService, TestFilter } from '../../types'; + +const OptionsWithArguments = ['-p', '-s', '-t', '--pattern', + '--start-directory', '--top-level-directory']; + +const OptionsWithoutArguments = ['-b', '-c', '-f', '-h', '-q', '-v', + '--buffer', '--catch', '--failfast', '--help', '--locals', + '--quiet', '--verbose']; + +@injectable() +export class ArgumentsService implements IArgumentsService { + private readonly helper: IArgumentsHelper; + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { + this.helper = serviceContainer.get(IArgumentsHelper); + } + public getKnownOptions(): { withArgs: string[]; withoutArgs: string[] } { + return { + withArgs: OptionsWithArguments, + withoutArgs: OptionsWithoutArguments + }; + } + public getOptionValue(args: string[], option: string): string | string[] | undefined { + return this.helper.getOptionValues(args, option); + } + public filterArguments(args: string[], argumentToRemoveOrFilter: string[] | TestFilter): string[] { + const optionsWithoutArgsToRemove: string[] = []; + const optionsWithArgsToRemove: string[] = []; + // Positional arguments in pytest positional args are test directories and files. + // So if we want to run a specific test, then remove positional args. + let removePositionalArgs = false; + if (Array.isArray(argumentToRemoveOrFilter)) { + argumentToRemoveOrFilter.forEach(item => { + if (OptionsWithArguments.indexOf(item) >= 0) { + optionsWithArgsToRemove.push(item); + } + if (OptionsWithoutArguments.indexOf(item) >= 0) { + optionsWithoutArgsToRemove.push(item); + } + }); + } else { + removePositionalArgs = true; + } + + let filteredArgs = args.slice(); + if (removePositionalArgs) { + const positionalArgs = this.helper.getPositionalArguments(filteredArgs, OptionsWithArguments, OptionsWithoutArguments); + filteredArgs = filteredArgs.filter(item => positionalArgs.indexOf(item) === -1); + } + return this.helper.filterArguments(filteredArgs, optionsWithArgsToRemove, optionsWithoutArgsToRemove); + } + public getTestFolders(args: string[]): string[] { + const shortValue = this.helper.getOptionValues(args, '-s'); + if (typeof shortValue === 'string') { + return [shortValue]; + } + const longValue = this.helper.getOptionValues(args, '--start-directory'); + if (typeof longValue === 'string') { + return [longValue]; + } + return ['.']; + } +} diff --git a/src/client/unittests/unittest/services/discoveryService.ts b/src/client/unittests/unittest/services/discoveryService.ts index cff7561c4420..fdf28cff3c93 100644 --- a/src/client/unittests/unittest/services/discoveryService.ts +++ b/src/client/unittests/unittest/services/discoveryService.ts @@ -4,8 +4,9 @@ import { inject, injectable, named } from 'inversify'; import { IServiceContainer } from '../../../ioc/types'; import { UNITTEST_PROVIDER } from '../../common/constants'; -import { Options, run } from '../../common/runner'; -import { ITestDiscoveryService, ITestsParser, TestDiscoveryOptions, Tests } from '../../common/types'; +import { Options } from '../../common/runner'; +import { ITestDiscoveryService, ITestRunner, ITestsParser, TestDiscoveryOptions, Tests } from '../../common/types'; +import { IArgumentsHelper } from '../../types'; type UnitTestDiscoveryOptions = TestDiscoveryOptions & { startDirectory: string; @@ -14,8 +15,13 @@ type UnitTestDiscoveryOptions = TestDiscoveryOptions & { @injectable() export class TestDiscoveryService implements ITestDiscoveryService { - constructor( @inject(IServiceContainer) private serviceContainer: IServiceContainer, - @inject(ITestsParser) @named(UNITTEST_PROVIDER) private testParser: ITestsParser) { } + private readonly argsHelper: IArgumentsHelper; + private readonly runner: ITestRunner; + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer, + @inject(ITestsParser) @named(UNITTEST_PROVIDER) private testParser: ITestsParser) { + this.argsHelper = serviceContainer.get(IArgumentsHelper); + this.runner = serviceContainer.get(ITestRunner); + } public async discoverTests(options: TestDiscoveryOptions): Promise { const pythonScript = this.getDiscoveryScript(options); const unitTestOptions = this.translateOptions(options); @@ -27,7 +33,7 @@ export class TestDiscoveryService implements ITestDiscoveryService { outChannel: options.outChannel }; - const data = await run(this.serviceContainer, UNITTEST_PROVIDER, runOptions); + const data = await this.runner.run(UNITTEST_PROVIDER, runOptions); if (options.token && options.token.isCancellationRequested) { return Promise.reject('cancelled'); @@ -51,43 +57,32 @@ for suite in suites._tests: pass`; } public translateOptions(options: TestDiscoveryOptions): UnitTestDiscoveryOptions { - const unitTestOptions = { ...options } as UnitTestDiscoveryOptions; - unitTestOptions.startDirectory = this.getStartDirectory(options); - unitTestOptions.pattern = this.getTestPattern(options); - return unitTestOptions; + return { + ...options, + startDirectory: this.getStartDirectory(options), + pattern: this.getTestPattern(options) + }; } private getStartDirectory(options: TestDiscoveryOptions) { - let startDirectory = '.'; - const indexOfStartDir = options.args.findIndex(arg => arg.indexOf('-s') === 0); - if (indexOfStartDir >= 0) { - const startDir = options.args[indexOfStartDir].trim(); - if (startDir.trim() === '-s' && options.args.length >= indexOfStartDir) { - // Assume the next items is the directory - startDirectory = options.args[indexOfStartDir + 1]; - } else { - startDirectory = startDir.substring(2).trim(); - if (startDirectory.startsWith('=') || startDirectory.startsWith(' ')) { - startDirectory = startDirectory.substring(1); - } - } + const shortValue = this.argsHelper.getOptionValues(options.args, '-s'); + if (typeof shortValue === 'string') { + return shortValue; } - return startDirectory; + const longValue = this.argsHelper.getOptionValues(options.args, '--start-directory'); + if (typeof longValue === 'string') { + return longValue; + } + return '.'; } private getTestPattern(options: TestDiscoveryOptions) { - let pattern = 'test*.py'; - const indexOfPattern = options.args.findIndex(arg => arg.indexOf('-p') === 0); - if (indexOfPattern >= 0) { - const patternValue = options.args[indexOfPattern].trim(); - if (patternValue.trim() === '-p' && options.args.length >= indexOfPattern) { - // Assume the next items is the directory - pattern = options.args[indexOfPattern + 1]; - } else { - pattern = patternValue.substring(2).trim(); - if (pattern.startsWith('=')) { - pattern = pattern.substring(1); - } - } + const shortValue = this.argsHelper.getOptionValues(options.args, '-p'); + if (typeof shortValue === 'string') { + return shortValue; + } + const longValue = this.argsHelper.getOptionValues(options.args, '--pattern'); + if (typeof longValue === 'string') { + return longValue; } - return pattern; + return 'test*.py'; } } diff --git a/src/client/unittests/unittest/services/parserService.ts b/src/client/unittests/unittest/services/parserService.ts index dd746be00694..1b4acdd194c9 100644 --- a/src/client/unittests/unittest/services/parserService.ts +++ b/src/client/unittests/unittest/services/parserService.ts @@ -3,7 +3,7 @@ import { inject, injectable } from 'inversify'; import * as path from 'path'; -import { ITestsHelper, ITestsParser, TestDiscoveryOptions, TestFile, TestFunction, Tests, TestStatus, TestSuite } from '../../common/types'; +import { ITestsHelper, ITestsParser, TestDiscoveryOptions, TestFile, TestFunction, Tests, TestStatus } from '../../common/types'; type UnitTestParserOptions = TestDiscoveryOptions & { startDirectory: string }; @@ -21,7 +21,7 @@ export class TestsParser implements ITestsParser { private getTestIds(content: string): string[] { let startedCollecting = false; return content.split(/\r?\n/g) - .map((line, index) => { + .map(line => { if (!startedCollecting) { if (line === 'start') { startedCollecting = true; @@ -34,13 +34,22 @@ export class TestsParser implements ITestsParser { } private parseTestIds(rootDirectory: string, testIds: string[]): Tests { const testFiles: TestFile[] = []; - testIds.forEach(testId => { - this.addTestId(rootDirectory, testId, testFiles); - }); + testIds.forEach(testId => this.addTestId(rootDirectory, testId, testFiles)); return this.testsHelper.flattenTestFiles(testFiles); } + /** + * Add the test Ids into the array provided. + * TestIds are fully qualified including the method names. + * E.g. tone_test.Failing2Tests.test_failure + * Where tone_test = folder, Failing2Tests = class/suite, test_failure = method. + * @private + * @param {string} rootDirectory + * @param {string[]} testIds + * @returns {Tests} + * @memberof TestsParser + */ private addTestId(rootDirectory: string, testId: string, testFiles: TestFile[]) { const testIdParts = testId.split('.'); // We must have a file, class and function name @@ -59,10 +68,8 @@ export class TestsParser implements ITestsParser { testFile = { name: path.basename(filePath), fullPath: filePath, - // tslint:disable-next-line:prefer-type-cast - functions: [] as TestFunction[], - // tslint:disable-next-line:prefer-type-cast - suites: [] as TestSuite[], + functions: [], + suites: [], nameToRun: `${className}.${functionName}`, xmlName: '', status: TestStatus.Idle, @@ -77,10 +84,8 @@ export class TestsParser implements ITestsParser { if (!testSuite) { testSuite = { name: className, - // tslint:disable-next-line:prefer-type-cast - functions: [] as TestFunction[], - // tslint:disable-next-line:prefer-type-cast - suites: [] as TestSuite[], + functions: [], + suites: [], isUnitTest: true, isInstance: false, nameToRun: `${path.parse(filePath).name}.${classNameToRun}`, diff --git a/src/test/common.ts b/src/test/common.ts index 755d1a41a6f2..d2645fd5e31c 100644 --- a/src/test/common.ts +++ b/src/test/common.ts @@ -2,6 +2,7 @@ import * as fs from 'fs-extra'; import * as path from 'path'; import { ConfigurationTarget, Uri, workspace } from 'vscode'; import { PythonSettings } from '../client/common/configSettings'; +import { EXTENSION_ROOT_DIR } from '../client/common/constants'; import { sleep } from './core'; import { IS_MULTI_ROOT_TEST } from './initialize'; @@ -9,7 +10,7 @@ export * from './core'; // tslint:disable:no-non-null-assertion no-unsafe-any await-promise no-any no-use-before-declare no-string-based-set-timeout no-unsafe-any no-any no-invalid-this -const fileInNonRootWorkspace = path.join(__dirname, '..', '..', 'src', 'test', 'pythonFiles', 'dummy.py'); +const fileInNonRootWorkspace = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'dummy.py'); export const rootWorkspaceUri = getWorkspaceRoot(); export const PYTHON_PATH = getPythonPath(); @@ -40,7 +41,7 @@ export async function updateSetting(setting: PythonSettingKeys, value: {} | unde function getWorkspaceRoot() { if (!Array.isArray(workspace.workspaceFolders) || workspace.workspaceFolders.length === 0) { - return Uri.file(path.join(__dirname, '..', '..', 'src', 'test')); + return Uri.file(path.join(EXTENSION_ROOT_DIR, 'src', 'test')); } if (workspace.workspaceFolders.length === 1) { return workspace.workspaceFolders[0].uri; diff --git a/src/test/common/platform/filesystem.test.ts b/src/test/common/platform/filesystem.unit.test.ts similarity index 96% rename from src/test/common/platform/filesystem.test.ts rename to src/test/common/platform/filesystem.unit.test.ts index b434bd5d9c96..eb29e10b22b9 100644 --- a/src/test/common/platform/filesystem.test.ts +++ b/src/test/common/platform/filesystem.unit.test.ts @@ -1,12 +1,15 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { expect } from 'chai'; +import { expect, use } from 'chai'; import * as fs from 'fs-extra'; import * as path from 'path'; import * as TypeMoq from 'typemoq'; import { FileSystem } from '../../../client/common/platform/fileSystem'; import { IFileSystem, IPlatformService } from '../../../client/common/platform/types'; +// tslint:disable-next-line:no-require-imports no-var-requires +const assertArrays = require('chai-arrays'); +use(assertArrays); // tslint:disable-next-line:max-func-body-length suite('FileSystem', () => { diff --git a/src/test/index.ts b/src/test/index.ts index 5a2ebe364582..58eb65ffd46c 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -4,9 +4,11 @@ if ((Reflect as any).metadata === undefined) { require('reflect-metadata'); } -import { IS_CI_SERVER, IS_CI_SERVER_TEST_DEBUGGER, - IS_MULTI_ROOT_TEST, IS_VSTS, MOCHA_CI_PROPERTIES, - MOCHA_CI_REPORTFILE, MOCHA_REPORTER_JUNIT } from './constants'; +import { + IS_CI_SERVER, IS_CI_SERVER_TEST_DEBUGGER, + IS_MULTI_ROOT_TEST, IS_VSTS, MOCHA_CI_PROPERTIES, + MOCHA_CI_REPORTFILE, MOCHA_REPORTER_JUNIT +} from './constants'; import * as testRunner from './testRunner'; process.env.VSC_PYTHON_CI_TEST = '1'; diff --git a/src/test/unittests/argsService.unit.test.ts b/src/test/unittests/argsService.unit.test.ts new file mode 100644 index 000000000000..e0ca1043c84e --- /dev/null +++ b/src/test/unittests/argsService.unit.test.ts @@ -0,0 +1,304 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:max-func-body-length no-any no-conditional-assignment no-increment-decrement no-invalid-this insecure-random +import { fail } from 'assert'; +import { expect } from 'chai'; +import { spawnSync } from 'child_process'; +import * as path from 'path'; +import * as typeMoq from 'typemoq'; +import { EnumEx } from '../../client/common/enumUtils'; +import { ILogger, Product } from '../../client/common/types'; +import { IServiceContainer } from '../../client/ioc/types'; +import { ArgumentsHelper } from '../../client/unittests/common/argumentsHelper'; +import { ArgumentsService as NoseTestArgumentsService } from '../../client/unittests/nosetest/services/argsService'; +import { ArgumentsService as PyTestArgumentsService } from '../../client/unittests/pytest/services/argsService'; +import { IArgumentsHelper, IArgumentsService } from '../../client/unittests/types'; +import { ArgumentsService as UnitTestArgumentsService } from '../../client/unittests/unittest/services/argsService'; +import { PYTHON_PATH } from '../common'; + +suite('Unit Tests - argsService', () => { + [Product.unittest, Product.nosetest, Product.pytest] + .forEach(product => { + const productNames = EnumEx.getNamesAndValues(Product); + const productName = productNames.find(item => item.value === product)!.name; + suite(productName, () => { + let argumentsService: IArgumentsService; + let moduleName = ''; + let expectedWithArgs: string[] = []; + let expectedWithoutArgs: string[] = []; + + suiteSetup(() => { + const serviceContainer = typeMoq.Mock.ofType(); + const logger = typeMoq.Mock.ofType(); + + serviceContainer + .setup(s => s.get(typeMoq.It.isValue(ILogger), typeMoq.It.isAny())) + .returns(() => logger.object); + + const argsHelper = new ArgumentsHelper(serviceContainer.object); + + serviceContainer + .setup(s => s.get(typeMoq.It.isValue(IArgumentsHelper), typeMoq.It.isAny())) + .returns(() => argsHelper); + + switch (product) { + case Product.unittest: { + argumentsService = new UnitTestArgumentsService(serviceContainer.object); + moduleName = 'unittest'; + break; + } + case Product.nosetest: { + argumentsService = new NoseTestArgumentsService(serviceContainer.object); + moduleName = 'nose'; + break; + } + case Product.pytest: { + moduleName = 'pytest'; + argumentsService = new PyTestArgumentsService(serviceContainer.object); + break; + } + default: { + throw new Error('Unrecognized Test Framework'); + } + } + + expectedWithArgs = getOptions(product, moduleName, true); + expectedWithoutArgs = getOptions(product, moduleName, false); + }); + + test('Check for new/unrecognized options with values', () => { + const options = argumentsService.getKnownOptions(); + const optionsNotFound = expectedWithArgs.filter(item => options.withArgs.indexOf(item) === -1); + + if (optionsNotFound.length > 0) { + fail('', optionsNotFound.join(', '), 'Options not found'); + } + }); + test('Check for new/unrecognized options without values', () => { + const options = argumentsService.getKnownOptions(); + const optionsNotFound = expectedWithoutArgs.filter(item => options.withoutArgs.indexOf(item) === -1); + + if (optionsNotFound.length > 0) { + fail('', optionsNotFound.join(', '), 'Options not found'); + } + }); + test('Test getting value for an option with a single value', () => { + for (const option of expectedWithArgs) { + const args = ['--some-option-with-a-value', '1234', '--another-value-with-inline=1234', option, 'abcd']; + const value = argumentsService.getOptionValue(args, option); + expect(value).to.equal('abcd'); + } + }); + test('Test getting value for an option with a multiple value', () => { + for (const option of expectedWithArgs) { + const args = ['--some-option-with-a-value', '1234', '--another-value-with-inline=1234', option, 'abcd', option, 'xyz']; + const value = argumentsService.getOptionValue(args, option); + expect(value).to.deep.equal(['abcd', 'xyz']); + } + }); + test('Test getting the test folder in unittest with -s', function () { + if (product !== Product.unittest) { + return this.skip(); + } + const dir = path.join('a', 'b', 'c'); + const args = ['anzy', '--one', '--three', '-s', dir]; + const testDirs = argumentsService.getTestFolders(args); + expect(testDirs).to.be.lengthOf(1); + expect(testDirs[0]).to.equal(dir); + }); + test('Test getting the test folder in unittest with -s in the middle', function () { + if (product !== Product.unittest) { + return this.skip(); + } + const dir = path.join('a', 'b', 'c'); + const args = ['anzy', '--one', '--three', '-s', dir, 'some other', '--value', '1234']; + const testDirs = argumentsService.getTestFolders(args); + expect(testDirs).to.be.lengthOf(1); + expect(testDirs[0]).to.equal(dir); + }); + test('Test getting the test folder in unittest with --start-directory', function () { + if (product !== Product.unittest) { + return this.skip(); + } + const dir = path.join('a', 'b', 'c'); + const args = ['anzy', '--one', '--three', '--start-directory', dir]; + const testDirs = argumentsService.getTestFolders(args); + expect(testDirs).to.be.lengthOf(1); + expect(testDirs[0]).to.equal(dir); + }); + test('Test getting the test folder in unittest with --start-directory in the middle', function () { + if (product !== Product.unittest) { + return this.skip(); + } + const dir = path.join('a', 'b', 'c'); + const args = ['anzy', '--one', '--three', '--start-directory', dir, 'some other', '--value', '1234']; + const testDirs = argumentsService.getTestFolders(args); + expect(testDirs).to.be.lengthOf(1); + expect(testDirs[0]).to.equal(dir); + }); + test('Test getting the test folder in nosetest', function () { + if (product !== Product.nosetest) { + return this.skip(); + } + const dir = path.join('a', 'b', 'c'); + const args = ['anzy', '--one', '--three', dir]; + const testDirs = argumentsService.getTestFolders(args); + expect(testDirs).to.be.lengthOf(1); + expect(testDirs[0]).to.equal(dir); + }); + test('Test getting the test folder in nosetest (with multiple dirs)', function () { + if (product !== Product.nosetest) { + return this.skip(); + } + const dir = path.join('a', 'b', 'c'); + const dir2 = path.join('a', 'b', '2'); + const args = ['anzy', '--one', '--three', dir, dir2]; + const testDirs = argumentsService.getTestFolders(args); + expect(testDirs).to.be.lengthOf(2); + expect(testDirs[0]).to.equal(dir); + expect(testDirs[1]).to.equal(dir2); + }); + test('Test getting the test folder in pytest', function () { + if (product !== Product.pytest) { + return this.skip(); + } + const dir = path.join('a', 'b', 'c'); + const args = ['anzy', '--one', '--rootdir', dir]; + const testDirs = argumentsService.getTestFolders(args); + expect(testDirs).to.be.lengthOf(1); + expect(testDirs[0]).to.equal(dir); + }); + test('Test getting the test folder in pytest (with multiple dirs)', function () { + if (product !== Product.pytest) { + return this.skip(); + } + const dir = path.join('a', 'b', 'c'); + const dir2 = path.join('a', 'b', '2'); + const args = ['anzy', '--one', '--rootdir', dir, '--rootdir', dir2]; + const testDirs = argumentsService.getTestFolders(args); + expect(testDirs).to.be.lengthOf(2); + expect(testDirs[0]).to.equal(dir); + expect(testDirs[1]).to.equal(dir2); + }); + test('Test getting the test folder in pytest (with multiple dirs in the middle)', function () { + if (product !== Product.pytest) { + return this.skip(); + } + const dir = path.join('a', 'b', 'c'); + const dir2 = path.join('a', 'b', '2'); + const args = ['anzy', '--one', '--rootdir', dir, '--rootdir', dir2, '-xyz']; + const testDirs = argumentsService.getTestFolders(args); + expect(testDirs).to.be.lengthOf(2); + expect(testDirs[0]).to.equal(dir); + expect(testDirs[1]).to.equal(dir2); + }); + test('Test getting the test folder in pytest (with single positional dir)', function () { + if (product !== Product.pytest) { + return this.skip(); + } + const dir = path.join('a', 'b', 'c'); + const args = ['anzy', '--one', dir]; + const testDirs = argumentsService.getTestFolders(args); + expect(testDirs).to.be.lengthOf(1); + expect(testDirs[0]).to.equal(dir); + }); + test('Test getting the test folder in pytest (with multiple positional dirs)', function () { + if (product !== Product.pytest) { + return this.skip(); + } + const dir = path.join('a', 'b', 'c'); + const dir2 = path.join('a', 'b', '2'); + const args = ['anzy', '--one', dir, dir2]; + const testDirs = argumentsService.getTestFolders(args); + expect(testDirs).to.be.lengthOf(2); + expect(testDirs[0]).to.equal(dir); + expect(testDirs[1]).to.equal(dir2); + }); + test('Test getting the test folder in pytest (with multiple dirs excluding python files)', function () { + if (product !== Product.pytest) { + return this.skip(); + } + const dir = path.join('a', 'b', 'c'); + const dir2 = path.join('a', 'b', '2'); + const args = ['anzy', '--one', dir, dir2, path.join(dir, 'one.py')]; + const testDirs = argumentsService.getTestFolders(args); + expect(testDirs).to.be.lengthOf(2); + expect(testDirs[0]).to.equal(dir); + expect(testDirs[1]).to.equal(dir2); + }); + test('Test filtering of arguments', () => { + const args: string[] = []; + const knownOptions = argumentsService.getKnownOptions(); + const argumentsToRemove: string[] = []; + const expectedFilteredArgs: string[] = []; + // Generate some random arguments. + for (let i = 0; i < 5; i += 1) { + args.push(knownOptions.withArgs[i], `Random Value ${i}`); + args.push(knownOptions.withoutArgs[i]); + + if (i % 2 === 0) { + argumentsToRemove.push(knownOptions.withArgs[i], knownOptions.withoutArgs[i]); + } else { + expectedFilteredArgs.push(knownOptions.withArgs[i], `Random Value ${i}`); + expectedFilteredArgs.push(knownOptions.withoutArgs[i]); + } + } + + const filteredArgs = argumentsService.filterArguments(args, argumentsToRemove); + expect(filteredArgs).to.be.deep.equal(expectedFilteredArgs); + }); + }); + }); +}); + +function getOptions(product: Product, moduleName: string, withValues: boolean) { + // const result = spawnSync('/Users/donjayamanne/Desktop/Development/PythonStuff/vscodePythonTesting/testingFolder/venv/bin/python', ['-m', moduleName, '-h']); + const result = spawnSync(PYTHON_PATH, ['-m', moduleName, '-h']); + const output = result.stdout.toString(); + + // Our regex isn't the best, so lets exclude stuff that shouldn't be captured. + const knownOptionsWithoutArgs: string[] = []; + const knownOptionsWithArgs: string[] = []; + if (product === Product.pytest) { + knownOptionsWithArgs.push(...['-c', '-p', '-r']); + } + + if (withValues) { + return getOptionsWithArguments(output) + .concat(...knownOptionsWithArgs) + .filter(item => knownOptionsWithoutArgs.indexOf(item) === -1) + .sort(); + } else { + return getOptionsWithoutArguments(output) + .concat(...knownOptionsWithoutArgs) + .filter(item => knownOptionsWithArgs.indexOf(item) === -1) + // In pytest, any option begining with --log- is known to have args. + .filter(item => product === Product.pytest ? !item.startsWith('--log-') : true) + .sort(); + } +} + +function getOptionsWithoutArguments(output: string) { + return getMatches('\\s{1,}(-{1,2}[A-Za-z0-9-]+)(?:,|\\s{2,})', output); +} +function getOptionsWithArguments(output: string) { + return getMatches('\\s{1,}(-{1,2}[A-Za-z0-9-]+)(?:=|\\s{0,1}[A-Z])', output); +} + +function getMatches(pattern, str) { + const matches: string[] = []; + const regex = new RegExp(pattern, 'gm'); + let result; + while ((result = regex.exec(str)) !== null) { + if (result.index === regex.lastIndex) { + regex.lastIndex++; + } + matches.push(result[1].trim()); + } + return matches + .sort() + .reduce((items, item) => items.indexOf(item) === -1 ? items.concat([item]) : items, []); +} diff --git a/src/test/unittests/common/argsHelper.unit.test.ts b/src/test/unittests/common/argsHelper.unit.test.ts new file mode 100644 index 000000000000..29a9ac3261d1 --- /dev/null +++ b/src/test/unittests/common/argsHelper.unit.test.ts @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:max-func-body-length no-any no-conditional-assignment no-increment-decrement no-invalid-this no-require-imports no-var-requires +import { expect, use } from 'chai'; +import * as typeMoq from 'typemoq'; +import { ILogger } from '../../../client/common/types'; +import { IServiceContainer } from '../../../client/ioc/types'; +import { ArgumentsHelper } from '../../../client/unittests/common/argumentsHelper'; +import { IArgumentsHelper } from '../../../client/unittests/types'; +const assertArrays = require('chai-arrays'); +use(assertArrays); + +suite('Unit Tests - Arguments Helper', () => { + let argsHelper: IArgumentsHelper; + setup(() => { + const serviceContainer = typeMoq.Mock.ofType(); + const logger = typeMoq.Mock.ofType(); + + serviceContainer + .setup(s => s.get(typeMoq.It.isValue(ILogger), typeMoq.It.isAny())) + .returns(() => logger.object); + + argsHelper = new ArgumentsHelper(serviceContainer.object); + }); + + test('Get Option Value', () => { + const args = ['-abc', '1234', 'zys', '--root', 'value']; + const value = argsHelper.getOptionValues(args, '--root'); + expect(value).to.not.be.array(); + expect(value).to.be.deep.equal('value'); + }); + test('Get Option Value when using =', () => { + const args = ['-abc', '1234', 'zys', '--root=value']; + const value = argsHelper.getOptionValues(args, '--root'); + expect(value).to.not.be.array(); + expect(value).to.be.deep.equal('value'); + }); + test('Get Option Values', () => { + const args = ['-abc', '1234', 'zys', '--root', 'value1', '--root', 'value2']; + const values = argsHelper.getOptionValues(args, '--root'); + expect(values).to.be.array(); + expect(values).to.be.lengthOf(2); + expect(values).to.be.deep.equal(['value1', 'value2']); + }); + test('Get Option Values when using =', () => { + const args = ['-abc', '1234', 'zys', '--root=value1', '--root=value2']; + const values = argsHelper.getOptionValues(args, '--root'); + expect(values).to.be.array(); + expect(values).to.be.lengthOf(2); + expect(values).to.be.deep.equal(['value1', 'value2']); + }); + test('Get Positional options', () => { + const args = ['-abc', '1234', '--value-option', 'value1', '--no-value-option', 'value2']; + const values = argsHelper.getPositionalArguments(args, ['--value-option', '-abc'], ['--no-value-option']); + expect(values).to.be.array(); + expect(values).to.be.lengthOf(1); + expect(values).to.be.deep.equal(['value2']); + }); + test('Get multiple Positional options', () => { + const args = ['-abc', '1234', '--value-option', 'value1', '--no-value-option', 'value2', 'value3']; + const values = argsHelper.getPositionalArguments(args, ['--value-option', '-abc'], ['--no-value-option']); + expect(values).to.be.array(); + expect(values).to.be.lengthOf(2); + expect(values).to.be.deep.equal(['value2', 'value3']); + }); + test('Get multiple Positional options and ineline values', () => { + const args = ['-abc=1234', '--value-option=value1', '--no-value-option', 'value2', 'value3']; + const values = argsHelper.getPositionalArguments(args, ['--value-option', '-abc'], ['--no-value-option']); + expect(values).to.be.array(); + expect(values).to.be.lengthOf(2); + expect(values).to.be.deep.equal(['value2', 'value3']); + }); + test('Get Positional options with trailing value option', () => { + const args = ['-abc', '1234', '--value-option', 'value1', '--value-option', 'value2', 'value3']; + const values = argsHelper.getPositionalArguments(args, ['--value-option', '-abc'], ['--no-value-option']); + expect(values).to.be.array(); + expect(values).to.be.lengthOf(1); + expect(values).to.be.deep.equal(['value3']); + }); + test('Get multiplle Positional options with trailing value option', () => { + const args = ['-abc', '1234', '--value-option', 'value1', '--value-option', 'value2', 'value3', '4']; + const values = argsHelper.getPositionalArguments(args, ['--value-option', '-abc'], ['--no-value-option']); + expect(values).to.be.array(); + expect(values).to.be.lengthOf(2); + expect(values).to.be.deep.equal(['value3', '4']); + }); + test('Filter to remove those with values', () => { + const args = ['-abc', '1234', '--value-option', 'value1', '--value-option', 'value2', 'value3', '4']; + const values = argsHelper.filterArguments(args, ['--value-option']); + expect(values).to.be.array(); + expect(values).to.be.lengthOf(4); + expect(values).to.be.deep.equal(['-abc', '1234', 'value3', '4']); + }); + test('Filter to remove those without values', () => { + const args = ['-abc', '1234', '--value-option', 'value1', '--no-value-option', 'value2', 'value3', '4']; + const values = argsHelper.filterArguments(args, [], ['--no-value-option']); + expect(values).to.be.array(); + expect(values).to.be.lengthOf(7); + expect(values).to.be.deep.equal(['-abc', '1234', '--value-option', 'value1', 'value2', 'value3', '4']); + }); + test('Filter to remove those with and without values', () => { + const args = ['-abc', '1234', '--value-option', 'value1', '--value-option', 'value2', 'value3', '4']; + const values = argsHelper.filterArguments(args, ['--value-option'], ['-abc']); + expect(values).to.be.array(); + expect(values).to.be.lengthOf(3); + expect(values).to.be.deep.equal(['1234', 'value3', '4']); + }); +}); diff --git a/src/test/unittests/debugger.test.ts b/src/test/unittests/debugger.test.ts index c526961527a9..d687de8b6755 100644 --- a/src/test/unittests/debugger.test.ts +++ b/src/test/unittests/debugger.test.ts @@ -3,8 +3,19 @@ import * as chaiAsPromised from 'chai-as-promised'; import * as path from 'path'; import { ConfigurationTarget } from 'vscode'; import { createDeferred } from '../../client/common/helpers'; -import { CANCELLATION_REASON, CommandSource } from '../../client/unittests/common/constants'; -import { ITestDebugLauncher, ITestManagerFactory, TestProvider } from '../../client/unittests/common/types'; +import { TestManagerRunner as NoseTestManagerRunner } from '../../client/unittests//nosetest/runner'; +import { TestManagerRunner as PytestManagerRunner } from '../../client/unittests//pytest/runner'; +import { TestManagerRunner as UnitTestTestManagerRunner } from '../../client/unittests//unittest/runner'; +import { ArgumentsHelper } from '../../client/unittests/common/argumentsHelper'; +import { CANCELLATION_REASON, CommandSource, NOSETEST_PROVIDER, PYTEST_PROVIDER, UNITTEST_PROVIDER } from '../../client/unittests/common/constants'; +import { TestRunner } from '../../client/unittests/common/runner'; +import { ITestDebugLauncher, ITestManagerFactory, ITestRunner, IXUnitParser, TestProvider } from '../../client/unittests/common/types'; +import { XUnitParser } from '../../client/unittests/common/xUnitParser'; +import { ArgumentsService as NoseTestArgumentsService } from '../../client/unittests/nosetest/services/argsService'; +import { ArgumentsService as PyTestArgumentsService } from '../../client/unittests/pytest/services/argsService'; +import { IArgumentsHelper, IArgumentsService, ITestManagerRunner, IUnitTestHelper } from '../../client/unittests/types'; +import { UnitTestHelper } from '../../client/unittests/unittest/helper'; +import { ArgumentsService as UnitTestArgumentsService } from '../../client/unittests/unittest/services/argsService'; import { deleteDirectory, rootWorkspaceUri, updateSetting } from '../common'; import { initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../initialize'; import { MockDebugLauncher } from './mocks'; @@ -60,6 +71,16 @@ suite('Unit Tests - debugging', () => { ioc.registerTestsHelper(); ioc.registerTestManagers(); ioc.registerMockUnitTestSocketServer(); + ioc.serviceManager.add(IArgumentsHelper, ArgumentsHelper); + ioc.serviceManager.add(ITestRunner, TestRunner); + ioc.serviceManager.add(IXUnitParser, XUnitParser); + ioc.serviceManager.add(IUnitTestHelper, UnitTestHelper); + ioc.serviceManager.add(IArgumentsService, NoseTestArgumentsService, NOSETEST_PROVIDER); + ioc.serviceManager.add(IArgumentsService, PyTestArgumentsService, PYTEST_PROVIDER); + ioc.serviceManager.add(IArgumentsService, UnitTestArgumentsService, UNITTEST_PROVIDER); + ioc.serviceManager.add(ITestManagerRunner, PytestManagerRunner, PYTEST_PROVIDER); + ioc.serviceManager.add(ITestManagerRunner, NoseTestManagerRunner, NOSETEST_PROVIDER); + ioc.serviceManager.add(ITestManagerRunner, UnitTestTestManagerRunner, UNITTEST_PROVIDER); ioc.serviceManager.addSingleton(ITestDebugLauncher, MockDebugLauncher); } @@ -77,8 +98,8 @@ suite('Unit Tests - debugging', () => { // This promise should never resolve nor reject. runningPromise - .then(() => deferred.reject('Debugger stopped when it shouldn\'t have')) - .catch(error => deferred.reject(error)); + .then(() => deferred.reject('Debugger stopped when it shouldn\'t have')) + .catch(error => deferred.reject(error)); mockDebugLauncher.launched .then((launched) => { @@ -87,14 +108,14 @@ suite('Unit Tests - debugging', () => { } else { deferred.reject('Debugger not launched'); } - }) .catch(error => deferred.reject(error)); + }).catch(error => deferred.reject(error)); await deferred.promise; } test('Debugger should start (unittest)', async () => { await updateSetting('unitTest.unittestArgs', ['-s=./tests', '-p=test_*.py'], rootWorkspaceUri, configTarget); - await testStartingDebugger('unittest'); + await testStartingDebugger('unittest'); }); test('Debugger should start (pytest)', async () => { diff --git a/src/test/unittests/nosetest/nosetest.discovery.unit.test.ts b/src/test/unittests/nosetest/nosetest.discovery.unit.test.ts new file mode 100644 index 000000000000..4ea9b9d54556 --- /dev/null +++ b/src/test/unittests/nosetest/nosetest.discovery.unit.test.ts @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable-next-line:max-func-body-length + +import { expect, use } from 'chai'; +import * as chaipromise from 'chai-as-promised'; +import * as typeMoq from 'typemoq'; +import { CancellationToken } from 'vscode'; +import { IServiceContainer } from '../../../client/ioc/types'; +import { NOSETEST_PROVIDER } from '../../../client/unittests/common/constants'; +import { ITestDiscoveryService, ITestRunner, ITestsParser, Options, TestDiscoveryOptions, Tests } from '../../../client/unittests/common/types'; +import { TestDiscoveryService } from '../../../client/unittests/nosetest/services/discoveryService'; +import { IArgumentsService, TestFilter } from '../../../client/unittests/types'; + +use(chaipromise); + +suite('Unit Tests - nose - Discovery', () => { + let discoveryService: ITestDiscoveryService; + let argsService: typeMoq.IMock; + let testParser: typeMoq.IMock; + let runner: typeMoq.IMock; + setup(() => { + const serviceContainer = typeMoq.Mock.ofType(); + argsService = typeMoq.Mock.ofType(); + testParser = typeMoq.Mock.ofType(); + runner = typeMoq.Mock.ofType(); + + serviceContainer.setup(s => s.get(typeMoq.It.isValue(IArgumentsService), typeMoq.It.isAny())) + .returns(() => argsService.object); + serviceContainer.setup(s => s.get(typeMoq.It.isValue(ITestRunner), typeMoq.It.isAny())) + .returns(() => runner.object); + + discoveryService = new TestDiscoveryService(serviceContainer.object, testParser.object); + }); + test('Ensure discovery is invoked with the right args', async () => { + const args: string[] = []; + const runOutput = 'xyz'; + const tests: Tests = { + summary: { errors: 1, failures: 0, passed: 0, skipped: 0 }, + testFiles: [], testFunctions: [], testSuites: [], + rootTestFolders: [], testFolders: [] + }; + argsService.setup(a => a.filterArguments(typeMoq.It.isValue(args), typeMoq.It.isValue(TestFilter.discovery))) + .returns(() => []) + .verifiable(typeMoq.Times.once()); + runner.setup(r => r.run(typeMoq.It.isValue(NOSETEST_PROVIDER), typeMoq.It.isAny())) + .callback((_, opts: Options) => { + expect(opts.args).to.include('--collect-only'); + expect(opts.args).to.include('-vvv'); + }) + .returns(() => Promise.resolve(runOutput)) + .verifiable(typeMoq.Times.once()); + testParser.setup(t => t.parse(typeMoq.It.isValue(runOutput), typeMoq.It.isAny())) + .returns(() => tests) + .verifiable(typeMoq.Times.once()); + + const options = typeMoq.Mock.ofType(); + const token = typeMoq.Mock.ofType(); + options.setup(o => o.args).returns(() => args); + options.setup(o => o.token).returns(() => token.object); + token.setup(t => t.isCancellationRequested) + .returns(() => false); + + const result = await discoveryService.discoverTests(options.object); + + expect(result).to.be.equal(tests); + argsService.verifyAll(); + runner.verifyAll(); + testParser.verifyAll(); + }); + test('Ensure discovery is cancelled', async () => { + const args: string[] = []; + const runOutput = 'xyz'; + const tests: Tests = { + summary: { errors: 1, failures: 0, passed: 0, skipped: 0 }, + testFiles: [], testFunctions: [], testSuites: [], + rootTestFolders: [], testFolders: [] + }; + argsService.setup(a => a.filterArguments(typeMoq.It.isValue(args), typeMoq.It.isValue(TestFilter.discovery))) + .returns(() => []) + .verifiable(typeMoq.Times.once()); + runner.setup(r => r.run(typeMoq.It.isValue(NOSETEST_PROVIDER), typeMoq.It.isAny())) + .callback((_, opts: Options) => { + expect(opts.args).to.include('--collect-only'); + expect(opts.args).to.include('-vvv'); + }) + .returns(() => Promise.resolve(runOutput)) + .verifiable(typeMoq.Times.once()); + testParser.setup(t => t.parse(typeMoq.It.isAny(), typeMoq.It.isAny())) + .returns(() => tests) + .verifiable(typeMoq.Times.never()); + + const options = typeMoq.Mock.ofType(); + const token = typeMoq.Mock.ofType(); + token.setup(t => t.isCancellationRequested) + .returns(() => true) + .verifiable(typeMoq.Times.once()); + + options.setup(o => o.args).returns(() => args); + options.setup(o => o.token).returns(() => token.object); + const promise = discoveryService.discoverTests(options.object); + + await expect(promise).to.eventually.be.rejectedWith('cancelled'); + argsService.verifyAll(); + runner.verifyAll(); + testParser.verifyAll(); + }); +}); diff --git a/src/test/unittests/nosetest.disovery.test.ts b/src/test/unittests/nosetest/nosetest.disovery.test.ts similarity index 88% rename from src/test/unittests/nosetest.disovery.test.ts rename to src/test/unittests/nosetest/nosetest.disovery.test.ts index 51001b1ab568..5bda99d7c657 100644 --- a/src/test/unittests/nosetest.disovery.test.ts +++ b/src/test/unittests/nosetest/nosetest.disovery.test.ts @@ -5,18 +5,19 @@ import * as assert from 'assert'; import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; -import { IProcessServiceFactory } from '../../client/common/process/types'; -import { CommandSource } from '../../client/unittests/common/constants'; -import { ITestManagerFactory } from '../../client/unittests/common/types'; -import { rootWorkspaceUri, updateSetting } from '../common'; -import { MockProcessService } from '../mocks/proc'; -import { initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../initialize'; -import { lookForTestFile } from './helper'; -import { UnitTestIocContainer } from './serviceRegistry'; +import { EXTENSION_ROOT_DIR } from '../../../client/common/constants'; +import { IProcessServiceFactory } from '../../../client/common/process/types'; +import { CommandSource } from '../../../client/unittests/common/constants'; +import { ITestManagerFactory } from '../../../client/unittests/common/types'; +import { rootWorkspaceUri, updateSetting } from '../../common'; +import { MockProcessService } from '../../mocks/proc'; +import { lookForTestFile } from '../helper'; +import { UnitTestIocContainer } from '../serviceRegistry'; +import { initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../../initialize'; -const PYTHON_FILES_PATH = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles'); -const UNITTEST_TEST_FILES_PATH = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles', 'noseFiles'); -const UNITTEST_SINGLE_TEST_FILE_PATH = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles', 'single'); +const PYTHON_FILES_PATH = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles'); +const UNITTEST_TEST_FILES_PATH = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'testFiles', 'noseFiles'); +const UNITTEST_SINGLE_TEST_FILE_PATH = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'testFiles', 'single'); const filesToDelete = [ path.join(UNITTEST_TEST_FILES_PATH, '.noseids'), path.join(UNITTEST_SINGLE_TEST_FILE_PATH, '.noseids') diff --git a/src/test/unittests/nosetest.run.test.ts b/src/test/unittests/nosetest/nosetest.run.test.ts similarity index 90% rename from src/test/unittests/nosetest.run.test.ts rename to src/test/unittests/nosetest/nosetest.run.test.ts index 37d26716a31e..88e2adc63b8e 100644 --- a/src/test/unittests/nosetest.run.test.ts +++ b/src/test/unittests/nosetest/nosetest.run.test.ts @@ -5,16 +5,17 @@ import * as assert from 'assert'; import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; -import { IProcessServiceFactory } from '../../client/common/process/types'; -import { CommandSource } from '../../client/unittests/common/constants'; -import { ITestManagerFactory, TestsToRun } from '../../client/unittests/common/types'; -import { rootWorkspaceUri, updateSetting } from '../common'; -import { MockProcessService } from '../mocks/proc'; -import { initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../initialize'; -import { UnitTestIocContainer } from './serviceRegistry'; +import { EXTENSION_ROOT_DIR } from '../../../client/common/constants'; +import { IProcessServiceFactory } from '../../../client/common/process/types'; +import { CommandSource } from '../../../client/unittests/common/constants'; +import { ITestManagerFactory, TestsToRun } from '../../../client/unittests/common/types'; +import { rootWorkspaceUri, updateSetting } from '../../common'; +import { MockProcessService } from '../../mocks/proc'; +import { UnitTestIocContainer } from '../serviceRegistry'; +import { initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../../initialize'; -const UNITTEST_TEST_FILES_PATH = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles', 'noseFiles'); -const UNITTEST_SINGLE_TEST_FILE_PATH = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles', 'single'); +const UNITTEST_TEST_FILES_PATH = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'testFiles', 'noseFiles'); +const UNITTEST_SINGLE_TEST_FILE_PATH = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'testFiles', 'single'); const filesToDelete = [ path.join(UNITTEST_TEST_FILES_PATH, '.noseids'), path.join(UNITTEST_SINGLE_TEST_FILE_PATH, '.noseids') @@ -65,7 +66,7 @@ suite('Unit Tests - nose - run against actual python process', () => { procService.onExecObservable((file, args, options, callback) => { if (args.indexOf('--collect-only') >= 0) { callback({ - out: fs.readFileSync(path.join(UNITTEST_TEST_FILES_PATH, outputFileName), 'utf8'), + out: fs.readFileSync(path.join(UNITTEST_TEST_FILES_PATH, outputFileName), 'utf8').replace(/\/Users\/donjayamanne\/.vscode\/extensions\/pythonVSCode\/src\/test\/pythonFiles\/testFiles\/noseFiles/g, UNITTEST_TEST_FILES_PATH), source: 'stdout' }); } diff --git a/src/test/unittests/nosetest.test.ts b/src/test/unittests/nosetest/nosetest.test.ts similarity index 77% rename from src/test/unittests/nosetest.test.ts rename to src/test/unittests/nosetest/nosetest.test.ts index 4ef31a0b1266..2f3581a0b682 100644 --- a/src/test/unittests/nosetest.test.ts +++ b/src/test/unittests/nosetest/nosetest.test.ts @@ -2,15 +2,16 @@ import * as assert from 'assert'; import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; -import { CommandSource } from '../../client/unittests/common/constants'; -import { ITestManagerFactory } from '../../client/unittests/common/types'; -import { rootWorkspaceUri, updateSetting } from '../common'; -import { initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../initialize'; -import { lookForTestFile } from './helper'; -import { UnitTestIocContainer } from './serviceRegistry'; +import { EXTENSION_ROOT_DIR } from '../../../client/common/constants'; +import { CommandSource } from '../../../client/unittests/common/constants'; +import { ITestManagerFactory } from '../../../client/unittests/common/types'; +import { rootWorkspaceUri, updateSetting } from '../../common'; +import { lookForTestFile } from '../helper'; +import { UnitTestIocContainer } from '../serviceRegistry'; +import { initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../../initialize'; -const UNITTEST_TEST_FILES_PATH = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles', 'noseFiles'); -const UNITTEST_SINGLE_TEST_FILE_PATH = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles', 'single'); +const UNITTEST_TEST_FILES_PATH = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'testFiles', 'noseFiles'); +const UNITTEST_SINGLE_TEST_FILE_PATH = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'testFiles', 'single'); const filesToDelete = [ path.join(UNITTEST_TEST_FILES_PATH, '.noseids'), path.join(UNITTEST_SINGLE_TEST_FILE_PATH, '.noseids') diff --git a/src/test/unittests/pytest.discovery.test.ts b/src/test/unittests/pytest/pytest.discovery.test.ts similarity index 92% rename from src/test/unittests/pytest.discovery.test.ts rename to src/test/unittests/pytest/pytest.discovery.test.ts index bdd6002ab81c..d2e0ef7ca93b 100644 --- a/src/test/unittests/pytest.discovery.test.ts +++ b/src/test/unittests/pytest/pytest.discovery.test.ts @@ -4,18 +4,19 @@ import * as assert from 'assert'; import * as path from 'path'; import * as vscode from 'vscode'; -import { IProcessServiceFactory } from '../../client/common/process/types'; -import { CommandSource } from '../../client/unittests/common/constants'; -import { ITestManagerFactory } from '../../client/unittests/common/types'; -import { rootWorkspaceUri, updateSetting } from '../common'; -import { MockProcessService } from '../mocks/proc'; -import { initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../initialize'; -import { UnitTestIocContainer } from './serviceRegistry'; +import { EXTENSION_ROOT_DIR } from '../../../client/common/constants'; +import { IProcessServiceFactory } from '../../../client/common/process/types'; +import { CommandSource } from '../../../client/unittests/common/constants'; +import { ITestManagerFactory } from '../../../client/unittests/common/types'; +import { rootWorkspaceUri, updateSetting } from '../../common'; +import { MockProcessService } from '../../mocks/proc'; +import { UnitTestIocContainer } from '../serviceRegistry'; +import { initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../../initialize'; -const UNITTEST_TEST_FILES_PATH = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles', 'standard'); -const UNITTEST_SINGLE_TEST_FILE_PATH = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles', 'single'); -const UNITTEST_TEST_FILES_PATH_WITH_CONFIGS = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles', 'unitestsWithConfigs'); -const unitTestTestFilesCwdPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles', 'cwd', 'src'); +const UNITTEST_TEST_FILES_PATH = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'testFiles', 'standard'); +const UNITTEST_SINGLE_TEST_FILE_PATH = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'testFiles', 'single'); +const UNITTEST_TEST_FILES_PATH_WITH_CONFIGS = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'testFiles', 'unitestsWithConfigs'); +const unitTestTestFilesCwdPath = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'testFiles', 'cwd', 'src'); // tslint:disable-next-line:max-func-body-length suite('Unit Tests - pytest - discovery with mocked process output', () => { diff --git a/src/test/unittests/pytest/pytest.discovery.unit.test.ts b/src/test/unittests/pytest/pytest.discovery.unit.test.ts new file mode 100644 index 000000000000..a7255308739f --- /dev/null +++ b/src/test/unittests/pytest/pytest.discovery.unit.test.ts @@ -0,0 +1,180 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:max-func-body-length + +import { expect, use } from 'chai'; +import * as chaipromise from 'chai-as-promised'; +import * as path from 'path'; +import * as typeMoq from 'typemoq'; +import { CancellationToken } from 'vscode'; +import { IServiceContainer } from '../../../client/ioc/types'; +import { PYTEST_PROVIDER } from '../../../client/unittests/common/constants'; +import { ITestDiscoveryService, ITestRunner, ITestsHelper, ITestsParser, Options, TestDiscoveryOptions, Tests } from '../../../client/unittests/common/types'; +import { TestDiscoveryService } from '../../../client/unittests/pytest/services/discoveryService'; +import { IArgumentsService, TestFilter } from '../../../client/unittests/types'; + +use(chaipromise); + +suite('Unit Tests - PyTest - Discovery', () => { + let discoveryService: ITestDiscoveryService; + let argsService: typeMoq.IMock; + let testParser: typeMoq.IMock; + let runner: typeMoq.IMock; + let helper: typeMoq.IMock; + setup(() => { + const serviceContainer = typeMoq.Mock.ofType(); + argsService = typeMoq.Mock.ofType(); + testParser = typeMoq.Mock.ofType(); + runner = typeMoq.Mock.ofType(); + helper = typeMoq.Mock.ofType(); + + serviceContainer.setup(s => s.get(typeMoq.It.isValue(IArgumentsService), typeMoq.It.isAny())) + .returns(() => argsService.object); + serviceContainer.setup(s => s.get(typeMoq.It.isValue(ITestRunner), typeMoq.It.isAny())) + .returns(() => runner.object); + serviceContainer.setup(s => s.get(typeMoq.It.isValue(ITestsHelper), typeMoq.It.isAny())) + .returns(() => helper.object); + + discoveryService = new TestDiscoveryService(serviceContainer.object, testParser.object); + }); + test('Ensure discovery is invoked with the right args and single dir', async () => { + const args: string[] = []; + const runOutput = 'xyz'; + const dir = path.join('a', 'b', 'c'); + const tests: Tests = { + summary: { errors: 1, failures: 0, passed: 0, skipped: 0 }, + testFiles: [], testFunctions: [], testSuites: [], + rootTestFolders: [], testFolders: [] + }; + argsService.setup(a => a.filterArguments(typeMoq.It.isValue(args), typeMoq.It.isValue(TestFilter.discovery))) + .returns(() => []) + .verifiable(typeMoq.Times.once()); + argsService.setup(a => a.getTestFolders(typeMoq.It.isValue(args))) + .returns(() => [dir]) + .verifiable(typeMoq.Times.once()); + helper.setup(a => a.mergeTests(typeMoq.It.isAny())) + .returns(() => tests) + .verifiable(typeMoq.Times.once()); + runner.setup(r => r.run(typeMoq.It.isValue(PYTEST_PROVIDER), typeMoq.It.isAny())) + .callback((_, opts: Options) => { + expect(opts.args).to.include('--cache-clear'); + expect(opts.args).to.include('-s'); + expect(opts.args).to.include('--collect-only'); + expect(opts.args[opts.args.length - 1]).to.equal(dir); + }) + .returns(() => Promise.resolve(runOutput)) + .verifiable(typeMoq.Times.once()); + testParser.setup(t => t.parse(typeMoq.It.isValue(runOutput), typeMoq.It.isAny())) + .returns(() => tests) + .verifiable(typeMoq.Times.once()); + + const options = typeMoq.Mock.ofType(); + const token = typeMoq.Mock.ofType(); + options.setup(o => o.args).returns(() => args); + options.setup(o => o.token).returns(() => token.object); + token.setup(t => t.isCancellationRequested) + .returns(() => false); + + const result = await discoveryService.discoverTests(options.object); + + expect(result).to.be.equal(tests); + argsService.verifyAll(); + runner.verifyAll(); + testParser.verifyAll(); + helper.verifyAll(); + }); + test('Ensure discovery is invoked with the right args and multiple dirs', async () => { + const args: string[] = []; + const runOutput = 'xyz'; + const dirs = [path.join('a', 'b', '1'), path.join('a', 'b', '2')]; + const tests: Tests = { + summary: { errors: 1, failures: 0, passed: 0, skipped: 0 }, + testFiles: [], testFunctions: [], testSuites: [], + rootTestFolders: [], testFolders: [] + }; + argsService.setup(a => a.filterArguments(typeMoq.It.isValue(args), typeMoq.It.isValue(TestFilter.discovery))) + .returns(() => []) + .verifiable(typeMoq.Times.once()); + argsService.setup(a => a.getTestFolders(typeMoq.It.isValue(args))) + .returns(() => dirs) + .verifiable(typeMoq.Times.once()); + helper.setup(a => a.mergeTests(typeMoq.It.isAny())) + .returns(() => tests) + .verifiable(typeMoq.Times.once()); + runner.setup(r => r.run(typeMoq.It.isValue(PYTEST_PROVIDER), typeMoq.It.isAny())) + .callback((_, opts: Options) => { + expect(opts.args).to.include('--cache-clear'); + expect(opts.args).to.include('-s'); + expect(opts.args).to.include('--collect-only'); + const dir = opts.args[opts.args.length - 1]; + expect(dirs).to.include(dir); + dirs.splice(dirs.indexOf(dir) - 1, 1); + }) + .returns(() => Promise.resolve(runOutput)) + .verifiable(typeMoq.Times.once()); + testParser.setup(t => t.parse(typeMoq.It.isValue(runOutput), typeMoq.It.isAny())) + .returns(() => tests) + .verifiable(typeMoq.Times.once()); + + const options = typeMoq.Mock.ofType(); + const token = typeMoq.Mock.ofType(); + options.setup(o => o.args).returns(() => args); + options.setup(o => o.token).returns(() => token.object); + token.setup(t => t.isCancellationRequested) + .returns(() => false); + + const result = await discoveryService.discoverTests(options.object); + + expect(result).to.be.equal(tests); + argsService.verifyAll(); + runner.verifyAll(); + testParser.verifyAll(); + helper.verifyAll(); + }); + test('Ensure discovery is cancelled', async () => { + const args: string[] = []; + const runOutput = 'xyz'; + const tests: Tests = { + summary: { errors: 1, failures: 0, passed: 0, skipped: 0 }, + testFiles: [], testFunctions: [], testSuites: [], + rootTestFolders: [], testFolders: [] + }; + argsService.setup(a => a.filterArguments(typeMoq.It.isValue(args), typeMoq.It.isValue(TestFilter.discovery))) + .returns(() => []) + .verifiable(typeMoq.Times.once()); + argsService.setup(a => a.getTestFolders(typeMoq.It.isValue(args))) + .returns(() => ['']) + .verifiable(typeMoq.Times.once()); + runner.setup(r => r.run(typeMoq.It.isValue(PYTEST_PROVIDER), typeMoq.It.isAny())) + .callback((_, opts: Options) => { + expect(opts.args).to.include('--cache-clear'); + expect(opts.args).to.include('-s'); + expect(opts.args).to.include('--collect-only'); + }) + .returns(() => Promise.resolve(runOutput)) + .verifiable(typeMoq.Times.once()); + testParser.setup(t => t.parse(typeMoq.It.isAny(), typeMoq.It.isAny())) + .returns(() => tests) + .verifiable(typeMoq.Times.never()); + helper.setup(a => a.mergeTests(typeMoq.It.isAny())) + .returns(() => tests); + + const options = typeMoq.Mock.ofType(); + const token = typeMoq.Mock.ofType(); + token.setup(t => t.isCancellationRequested) + .returns(() => true) + .verifiable(typeMoq.Times.once()); + + options.setup(o => o.args).returns(() => args); + options.setup(o => o.token).returns(() => token.object); + const promise = discoveryService.discoverTests(options.object); + + await expect(promise).to.eventually.be.rejectedWith('cancelled'); + argsService.verifyAll(); + runner.verifyAll(); + testParser.verifyAll(); + }); +}); diff --git a/src/test/unittests/pytest.run.test.ts b/src/test/unittests/pytest/pytest.run.test.ts similarity index 89% rename from src/test/unittests/pytest.run.test.ts rename to src/test/unittests/pytest/pytest.run.test.ts index 57cc69f7bf97..09941039ed74 100644 --- a/src/test/unittests/pytest.run.test.ts +++ b/src/test/unittests/pytest/pytest.run.test.ts @@ -5,16 +5,17 @@ import * as assert from 'assert'; import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; -import { IProcessServiceFactory } from '../../client/common/process/types'; -import { CommandSource } from '../../client/unittests/common/constants'; -import { ITestManagerFactory, TestFile, TestsToRun } from '../../client/unittests/common/types'; -import { rootWorkspaceUri, updateSetting } from '../common'; -import { MockProcessService } from '../mocks/proc'; -import { initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../initialize'; -import { UnitTestIocContainer } from './serviceRegistry'; +import { EXTENSION_ROOT_DIR } from '../../../client/common/constants'; +import { IProcessServiceFactory } from '../../../client/common/process/types'; +import { CommandSource } from '../../../client/unittests/common/constants'; +import { ITestManagerFactory, TestFile, TestsToRun } from '../../../client/unittests/common/types'; +import { rootWorkspaceUri, updateSetting } from '../../common'; +import { MockProcessService } from '../../mocks/proc'; +import { UnitTestIocContainer } from '../serviceRegistry'; +import { initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../../initialize'; -const UNITTEST_TEST_FILES_PATH = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles', 'standard'); -const PYTEST_RESULTS_PATH = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles', 'pytestFiles', 'results'); +const UNITTEST_TEST_FILES_PATH = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'testFiles', 'standard'); +const PYTEST_RESULTS_PATH = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'testFiles', 'pytestFiles', 'results'); // tslint:disable-next-line:max-func-body-length suite('Unit Tests - pytest - run with mocked process output', () => { @@ -48,7 +49,7 @@ suite('Unit Tests - pytest - run with mocked process output', () => { procService.onExecObservable((file, args, options, callback) => { if (args.indexOf('--collect-only') >= 0) { callback({ - out: fs.readFileSync(path.join(PYTEST_RESULTS_PATH, outputFileName), 'utf8'), + out: fs.readFileSync(path.join(PYTEST_RESULTS_PATH, outputFileName), 'utf8').replace(/\/Users\/donjayamanne\/.vscode\/extensions\/pythonVSCode\/src\/test\/pythonFiles\/testFiles\/noseFiles/g, PYTEST_RESULTS_PATH), source: 'stdout' }); } diff --git a/src/test/unittests/pytest.test.ts b/src/test/unittests/pytest/pytest.test.ts similarity index 79% rename from src/test/unittests/pytest.test.ts rename to src/test/unittests/pytest/pytest.test.ts index d8cea72441d5..210c3c5d8f84 100644 --- a/src/test/unittests/pytest.test.ts +++ b/src/test/unittests/pytest/pytest.test.ts @@ -1,13 +1,14 @@ import * as assert from 'assert'; import * as path from 'path'; import * as vscode from 'vscode'; -import { CommandSource } from '../../client/unittests/common/constants'; -import { ITestManagerFactory } from '../../client/unittests/common/types'; -import { rootWorkspaceUri, updateSetting } from '../common'; -import { initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../initialize'; -import { UnitTestIocContainer } from './serviceRegistry'; +import { EXTENSION_ROOT_DIR } from '../../../client/common/constants'; +import { CommandSource } from '../../../client/unittests/common/constants'; +import { ITestManagerFactory } from '../../../client/unittests/common/types'; +import { rootWorkspaceUri, updateSetting } from '../../common'; +import { UnitTestIocContainer } from '../serviceRegistry'; +import { initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../../initialize'; -const UNITTEST_SINGLE_TEST_FILE_PATH = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles', 'single'); +const UNITTEST_SINGLE_TEST_FILE_PATH = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'testFiles', 'single'); // tslint:disable-next-line:max-func-body-length suite('Unit Tests - pytest - discovery against actual python process', () => { diff --git a/src/test/unittests/unittest.discovery.test.ts b/src/test/unittests/unittest/unittest.discovery.test.ts similarity index 92% rename from src/test/unittests/unittest.discovery.test.ts rename to src/test/unittests/unittest/unittest.discovery.test.ts index e573726c2b4e..d3e8c5b477b5 100644 --- a/src/test/unittests/unittest.discovery.test.ts +++ b/src/test/unittests/unittest/unittest.discovery.test.ts @@ -6,15 +6,16 @@ import * as fs from 'fs-extra'; import { EOL } from 'os'; import * as path from 'path'; import { ConfigurationTarget } from 'vscode'; -import { IProcessServiceFactory } from '../../client/common/process/types'; -import { CommandSource } from '../../client/unittests/common/constants'; -import { ITestManagerFactory } from '../../client/unittests/common/types'; -import { rootWorkspaceUri, updateSetting } from '../common'; -import { MockProcessService } from '../mocks/proc'; -import { initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../initialize'; -import { UnitTestIocContainer } from './serviceRegistry'; +import { EXTENSION_ROOT_DIR } from '../../../client/common/constants'; +import { IProcessServiceFactory } from '../../../client/common/process/types'; +import { CommandSource } from '../../../client/unittests/common/constants'; +import { ITestManagerFactory } from '../../../client/unittests/common/types'; +import { rootWorkspaceUri, updateSetting } from '../../common'; +import { MockProcessService } from '../../mocks/proc'; +import { UnitTestIocContainer } from '../serviceRegistry'; +import { initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../../initialize'; -const testFilesPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles'); +const testFilesPath = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'testFiles'); const UNITTEST_TEST_FILES_PATH = path.join(testFilesPath, 'standard'); const UNITTEST_SINGLE_TEST_FILE_PATH = path.join(testFilesPath, 'single'); const unitTestTestFilesCwdPath = path.join(testFilesPath, 'cwd', 'src'); diff --git a/src/test/unittests/unittest/unittest.discovery.unit.test.ts b/src/test/unittests/unittest/unittest.discovery.unit.test.ts new file mode 100644 index 000000000000..b70ad76845d5 --- /dev/null +++ b/src/test/unittests/unittest/unittest.discovery.unit.test.ts @@ -0,0 +1,303 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:max-func-body-length + +import { expect, use } from 'chai'; +import * as chaipromise from 'chai-as-promised'; +import * as path from 'path'; +import * as typeMoq from 'typemoq'; +import { CancellationToken } from 'vscode'; +import { IServiceContainer } from '../../../client/ioc/types'; +import { UNITTEST_PROVIDER } from '../../../client/unittests/common/constants'; +import { ITestDiscoveryService, ITestRunner, ITestsParser, Options, TestDiscoveryOptions, Tests } from '../../../client/unittests/common/types'; +import { IArgumentsHelper } from '../../../client/unittests/types'; +import { TestDiscoveryService } from '../../../client/unittests/unittest/services/discoveryService'; + +use(chaipromise); + +suite('Unit Tests - Unittest - Discovery', () => { + let discoveryService: ITestDiscoveryService; + let argsHelper: typeMoq.IMock; + let testParser: typeMoq.IMock; + let runner: typeMoq.IMock; + const dir = path.join('a', 'b', 'c'); + const pattern = 'Pattern_To_Search_For'; + setup(() => { + const serviceContainer = typeMoq.Mock.ofType(); + argsHelper = typeMoq.Mock.ofType(); + testParser = typeMoq.Mock.ofType(); + runner = typeMoq.Mock.ofType(); + + serviceContainer.setup(s => s.get(typeMoq.It.isValue(IArgumentsHelper), typeMoq.It.isAny())) + .returns(() => argsHelper.object); + serviceContainer.setup(s => s.get(typeMoq.It.isValue(ITestRunner), typeMoq.It.isAny())) + .returns(() => runner.object); + + discoveryService = new TestDiscoveryService(serviceContainer.object, testParser.object); + }); + test('Ensure discovery is invoked with the right args with start directory defined with -s', async () => { + const args: string[] = []; + const runOutput = 'xyz'; + const tests: Tests = { + summary: { errors: 1, failures: 0, passed: 0, skipped: 0 }, + testFiles: [], testFunctions: [], testSuites: [], + rootTestFolders: [], testFolders: [] + }; + argsHelper.setup(a => a.getOptionValues(typeMoq.It.isValue(args), typeMoq.It.isValue('-s'))) + .returns(() => dir) + .verifiable(typeMoq.Times.once()); + runner.setup(r => r.run(typeMoq.It.isValue(UNITTEST_PROVIDER), typeMoq.It.isAny())) + .callback((_, opts: Options) => { + expect(opts.args).to.include('-c'); + expect(opts.args[1]).to.contain(dir); + expect(opts.args[1]).to.not.contain('loader.discover("."'); + }) + .returns(() => Promise.resolve(runOutput)) + .verifiable(typeMoq.Times.once()); + testParser.setup(t => t.parse(typeMoq.It.isValue(runOutput), typeMoq.It.isAny())) + .returns(() => tests) + .verifiable(typeMoq.Times.once()); + + const options = typeMoq.Mock.ofType(); + const token = typeMoq.Mock.ofType(); + options.setup(o => o.args).returns(() => args); + options.setup(o => o.token).returns(() => token.object); + token.setup(t => t.isCancellationRequested) + .returns(() => false); + + const result = await discoveryService.discoverTests(options.object); + + expect(result).to.be.equal(tests); + runner.verifyAll(); + testParser.verifyAll(); + }); + test('Ensure discovery is invoked with the right args with start directory defined with --start-directory', async () => { + const args: string[] = []; + const runOutput = 'xyz'; + const tests: Tests = { + summary: { errors: 1, failures: 0, passed: 0, skipped: 0 }, + testFiles: [], testFunctions: [], testSuites: [], + rootTestFolders: [], testFolders: [] + }; + argsHelper.setup(a => a.getOptionValues(typeMoq.It.isValue(args), typeMoq.It.isValue('-s'))) + .returns(() => undefined) + .verifiable(typeMoq.Times.once()); + argsHelper.setup(a => a.getOptionValues(typeMoq.It.isValue(args), typeMoq.It.isValue('--start-directory'))) + .returns(() => dir) + .verifiable(typeMoq.Times.once()); + runner.setup(r => r.run(typeMoq.It.isValue(UNITTEST_PROVIDER), typeMoq.It.isAny())) + .callback((_, opts: Options) => { + expect(opts.args).to.include('-c'); + expect(opts.args[1]).to.contain(dir); + expect(opts.args[1]).to.not.contain('loader.discover("."'); + }) + .returns(() => Promise.resolve(runOutput)) + .verifiable(typeMoq.Times.once()); + testParser.setup(t => t.parse(typeMoq.It.isValue(runOutput), typeMoq.It.isAny())) + .returns(() => tests) + .verifiable(typeMoq.Times.once()); + + const options = typeMoq.Mock.ofType(); + const token = typeMoq.Mock.ofType(); + options.setup(o => o.args).returns(() => args); + options.setup(o => o.token).returns(() => token.object); + token.setup(t => t.isCancellationRequested) + .returns(() => false); + + const result = await discoveryService.discoverTests(options.object); + + expect(result).to.be.equal(tests); + runner.verifyAll(); + testParser.verifyAll(); + }); + test('Ensure discovery is invoked with the right args without a start directory', async () => { + const args: string[] = []; + const runOutput = 'xyz'; + const tests: Tests = { + summary: { errors: 1, failures: 0, passed: 0, skipped: 0 }, + testFiles: [], testFunctions: [], testSuites: [], + rootTestFolders: [], testFolders: [] + }; + argsHelper.setup(a => a.getOptionValues(typeMoq.It.isValue(args), typeMoq.It.isValue('-s'))) + .returns(() => undefined) + .verifiable(typeMoq.Times.once()); + argsHelper.setup(a => a.getOptionValues(typeMoq.It.isValue(args), typeMoq.It.isValue('--start-directory'))) + .returns(() => undefined) + .verifiable(typeMoq.Times.once()); + runner.setup(r => r.run(typeMoq.It.isValue(UNITTEST_PROVIDER), typeMoq.It.isAny())) + .callback((_, opts: Options) => { + expect(opts.args).to.include('-c'); + expect(opts.args[1]).to.not.contain(dir); + expect(opts.args[1]).to.contain('loader.discover("."'); + }) + .returns(() => Promise.resolve(runOutput)) + .verifiable(typeMoq.Times.once()); + testParser.setup(t => t.parse(typeMoq.It.isValue(runOutput), typeMoq.It.isAny())) + .returns(() => tests) + .verifiable(typeMoq.Times.once()); + + const options = typeMoq.Mock.ofType(); + const token = typeMoq.Mock.ofType(); + options.setup(o => o.args).returns(() => args); + options.setup(o => o.token).returns(() => token.object); + token.setup(t => t.isCancellationRequested) + .returns(() => false); + + const result = await discoveryService.discoverTests(options.object); + + expect(result).to.be.equal(tests); + runner.verifyAll(); + testParser.verifyAll(); + }); + test('Ensure discovery is invoked with the right args without a pattern defined with -p', async () => { + const args: string[] = []; + const runOutput = 'xyz'; + const tests: Tests = { + summary: { errors: 1, failures: 0, passed: 0, skipped: 0 }, + testFiles: [], testFunctions: [], testSuites: [], + rootTestFolders: [], testFolders: [] + }; + argsHelper.setup(a => a.getOptionValues(typeMoq.It.isValue(args), typeMoq.It.isValue('-p'))) + .returns(() => pattern) + .verifiable(typeMoq.Times.once()); + runner.setup(r => r.run(typeMoq.It.isValue(UNITTEST_PROVIDER), typeMoq.It.isAny())) + .callback((_, opts: Options) => { + expect(opts.args).to.include('-c'); + expect(opts.args[1]).to.contain(pattern); + expect(opts.args[1]).to.not.contain('test*.py'); + }) + .returns(() => Promise.resolve(runOutput)) + .verifiable(typeMoq.Times.once()); + testParser.setup(t => t.parse(typeMoq.It.isValue(runOutput), typeMoq.It.isAny())) + .returns(() => tests) + .verifiable(typeMoq.Times.once()); + + const options = typeMoq.Mock.ofType(); + const token = typeMoq.Mock.ofType(); + options.setup(o => o.args).returns(() => args); + options.setup(o => o.token).returns(() => token.object); + token.setup(t => t.isCancellationRequested) + .returns(() => false); + + const result = await discoveryService.discoverTests(options.object); + + expect(result).to.be.equal(tests); + runner.verifyAll(); + testParser.verifyAll(); + }); + test('Ensure discovery is invoked with the right args without a pattern defined with ---pattern', async () => { + const args: string[] = []; + const runOutput = 'xyz'; + const tests: Tests = { + summary: { errors: 1, failures: 0, passed: 0, skipped: 0 }, + testFiles: [], testFunctions: [], testSuites: [], + rootTestFolders: [], testFolders: [] + }; + argsHelper.setup(a => a.getOptionValues(typeMoq.It.isValue(args), typeMoq.It.isValue('-p'))) + .returns(() => undefined) + .verifiable(typeMoq.Times.once()); + argsHelper.setup(a => a.getOptionValues(typeMoq.It.isValue(args), typeMoq.It.isValue('--pattern'))) + .returns(() => pattern) + .verifiable(typeMoq.Times.once()); + runner.setup(r => r.run(typeMoq.It.isValue(UNITTEST_PROVIDER), typeMoq.It.isAny())) + .callback((_, opts: Options) => { + expect(opts.args).to.include('-c'); + expect(opts.args[1]).to.contain(pattern); + expect(opts.args[1]).to.not.contain('test*.py'); + }) + .returns(() => Promise.resolve(runOutput)) + .verifiable(typeMoq.Times.once()); + testParser.setup(t => t.parse(typeMoq.It.isValue(runOutput), typeMoq.It.isAny())) + .returns(() => tests) + .verifiable(typeMoq.Times.once()); + + const options = typeMoq.Mock.ofType(); + const token = typeMoq.Mock.ofType(); + options.setup(o => o.args).returns(() => args); + options.setup(o => o.token).returns(() => token.object); + token.setup(t => t.isCancellationRequested) + .returns(() => false); + + const result = await discoveryService.discoverTests(options.object); + + expect(result).to.be.equal(tests); + runner.verifyAll(); + testParser.verifyAll(); + }); + test('Ensure discovery is invoked with the right args without a pattern not defined', async () => { + const args: string[] = []; + const runOutput = 'xyz'; + const tests: Tests = { + summary: { errors: 1, failures: 0, passed: 0, skipped: 0 }, + testFiles: [], testFunctions: [], testSuites: [], + rootTestFolders: [], testFolders: [] + }; + argsHelper.setup(a => a.getOptionValues(typeMoq.It.isValue(args), typeMoq.It.isValue('-p'))) + .returns(() => undefined) + .verifiable(typeMoq.Times.once()); + argsHelper.setup(a => a.getOptionValues(typeMoq.It.isValue(args), typeMoq.It.isValue('--pattern'))) + .returns(() => undefined) + .verifiable(typeMoq.Times.once()); + runner.setup(r => r.run(typeMoq.It.isValue(UNITTEST_PROVIDER), typeMoq.It.isAny())) + .callback((_, opts: Options) => { + expect(opts.args).to.include('-c'); + expect(opts.args[1]).to.not.contain(pattern); + expect(opts.args[1]).to.contain('test*.py'); + }) + .returns(() => Promise.resolve(runOutput)) + .verifiable(typeMoq.Times.once()); + testParser.setup(t => t.parse(typeMoq.It.isValue(runOutput), typeMoq.It.isAny())) + .returns(() => tests) + .verifiable(typeMoq.Times.once()); + + const options = typeMoq.Mock.ofType(); + const token = typeMoq.Mock.ofType(); + options.setup(o => o.args).returns(() => args); + options.setup(o => o.token).returns(() => token.object); + token.setup(t => t.isCancellationRequested) + .returns(() => false); + + const result = await discoveryService.discoverTests(options.object); + + expect(result).to.be.equal(tests); + runner.verifyAll(); + testParser.verifyAll(); + }); + test('Ensure discovery is cancelled', async () => { + const args: string[] = []; + const runOutput = 'xyz'; + const tests: Tests = { + summary: { errors: 1, failures: 0, passed: 0, skipped: 0 }, + testFiles: [], testFunctions: [], testSuites: [], + rootTestFolders: [], testFolders: [] + }; + argsHelper.setup(a => a.getOptionValues(typeMoq.It.isValue(args), typeMoq.It.isValue('-p'))) + .returns(() => undefined) + .verifiable(typeMoq.Times.once()); + argsHelper.setup(a => a.getOptionValues(typeMoq.It.isValue(args), typeMoq.It.isValue('--pattern'))) + .returns(() => undefined) + .verifiable(typeMoq.Times.once()); + runner.setup(r => r.run(typeMoq.It.isValue(UNITTEST_PROVIDER), typeMoq.It.isAny())) + .returns(() => Promise.resolve(runOutput)) + .verifiable(typeMoq.Times.once()); + testParser.setup(t => t.parse(typeMoq.It.isValue(runOutput), typeMoq.It.isAny())) + .returns(() => tests) + .verifiable(typeMoq.Times.never()); + + const options = typeMoq.Mock.ofType(); + const token = typeMoq.Mock.ofType(); + options.setup(o => o.args).returns(() => args); + options.setup(o => o.token).returns(() => token.object); + token.setup(t => t.isCancellationRequested) + .returns(() => true); + + const promise = discoveryService.discoverTests(options.object); + + await expect(promise).to.eventually.be.rejectedWith('cancelled'); + runner.verifyAll(); + testParser.verifyAll(); + }); +}); diff --git a/src/test/unittests/unittest.run.test.ts b/src/test/unittests/unittest/unittest.run.test.ts similarity index 90% rename from src/test/unittests/unittest.run.test.ts rename to src/test/unittests/unittest/unittest.run.test.ts index 757ecf836199..45ecc8a9e939 100644 --- a/src/test/unittests/unittest.run.test.ts +++ b/src/test/unittests/unittest/unittest.run.test.ts @@ -6,16 +6,23 @@ import * as fs from 'fs-extra'; import { EOL } from 'os'; import * as path from 'path'; import { ConfigurationTarget } from 'vscode'; -import { IProcessServiceFactory } from '../../client/common/process/types'; -import { CommandSource } from '../../client/unittests/common/constants'; -import { ITestManagerFactory, IUnitTestSocketServer, TestsToRun } from '../../client/unittests/common/types'; -import { rootWorkspaceUri, updateSetting } from '../common'; -import { MockProcessService } from '../mocks/proc'; -import { initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../initialize'; -import { MockUnitTestSocketServer } from './mocks'; -import { UnitTestIocContainer } from './serviceRegistry'; +import { EXTENSION_ROOT_DIR } from '../../../client/common/constants'; +import { IProcessServiceFactory } from '../../../client/common/process/types'; +import { ArgumentsHelper } from '../../../client/unittests/common/argumentsHelper'; +import { CommandSource, UNITTEST_PROVIDER } from '../../../client/unittests/common/constants'; +import { TestRunner } from '../../../client/unittests/common/runner'; +import { ITestManagerFactory, ITestRunner, IUnitTestSocketServer, TestsToRun } from '../../../client/unittests/common/types'; +import { IArgumentsHelper, IArgumentsService, ITestManagerRunner, IUnitTestHelper } from '../../../client/unittests/types'; +import { UnitTestHelper } from '../../../client/unittests/unittest/helper'; +import { TestManagerRunner } from '../../../client/unittests/unittest/runner'; +import { ArgumentsService } from '../../../client/unittests/unittest/services/argsService'; +import { rootWorkspaceUri, updateSetting } from '../../common'; +import { MockProcessService } from '../../mocks/proc'; +import { MockUnitTestSocketServer } from '../mocks'; +import { UnitTestIocContainer } from '../serviceRegistry'; +import { initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../../initialize'; -const testFilesPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles'); +const testFilesPath = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'testFiles'); const UNITTEST_TEST_FILES_PATH = path.join(testFilesPath, 'standard'); const unitTestSpecificTestFilesPath = path.join(testFilesPath, 'specificTest'); const defaultUnitTestArgs = [ @@ -68,6 +75,11 @@ suite('Unit Tests - unittest - run with mocked process output', () => { ioc.registerTestsHelper(); ioc.registerTestStorage(); ioc.registerTestVisitors(); + ioc.serviceManager.add(IArgumentsService, ArgumentsService, UNITTEST_PROVIDER); + ioc.serviceManager.add(IArgumentsHelper, ArgumentsHelper); + ioc.serviceManager.add(ITestManagerRunner, TestManagerRunner, UNITTEST_PROVIDER); + ioc.serviceManager.add(ITestRunner, TestRunner); + ioc.serviceManager.add(IUnitTestHelper, UnitTestHelper); } async function ignoreTestLauncher() { diff --git a/src/test/unittests/unittest.test.ts b/src/test/unittests/unittest/unittest.test.ts similarity index 83% rename from src/test/unittests/unittest.test.ts rename to src/test/unittests/unittest/unittest.test.ts index 8465b7bdbb92..ebad6a6b5d18 100644 --- a/src/test/unittests/unittest.test.ts +++ b/src/test/unittests/unittest/unittest.test.ts @@ -2,13 +2,14 @@ import * as assert from 'assert'; import * as fs from 'fs-extra'; import * as path from 'path'; import { ConfigurationTarget } from 'vscode'; -import { CommandSource } from '../../client/unittests/common/constants'; -import { ITestManagerFactory } from '../../client/unittests/common/types'; -import { rootWorkspaceUri, updateSetting } from '../common'; -import { initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../initialize'; -import { UnitTestIocContainer } from './serviceRegistry'; +import { EXTENSION_ROOT_DIR } from '../../../client/common/constants'; +import { CommandSource } from '../../../client/unittests/common/constants'; +import { ITestManagerFactory } from '../../../client/unittests/common/types'; +import { rootWorkspaceUri, updateSetting } from '../../common'; +import { UnitTestIocContainer } from '../serviceRegistry'; +import { initialize, initializeTest, IS_MULTI_ROOT_TEST } from './../../initialize'; -const testFilesPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'testFiles'); +const testFilesPath = path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'pythonFiles', 'testFiles'); const UNITTEST_TEST_FILES_PATH = path.join(testFilesPath, 'standard'); const UNITTEST_SINGLE_TEST_FILE_PATH = path.join(testFilesPath, 'single'); const defaultUnitTestArgs = [ From 6dbaf9003b2d7f73f2129e6612fb0afee0446f14 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 19 Jun 2018 14:14:46 -0700 Subject: [PATCH 360/433] Display banner once debugging has terminated (#2009) Fixes #2008 --- src/client/common/application/debugService.ts | 5 ++- src/client/common/application/types.ts | 5 +++ src/client/debugger/banner.ts | 6 ++-- src/test/debugger/banner.unit.test.ts | 32 +++++++++---------- 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/client/common/application/debugService.ts b/src/client/common/application/debugService.ts index 975feee63f4e..33d8cad5e1c7 100644 --- a/src/client/common/application/debugService.ts +++ b/src/client/common/application/debugService.ts @@ -9,9 +9,12 @@ import { IDebugService } from './types'; @injectable() export class DebugService implements IDebugService { - public get onDidStartDebugSession(): Event{ + public get onDidStartDebugSession(): Event { return debug.onDidStartDebugSession; } + public get onDidTerminateDebugSession(): Event { + return debug.onDidTerminateDebugSession; + } public startDebugging(folder: WorkspaceFolder | undefined, nameOrConfiguration: string | DebugConfiguration): Thenable { return debug.startDebugging(folder, nameOrConfiguration); } diff --git a/src/client/common/application/types.ts b/src/client/common/application/types.ts index c6546ce056fe..3943d9cb7504 100644 --- a/src/client/common/application/types.ts +++ b/src/client/common/application/types.ts @@ -539,6 +539,11 @@ export interface IDebugService { * An [event](#Event) which fires when a new [debug session](#DebugSession) has been started. */ onDidStartDebugSession: Event; + + /** + * An [event](#Event) which fires when a [debug session](#DebugSession) has terminated. + */ + onDidTerminateDebugSession: Event; /** * Start debugging by using either a named launch or named compound configuration, * or by directly passing a [DebugConfiguration](#DebugConfiguration). diff --git a/src/client/debugger/banner.ts b/src/client/debugger/banner.ts index 5d45677749c6..a5971b7cfe36 100644 --- a/src/client/debugger/banner.ts +++ b/src/client/debugger/banner.ts @@ -39,10 +39,10 @@ export class ExperimentalDebuggerBanner implements IExperimentalDebuggerBanner { return; } const debuggerService = this.serviceContainer.get(IDebugService); - const disposable = debuggerService.onDidStartDebugSession(async e => { + const disposable = debuggerService.onDidTerminateDebugSession(async e => { if (e.type === ExperimentalDebuggerType) { const logger = this.serviceContainer.get(ILogger); - await this.onDebugSessionStarted() + await this.onDidTerminateDebugSession() .catch(ex => logger.logError('Error in debugger Banner', ex)); } }); @@ -114,7 +114,7 @@ export class ExperimentalDebuggerBanner implements IExperimentalDebuggerBanner { const num = parseInt(`0x${lastHexValue}`, 16); return isNaN(num) ? crypto.randomBytes(1).toString('hex').slice(-1) : lastHexValue; } - private async onDebugSessionStarted(): Promise { + private async onDidTerminateDebugSession(): Promise { if (!this.enabled) { return; } diff --git a/src/test/debugger/banner.unit.test.ts b/src/test/debugger/banner.unit.test.ts index 886d2ff16eba..22706fea11ae 100644 --- a/src/test/debugger/banner.unit.test.ts +++ b/src/test/debugger/banner.unit.test.ts @@ -70,9 +70,9 @@ suite('Debugging - Banner', () => { browser.verifyAll(); }); test('Increment Debugger Launch Counter when debug session starts', async () => { - let onDidStartDebugSessionCb: (e: DebugSession) => Promise; - debugService.setup(d => d.onDidStartDebugSession(typemoq.It.isAny())) - .callback(cb => onDidStartDebugSessionCb = cb) + let onDidTerminateDebugSessionCb: (e: DebugSession) => Promise; + debugService.setup(d => d.onDidTerminateDebugSession(typemoq.It.isAny())) + .callback(cb => onDidTerminateDebugSessionCb = cb) .verifiable(typemoq.Times.once()); const debuggerLaunchCounter = 1234; @@ -84,7 +84,7 @@ suite('Debugging - Banner', () => { .verifiable(typemoq.Times.atLeastOnce()); banner.initialize(); - await onDidStartDebugSessionCb!({ type: ExperimentalDebuggerType } as any); + await onDidTerminateDebugSessionCb!({ type: ExperimentalDebuggerType } as any); launchCounterState.verifyAll(); browser.verifyAll(); @@ -92,7 +92,7 @@ suite('Debugging - Banner', () => { showBannerState.verifyAll(); }); test('Do not Increment Debugger Launch Counter when debug session starts and Banner is disabled', async () => { - debugService.setup(d => d.onDidStartDebugSession(typemoq.It.isAny())) + debugService.setup(d => d.onDidTerminateDebugSession(typemoq.It.isAny())) .verifiable(typemoq.Times.never()); const debuggerLaunchCounter = 1234; @@ -147,11 +147,11 @@ suite('Debugging - Banner', () => { launchThresholdCounterState.verifyAll(); }); test('showBanner must be invoked when shouldShowBanner returns true', async () => { - let onDidStartDebugSessionCb: (e: DebugSession) => Promise; + let onDidTerminateDebugSessionCb: (e: DebugSession) => Promise; const currentLaunchCounter = 50; - debugService.setup(d => d.onDidStartDebugSession(typemoq.It.isAny())) - .callback(cb => onDidStartDebugSessionCb = cb) + debugService.setup(d => d.onDidTerminateDebugSession(typemoq.It.isAny())) + .callback(cb => onDidTerminateDebugSessionCb = cb) .verifiable(typemoq.Times.atLeastOnce()); showBannerState.setup(s => s.value).returns(() => true) .verifiable(typemoq.Times.atLeastOnce()); @@ -166,7 +166,7 @@ suite('Debugging - Banner', () => { appShell.setup(a => a.showInformationMessage(typemoq.It.isValue(message), typemoq.It.isValue(yes), typemoq.It.isValue(no))) .verifiable(typemoq.Times.once()); banner.initialize(); - await onDidStartDebugSessionCb!({ type: ExperimentalDebuggerType } as any); + await onDidTerminateDebugSessionCb!({ type: ExperimentalDebuggerType } as any); appShell.verifyAll(); showBannerState.verifyAll(); @@ -174,11 +174,11 @@ suite('Debugging - Banner', () => { launchThresholdCounterState.verifyAll(); }); test('showBanner must not be invoked the second time after dismissing the message', async () => { - let onDidStartDebugSessionCb: (e: DebugSession) => Promise; + let onDidTerminateDebugSessionCb: (e: DebugSession) => Promise; let currentLaunchCounter = 50; - debugService.setup(d => d.onDidStartDebugSession(typemoq.It.isAny())) - .callback(cb => onDidStartDebugSessionCb = cb) + debugService.setup(d => d.onDidTerminateDebugSession(typemoq.It.isAny())) + .callback(cb => onDidTerminateDebugSessionCb = cb) .verifiable(typemoq.Times.atLeastOnce()); showBannerState.setup(s => s.value).returns(() => true) .verifiable(typemoq.Times.atLeastOnce()); @@ -193,10 +193,10 @@ suite('Debugging - Banner', () => { .returns(() => Promise.resolve(undefined)) .verifiable(typemoq.Times.once()); banner.initialize(); - await onDidStartDebugSessionCb!({ type: ExperimentalDebuggerType } as any); - await onDidStartDebugSessionCb!({ type: ExperimentalDebuggerType } as any); - await onDidStartDebugSessionCb!({ type: ExperimentalDebuggerType } as any); - await onDidStartDebugSessionCb!({ type: ExperimentalDebuggerType } as any); + await onDidTerminateDebugSessionCb!({ type: ExperimentalDebuggerType } as any); + await onDidTerminateDebugSessionCb!({ type: ExperimentalDebuggerType } as any); + await onDidTerminateDebugSessionCb!({ type: ExperimentalDebuggerType } as any); + await onDidTerminateDebugSessionCb!({ type: ExperimentalDebuggerType } as any); appShell.verifyAll(); showBannerState.verifyAll(); From e375c470adb31725028399eb8bbd053ab0e729ff Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 19 Jun 2018 14:28:23 -0700 Subject: [PATCH 361/433] Relax path validations (ignore %) (#2006) * Relax path validations (ignore %) * Allow trailing path delimiters * Update message with solution --- .../diagnostics/applicationDiagnostics.ts | 2 +- .../diagnostics/checks/envPathVariable.ts | 6 +++--- .../applicationDiagnostics.unit.test.ts | 2 +- .../checks/envPathVariable.unit.test.ts | 15 ++++++++++++++- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/client/application/diagnostics/applicationDiagnostics.ts b/src/client/application/diagnostics/applicationDiagnostics.ts index c3eee0d55fe3..5956cf5d6e5e 100644 --- a/src/client/application/diagnostics/applicationDiagnostics.ts +++ b/src/client/application/diagnostics/applicationDiagnostics.ts @@ -27,7 +27,7 @@ export class ApplicationDiagnostics implements IApplicationDiagnostics { const logger = this.serviceContainer.get(ILogger); const outputChannel = this.serviceContainer.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); diagnostics.forEach(item => { - const message = `Diagnostic Code: ${item.code}, Mesage: ${item.message}`; + const message = `Diagnostic Code: ${item.code}, Message: ${item.message}`; switch (item.severity) { case DiagnosticSeverity.Error: { logger.logError(message); diff --git a/src/client/application/diagnostics/checks/envPathVariable.ts b/src/client/application/diagnostics/checks/envPathVariable.ts index c2b09b70c0dc..9b00ed1303f1 100644 --- a/src/client/application/diagnostics/checks/envPathVariable.ts +++ b/src/client/application/diagnostics/checks/envPathVariable.ts @@ -16,8 +16,8 @@ import { DiagnosticCodes } from '../constants'; import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../promptHandler'; import { DiagnosticScope, IDiagnostic, IDiagnosticHandlerService } from '../types'; -const InvalidEnvPathVariableMessage = 'The environment variable \'{0}\' seems to have some paths containing characters (\';\', \'"\', \'%\' or \';;\').' + - ' The existence of such characters are known to have caused the {1} extension to not load.'; +const InvalidEnvPathVariableMessage = 'The environment variable \'{0}\' seems to have some paths containing characters (\';\', \'"\' or \';;\').' + + ' The existence of such characters are known to have caused the {1} extension to not load. If the extension fails to load please modify your paths to remove these characters.'; export class InvalidEnvironmentPathVariableDiagnostic extends BaseDiagnostic { constructor(message) { @@ -79,6 +79,6 @@ export class EnvironmentPathVariableDiagnosticsService extends BaseDiagnosticsSe const pathValue = currentProc.env[this.platform.pathVariableName]; const pathSeparator = this.serviceContainer.get(IPathUtils).delimiter; const paths = pathValue.split(pathSeparator); - return paths.filter(item => item.indexOf('"') >= 0 || item.indexOf(';') >= 0 || item.indexOf('%') >= 0 || item.length === 0).length > 0; + return paths.filter((item, index) => item.indexOf('"') >= 0 || item.indexOf(';') >= 0 || (item.length === 0 && index !== paths.length - 1)).length > 0; } } diff --git a/src/test/application/diagnostics/applicationDiagnostics.unit.test.ts b/src/test/application/diagnostics/applicationDiagnostics.unit.test.ts index 03f3c149d153..1fad39ac5453 100644 --- a/src/test/application/diagnostics/applicationDiagnostics.unit.test.ts +++ b/src/test/application/diagnostics/applicationDiagnostics.unit.test.ts @@ -79,7 +79,7 @@ suite('Application Diagnostics - ApplicationDiagnostics', () => { } for (const diagnostic of diagnostics) { - const message = `Diagnostic Code: ${diagnostic.code}, Mesage: ${diagnostic.message}`; + const message = `Diagnostic Code: ${diagnostic.code}, Message: ${diagnostic.message}`; switch (diagnostic.severity) { case DiagnosticSeverity.Error: { logger.setup(l => l.logError(message)) diff --git a/src/test/application/diagnostics/checks/envPathVariable.unit.test.ts b/src/test/application/diagnostics/checks/envPathVariable.unit.test.ts index c82802791b0b..e71efc0406cd 100644 --- a/src/test/application/diagnostics/checks/envPathVariable.unit.test.ts +++ b/src/test/application/diagnostics/checks/envPathVariable.unit.test.ts @@ -114,7 +114,8 @@ suite('Application Diagnostics - Checks Env Path Variable', () => { expect(diagnostics).to.be.deep.equal([]); }); - [';;', '"', '%'].forEach(invalidCharacter => { + // Note: On windows, when a path contains a `;` then Windows encloses the path within `"`. + [';;', '"'].forEach(invalidCharacter => { test(`Should return single diagnostics for Windows if path contains ${invalidCharacter}`, async () => { platformService.setup(p => p.isWindows).returns(() => true); const paths = [ @@ -132,6 +133,18 @@ suite('Application Diagnostics - Checks Env Path Variable', () => { expect(diagnostics[0].severity).to.be.equal(DiagnosticSeverity.Warning); expect(diagnostics[0].scope).to.be.equal(DiagnosticScope.Global); }); + test('Should not return diagnostics for Windows if path ends with delimiter', async () => { + const paths = [ + path.join('one', 'two', 'three'), + path.join('one', 'two', 'four') + ].join(pathDelimiter) + pathDelimiter; + platformService.setup(p => p.isWindows).returns(() => true); + procEnv.setup(env => env[pathVariableName]).returns(() => paths); + + const diagnostics = await diagnosticService.diagnose(); + + expect(diagnostics).to.be.lengthOf(0); + }); }); test('Should display three options in message displayed with 2 commands', async () => { platformService.setup(p => p.isWindows).returns(() => true); From b212a0967e76dcea02a222077bab9db76ecd1e71 Mon Sep 17 00:00:00 2001 From: Derek Keeler Date: Tue, 19 Jun 2018 18:25:47 -0700 Subject: [PATCH 362/433] Vsts badges in CONTRIBUTING (#2011) --- CONTRIBUTING.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cded8ef3f303..eb92cc799572 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,15 @@ # Contributing to the Python extension for Visual Studio Code + [![Build Status (Travis)](https://travis-ci.org/Microsoft/vscode-python.svg?branch=master)](https://travis-ci.org/Microsoft/vscode-python) [![Build status (AppVeyor)](https://ci.appveyor.com/api/projects/status/s0pt8d79gqw222j7?svg=true)](https://ci.appveyor.com/project/DonJayamanne/vscode-python-v3vd6) [![codecov](https://codecov.io/gh/Microsoft/vscode-python/branch/master/graph/badge.svg)](https://codecov.io/gh/Microsoft/vscode-python) +--- + +| VSCode-Python-CI | VSCode-Python-Rolling-CI | VSCode-Python-ptvsd_master-CI | +|-|-|-| +|[![Build status](https://vscode-python.visualstudio.com/VSCode-Python/_apis/build/status/VSCode-Python-CI)](https://vscode-python.visualstudio.com/VSCode-Python/_apis/build/latest/VSCode-Python-CI) | [![vsts](https://vscode-python.visualstudio.com/VSCode-Python/_apis/build/status/VSCode-Python-Rolling-CI)](https://vscode-python.visualstudio.com/VSCode-Python/_apis/build/latest/VSCode-Python-Rolling-CI) | [![Build status](https://vscode-python.visualstudio.com/VSCode-Python/_apis/build/status/VSCode-Python-ptvsd_master-CI)](https://vscode-python.visualstudio.com/VSCode-Python/_apis/build/latest/VSCode-Python-ptvsd_master-CI)| +--- + # Contributing to Microsoft Python Analysis Engine [Contributing to Python Analysis Engine](https://github.com/Microsoft/vscode-python/blob/master/CONTRIBUTING%20-%20PYTHON_ANALYSIS.md) From 48c2ab7e6b5daca84cbaa169432f37a5049e6010 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Wed, 20 Jun 2018 11:13:06 -0700 Subject: [PATCH 363/433] 2018.6.0 final release (#2020) --- CHANGELOG.md | 54 ++++++++++++++++++++++++++++++++--- news/1 Enhancements/1037.md | 2 -- news/1 Enhancements/127.md | 1 - news/1 Enhancements/156.md | 8 ------ news/1 Enhancements/1902.md | 2 -- news/1 Enhancements/1959.md | 4 --- news/1 Enhancements/995.md | 1 - news/2 Fixes/1064.md | 1 - news/2 Fixes/1070.md | 1 - news/2 Fixes/1638.md | 2 -- news/2 Fixes/1721.md | 1 - news/2 Fixes/1759.md | 1 - news/2 Fixes/1800.md | 1 - news/2 Fixes/1811.md | 2 -- news/2 Fixes/1829.md | 1 - news/2 Fixes/1875.md | 1 - news/2 Fixes/1919.md | 1 - news/2 Fixes/459.md | 1 - news/3 Code Health/1237.md | 1 - news/3 Code Health/1250.md | 1 - news/3 Code Health/1338.md | 1 - news/3 Code Health/1376.md | 1 - news/3 Code Health/1402.md | 1 - news/3 Code Health/1407.md | 1 - news/3 Code Health/1542.md | 1 - news/3 Code Health/1545.md | 1 - news/3 Code Health/1614.md | 1 - news/3 Code Health/1766.md | 1 - news/3 Code Health/1767.md | 1 - news/3 Code Health/1770.md | 1 - news/3 Code Health/1803.md | 1 - news/3 Code Health/1815.md | 1 - news/3 Code Health/1817.md | 1 - news/3 Code Health/1821.md | 1 - news/3 Code Health/1833.md | 1 - news/3 Code Health/1842.md | 1 - news/3 Code Health/1867.md | 1 - news/3 Code Health/1885.md | 1 - news/3 Code Health/1887.md | 1 - news/3 Code Health/1893.md | 1 - news/3 Code Health/1897.md | 1 - news/3 Code Health/1918.md | 1 - news/3 Code Health/1922.md | 1 - news/3 Code Health/1953.md | 1 - news/3 Code Health/1957.md | 1 - news/3 Code Health/1968.md | 1 - news/3 Code Health/256.md | 1 - news/3 Code Health/932.md | 1 - news/announce.py | 57 +++++++++++++++++++++---------------- package.json | 2 +- 50 files changed, 84 insertions(+), 90 deletions(-) delete mode 100644 news/1 Enhancements/1037.md delete mode 100644 news/1 Enhancements/127.md delete mode 100644 news/1 Enhancements/156.md delete mode 100644 news/1 Enhancements/1902.md delete mode 100644 news/1 Enhancements/1959.md delete mode 100644 news/1 Enhancements/995.md delete mode 100644 news/2 Fixes/1064.md delete mode 100644 news/2 Fixes/1070.md delete mode 100644 news/2 Fixes/1638.md delete mode 100644 news/2 Fixes/1721.md delete mode 100644 news/2 Fixes/1759.md delete mode 100644 news/2 Fixes/1800.md delete mode 100644 news/2 Fixes/1811.md delete mode 100644 news/2 Fixes/1829.md delete mode 100644 news/2 Fixes/1875.md delete mode 100644 news/2 Fixes/1919.md delete mode 100644 news/2 Fixes/459.md delete mode 100644 news/3 Code Health/1237.md delete mode 100644 news/3 Code Health/1250.md delete mode 100644 news/3 Code Health/1338.md delete mode 100644 news/3 Code Health/1376.md delete mode 100644 news/3 Code Health/1402.md delete mode 100644 news/3 Code Health/1407.md delete mode 100644 news/3 Code Health/1542.md delete mode 100644 news/3 Code Health/1545.md delete mode 100644 news/3 Code Health/1614.md delete mode 100644 news/3 Code Health/1766.md delete mode 100644 news/3 Code Health/1767.md delete mode 100644 news/3 Code Health/1770.md delete mode 100644 news/3 Code Health/1803.md delete mode 100644 news/3 Code Health/1815.md delete mode 100644 news/3 Code Health/1817.md delete mode 100644 news/3 Code Health/1821.md delete mode 100644 news/3 Code Health/1833.md delete mode 100644 news/3 Code Health/1842.md delete mode 100644 news/3 Code Health/1867.md delete mode 100644 news/3 Code Health/1885.md delete mode 100644 news/3 Code Health/1887.md delete mode 100644 news/3 Code Health/1893.md delete mode 100644 news/3 Code Health/1897.md delete mode 100644 news/3 Code Health/1918.md delete mode 100644 news/3 Code Health/1922.md delete mode 100644 news/3 Code Health/1953.md delete mode 100644 news/3 Code Health/1957.md delete mode 100644 news/3 Code Health/1968.md delete mode 100644 news/3 Code Health/256.md delete mode 100644 news/3 Code Health/932.md diff --git a/CHANGELOG.md b/CHANGELOG.md index f160bf492d86..75273b3ada27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2018.6.0-beta 11 June 2018) +## 2018.6.0 (20 June 2018) ### Thanks @@ -53,17 +53,39 @@ part of! ### Enhancements -1. Add setting for auto run test discovery on save, `python.unitTest.autoTestDiscoverOnSaveEnabled`. +1. Add setting to control automatic test discovery on save, `python.unitTest.autoTestDiscoverOnSaveEnabled`. (thanks [Lingyu Li](http://github.com/lingyv-li/)) ([#1037](https://github.com/Microsoft/vscode-python/issues/1037)) 1. Add `gevent` launch configuration option to enable debugging of gevent monkey patched code. + (thanks [Bence Nagy](https://github.com/underyx)) ([#127](https://github.com/Microsoft/vscode-python/issues/127)) +1. Add support for the `"source.organizeImports"` setting for `"editor.codeActionsOnSave"` (thanks [Nathan Gaberel](https://github.com/n6g7)); you can turn this on just for Python using: + ```json + "[python]": { + "editor.codeActionsOnSave": { + "source.organizeImports": true + } + } + ``` + ([#156](https://github.com/Microsoft/vscode-python/issues/156)) 1. Added Spanish translation. (thanks [Mario Rubio](https://github.com/mario-mra/)) ([#1902](https://github.com/Microsoft/vscode-python/issues/1902)) +1. Add a French translation (thanks to [Jérémy](https://github.com/PixiBixi) for + the initial patch, and thanks to [Nathan Gaberel](https://github.com/n6g7), + [Bruno Alla](https://github.com/browniebroke), and + [Tarek Ziade](https://github.com/tarekziade) for reviews). + ([#1959](https://github.com/Microsoft/vscode-python/issues/1959)) +1. Add syntax highlighting for [Pipenv](http://pipenv.readthedocs.io/en/latest/)-related + files (thanks [Nathan Gaberel](https://github.com/n6g7)). + ([#995](https://github.com/Microsoft/vscode-python/issues/995)) ### Fixes +1. Modified to change error message displayed when path to a tool (`linter`, `formatter`, etc) is invalid. + ([#1064](https://github.com/Microsoft/vscode-python/issues/1064)) +1. Improvements to the logic used to parse the arguments passed into the test frameworks. + ([#1070](https://github.com/Microsoft/vscode-python/issues/1070)) 1. Ensure navigation to definitons follows imports and is transparent to decoration. (thanks [Peter Law](https://github.com/PeterJCLaw)) ([#1638](https://github.com/Microsoft/vscode-python/issues/1638)) @@ -74,10 +96,14 @@ part of! 1. Fix to display all interpreters in the interpreter list when a workspace contains a `Pipfile`. ([#1800](https://github.com/Microsoft/vscode-python/issues/1800)) 1. Use file system API to perform file path comparisons when performing code navigation. - (thanks to [bstaint](https://github.com/bstaint) for the initial patch) + (thanks to [bstaint](https://github.com/bstaint) for the problem diagnosis) ([#1811](https://github.com/Microsoft/vscode-python/issues/1811)) 1. Automatically add path mappings for remote debugging when attaching to the localhost. ([#1829](https://github.com/Microsoft/vscode-python/issues/1829)) +1. Change keyboard shortcut for `Run Selection/Line in Python Terminal` to `Shit+Enter`. + ([#1875](https://github.com/Microsoft/vscode-python/issues/1875)) +1. Fix unhandled rejected promises in unit tests. + ([#1919](https://github.com/Microsoft/vscode-python/issues/1919)) 1. Fix debugger issue that causes the debugger to hang and silently exit stepping over a line of code instantiating an ITK vector object. ([#459](https://github.com/Microsoft/vscode-python/issues/459)) @@ -85,14 +111,22 @@ part of! 1. Add telemetry to capture type of python interpreter used in workspace. ([#1237](https://github.com/Microsoft/vscode-python/issues/1237)) +1. Enabled multi-thrreaded debugger tests for the `experimental` debugger. + ([#1250](https://github.com/Microsoft/vscode-python/issues/1250)) +1. Log relevant environment information when the existence of `pipenv` cannot be determined. + ([#1338](https://github.com/Microsoft/vscode-python/issues/1338)) 1. Use [dotenv](https://www.npmjs.com/package/dotenv) package to parse [environment variables definition files](https://code.visualstudio.com/docs/python/environments#_environment-variable-definitions-file). ([#1376](https://github.com/Microsoft/vscode-python/issues/1376)) 1. Move from yarn to npm. ([#1402](https://github.com/Microsoft/vscode-python/issues/1402)) +1. Fix django and flask debugger tests when using the experimental debugger. + ([#1407](https://github.com/Microsoft/vscode-python/issues/1407)) 1. Capture telemetry for the usage of the `Create Terminal` command along with other instances when a terminal is created implicitly. ([#1542](https://github.com/Microsoft/vscode-python/issues/1542)) 1. Add telemetry to capture availability of Python 3, version of Python used in workspace and the number of workspace folders. ([#1545](https://github.com/Microsoft/vscode-python/issues/1545)) +1. Ensure all CI tests (except for debugger) are no longer allowed to fail. + ([#1614](https://github.com/Microsoft/vscode-python/issues/1614)) 1. Capture telemetry for the usage of the feature that formats a line as you type (`editor.formatOnType`). ([#1766](https://github.com/Microsoft/vscode-python/issues/1766)) 1. Capture telemetry for the new debugger. @@ -113,7 +147,7 @@ part of! ([#1842](https://github.com/Microsoft/vscode-python/issues/1842)) 1. Add better exception handling when parsing responses received from the Jedi language service. ([#1867](https://github.com/Microsoft/vscode-python/issues/1867)) -1. Resolve warnings in CI Tests and fix some broken CI Tests. +1. Resolve warnings in CI Tests and fix some broken CI tests. ([#1885](https://github.com/Microsoft/vscode-python/issues/1885)) 1. Reduce sample count used to capture performance metrics in order to reduce time taken to complete the tests. ([#1887](https://github.com/Microsoft/vscode-python/issues/1887)) @@ -121,6 +155,16 @@ part of! ([#1893](https://github.com/Microsoft/vscode-python/issues/1893)) 1. Add JUnit file output to enable CI integration with VSTS. ([#1897](https://github.com/Microsoft/vscode-python/issues/1897)) +1. Log unhandled rejected promises when running unit tests. + ([#1918](https://github.com/Microsoft/vscode-python/issues/1918)) +1. Add ability to run tests without having to launch VS Code. + ([#1922](https://github.com/Microsoft/vscode-python/issues/1922)) +1. Fix rename refactoring unit tests. + ([#1953](https://github.com/Microsoft/vscode-python/issues/1953)) +1. Fix failing test on Mac when validating the path of a python interperter. + ([#1957](https://github.com/Microsoft/vscode-python/issues/1957)) +1. Display banner prompting user to complete a survey for the use of the `Experimental Debugger`. + ([#1968](https://github.com/Microsoft/vscode-python/issues/1968)) 1. Use a glob pattern to look for `conda` executables. ([#256](https://github.com/Microsoft/vscode-python/issues/256)) 1. Create tests to measure activation times for the extension. @@ -128,6 +172,8 @@ part of! + + ## 2018.5.0 (05 Jun 2018) Thanks to the following projects which we fully rely on to provide some of diff --git a/news/1 Enhancements/1037.md b/news/1 Enhancements/1037.md deleted file mode 100644 index f096c595b71f..000000000000 --- a/news/1 Enhancements/1037.md +++ /dev/null @@ -1,2 +0,0 @@ -Add setting for auto run test discover on save, `python.unitTest.autoTestDiscoverOnSaveEnabled`. -(thanks [Lingyu Li](http://github.com/lingyv-li/)) diff --git a/news/1 Enhancements/127.md b/news/1 Enhancements/127.md deleted file mode 100644 index 1bcda6fbb75d..000000000000 --- a/news/1 Enhancements/127.md +++ /dev/null @@ -1 +0,0 @@ -Add `gevent` launch configuration option to enable debugging of gevent monkey patched code. diff --git a/news/1 Enhancements/156.md b/news/1 Enhancements/156.md deleted file mode 100644 index c5c4258764b7..000000000000 --- a/news/1 Enhancements/156.md +++ /dev/null @@ -1,8 +0,0 @@ -Add support for the `"source.organizeImports"` setting for `"editor.codeActionsOnSave"` (thanks [Nathan Gaberel](https://github.com/n6g7)); you can turn this on just for Python using: -```json -"[python]": { - "editor.codeActionsOnSave": { - "source.organizeImports": true - } -} -``` diff --git a/news/1 Enhancements/1902.md b/news/1 Enhancements/1902.md deleted file mode 100644 index c9272a9ddebd..000000000000 --- a/news/1 Enhancements/1902.md +++ /dev/null @@ -1,2 +0,0 @@ -Added Spanish translation. -(thanks [Mario Rubio](https://github.com/mario-mra/)) diff --git a/news/1 Enhancements/1959.md b/news/1 Enhancements/1959.md deleted file mode 100644 index b24f3458ba05..000000000000 --- a/news/1 Enhancements/1959.md +++ /dev/null @@ -1,4 +0,0 @@ -Add a French translation (thanks to [Jérémy](https://github.com/PixiBixi) for -the initial patch, and thanks to [Nathan Gaberel](https://github.com/n6g7), -[Bruno Alla](https://github.com/browniebroke), and -[Tarek Ziade](https://github.com/tarekziade) for reviews). diff --git a/news/1 Enhancements/995.md b/news/1 Enhancements/995.md deleted file mode 100644 index 28e84ce78994..000000000000 --- a/news/1 Enhancements/995.md +++ /dev/null @@ -1 +0,0 @@ -Add syntax highlighting for [Pipenv](http://pipenv.readthedocs.io/en/latest/) files (thanks [Nathan Gaberel](https://github.com/n6g7)). diff --git a/news/2 Fixes/1064.md b/news/2 Fixes/1064.md deleted file mode 100644 index 389199f587a6..000000000000 --- a/news/2 Fixes/1064.md +++ /dev/null @@ -1 +0,0 @@ -Modified to change error message displayed when path to a tool (`linter`, `formatter`, etc) is invalid. diff --git a/news/2 Fixes/1070.md b/news/2 Fixes/1070.md deleted file mode 100644 index 5f9100ebc192..000000000000 --- a/news/2 Fixes/1070.md +++ /dev/null @@ -1 +0,0 @@ -Improvements to the logic used to parse the arguments passed into the test frameworks. diff --git a/news/2 Fixes/1638.md b/news/2 Fixes/1638.md deleted file mode 100644 index 82123fa1102c..000000000000 --- a/news/2 Fixes/1638.md +++ /dev/null @@ -1,2 +0,0 @@ -Ensure navigation to definitons follows imports and is transparent to decoration. -(thanks [Peter Law](https://github.com/PeterJCLaw)) diff --git a/news/2 Fixes/1721.md b/news/2 Fixes/1721.md deleted file mode 100644 index 8b733365272c..000000000000 --- a/news/2 Fixes/1721.md +++ /dev/null @@ -1 +0,0 @@ -Fix for intellisense failing when using the new `Outline` feature. diff --git a/news/2 Fixes/1759.md b/news/2 Fixes/1759.md deleted file mode 100644 index 4938502ae95f..000000000000 --- a/news/2 Fixes/1759.md +++ /dev/null @@ -1 +0,0 @@ -When debugging unit tests, use the `env` file configured in `settings.json` under `python.envFile`. diff --git a/news/2 Fixes/1800.md b/news/2 Fixes/1800.md deleted file mode 100644 index a4fb662e2cd3..000000000000 --- a/news/2 Fixes/1800.md +++ /dev/null @@ -1 +0,0 @@ -Fix to display all interpreters in the interpreter list when a workspace contains a `Pipfile`. diff --git a/news/2 Fixes/1811.md b/news/2 Fixes/1811.md deleted file mode 100644 index c33287129520..000000000000 --- a/news/2 Fixes/1811.md +++ /dev/null @@ -1,2 +0,0 @@ -Use file system API to perform file path comparisons when performing code navigation. -(thanks to [bstaint](https://github.com/bstaint) for the initial patch) diff --git a/news/2 Fixes/1829.md b/news/2 Fixes/1829.md deleted file mode 100644 index de0e75d4e4db..000000000000 --- a/news/2 Fixes/1829.md +++ /dev/null @@ -1 +0,0 @@ -Automatically add path mappings for remote debugging when attaching to the localhost. diff --git a/news/2 Fixes/1875.md b/news/2 Fixes/1875.md deleted file mode 100644 index d687dcf63a14..000000000000 --- a/news/2 Fixes/1875.md +++ /dev/null @@ -1 +0,0 @@ -Change keyboard shortcut for `Run Selection/Line in Python Terminal` to `Shit+Enter`. diff --git a/news/2 Fixes/1919.md b/news/2 Fixes/1919.md deleted file mode 100644 index bff5edbd4083..000000000000 --- a/news/2 Fixes/1919.md +++ /dev/null @@ -1 +0,0 @@ -Fix unhandled rejected promises in unit tests. diff --git a/news/2 Fixes/459.md b/news/2 Fixes/459.md deleted file mode 100644 index bba743f3c26f..000000000000 --- a/news/2 Fixes/459.md +++ /dev/null @@ -1 +0,0 @@ -Fix debugger issue that causes the debugger to hang and silently exit stepping over a line of code instantiating an ITK vector object. diff --git a/news/3 Code Health/1237.md b/news/3 Code Health/1237.md deleted file mode 100644 index 782e955854c6..000000000000 --- a/news/3 Code Health/1237.md +++ /dev/null @@ -1 +0,0 @@ -Add telemetry to capture type of python interpreter used in workspace. diff --git a/news/3 Code Health/1250.md b/news/3 Code Health/1250.md deleted file mode 100644 index f4eeb42a9892..000000000000 --- a/news/3 Code Health/1250.md +++ /dev/null @@ -1 +0,0 @@ -Enabled multi-thrreaded debugger tests for the `experimental` debugger. diff --git a/news/3 Code Health/1338.md b/news/3 Code Health/1338.md deleted file mode 100644 index 9846aecaa276..000000000000 --- a/news/3 Code Health/1338.md +++ /dev/null @@ -1 +0,0 @@ -Log relevant environment information when the existence of `pipenv` cannot be determined. diff --git a/news/3 Code Health/1376.md b/news/3 Code Health/1376.md deleted file mode 100644 index c884f9aa7a5c..000000000000 --- a/news/3 Code Health/1376.md +++ /dev/null @@ -1 +0,0 @@ -Use [dotenv](https://www.npmjs.com/package/dotenv) package to parse [environment variables definition files](https://code.visualstudio.com/docs/python/environments#_environment-variable-definitions-file). diff --git a/news/3 Code Health/1402.md b/news/3 Code Health/1402.md deleted file mode 100644 index 6e174acb433d..000000000000 --- a/news/3 Code Health/1402.md +++ /dev/null @@ -1 +0,0 @@ -Move from yarn to npm. diff --git a/news/3 Code Health/1407.md b/news/3 Code Health/1407.md deleted file mode 100644 index d61ba644abe2..000000000000 --- a/news/3 Code Health/1407.md +++ /dev/null @@ -1 +0,0 @@ -Fix django and flask debugger tests when using the `experimental` debugger. diff --git a/news/3 Code Health/1542.md b/news/3 Code Health/1542.md deleted file mode 100644 index 9421570bde59..000000000000 --- a/news/3 Code Health/1542.md +++ /dev/null @@ -1 +0,0 @@ -Capture telemetry for the usage of the `Create Terminal` command along with other instances when a terminal is created implicitly. diff --git a/news/3 Code Health/1545.md b/news/3 Code Health/1545.md deleted file mode 100644 index 58ecc563d484..000000000000 --- a/news/3 Code Health/1545.md +++ /dev/null @@ -1 +0,0 @@ -Add telemetry to capture availability of Python 3, version of Python used in workspace and the number of workspace folders. diff --git a/news/3 Code Health/1614.md b/news/3 Code Health/1614.md deleted file mode 100644 index 6a5ad111df25..000000000000 --- a/news/3 Code Health/1614.md +++ /dev/null @@ -1 +0,0 @@ -Ensure all CI tests (except for debugger) are no longer allowed to fail. diff --git a/news/3 Code Health/1766.md b/news/3 Code Health/1766.md deleted file mode 100644 index 6af23252b987..000000000000 --- a/news/3 Code Health/1766.md +++ /dev/null @@ -1 +0,0 @@ -Capture telemetry for the usage of the feature that formats a line as you type (`editor.formatOnType`). diff --git a/news/3 Code Health/1767.md b/news/3 Code Health/1767.md deleted file mode 100644 index b8bfe4bd2839..000000000000 --- a/news/3 Code Health/1767.md +++ /dev/null @@ -1 +0,0 @@ -Capture telemetry for the new debugger. diff --git a/news/3 Code Health/1770.md b/news/3 Code Health/1770.md deleted file mode 100644 index 0c7d35e39b22..000000000000 --- a/news/3 Code Health/1770.md +++ /dev/null @@ -1 +0,0 @@ -Capture telemetry for usage of the setting `python.autocomplete.addBrackets` diff --git a/news/3 Code Health/1803.md b/news/3 Code Health/1803.md deleted file mode 100644 index 9a272209b91e..000000000000 --- a/news/3 Code Health/1803.md +++ /dev/null @@ -1 +0,0 @@ -Speed up githook by skipping commits not containing any `.ts` files. diff --git a/news/3 Code Health/1815.md b/news/3 Code Health/1815.md deleted file mode 100644 index cd0a4f5d40e7..000000000000 --- a/news/3 Code Health/1815.md +++ /dev/null @@ -1 +0,0 @@ -Update typescript package to 2.9.1. diff --git a/news/3 Code Health/1817.md b/news/3 Code Health/1817.md deleted file mode 100644 index f50a3adb42e1..000000000000 --- a/news/3 Code Health/1817.md +++ /dev/null @@ -1 +0,0 @@ -Log Conda not existing message as an information instead of an error. diff --git a/news/3 Code Health/1821.md b/news/3 Code Health/1821.md deleted file mode 100644 index 2ef4abc844c2..000000000000 --- a/news/3 Code Health/1821.md +++ /dev/null @@ -1 +0,0 @@ -Make use of `ILogger` to log messages instead of using `console.error`. diff --git a/news/3 Code Health/1833.md b/news/3 Code Health/1833.md deleted file mode 100644 index e9a49948e14a..000000000000 --- a/news/3 Code Health/1833.md +++ /dev/null @@ -1 +0,0 @@ -Update `parso` package to 0.2.1. diff --git a/news/3 Code Health/1842.md b/news/3 Code Health/1842.md deleted file mode 100644 index f0ab0021f46d..000000000000 --- a/news/3 Code Health/1842.md +++ /dev/null @@ -1 +0,0 @@ -Update `isort` package to 4.3.4. diff --git a/news/3 Code Health/1867.md b/news/3 Code Health/1867.md deleted file mode 100644 index 57590fb445fb..000000000000 --- a/news/3 Code Health/1867.md +++ /dev/null @@ -1 +0,0 @@ -Add better exception handling when parsing responses received from the Jedi language service. diff --git a/news/3 Code Health/1885.md b/news/3 Code Health/1885.md deleted file mode 100644 index ed55aae702af..000000000000 --- a/news/3 Code Health/1885.md +++ /dev/null @@ -1 +0,0 @@ -Resolve warnings in CI Tests and fix some broken CI Tests. diff --git a/news/3 Code Health/1887.md b/news/3 Code Health/1887.md deleted file mode 100644 index f9e26d3bd5c4..000000000000 --- a/news/3 Code Health/1887.md +++ /dev/null @@ -1 +0,0 @@ -Reduce sample count used to capture performance metrics in order to reduce time taken to complete the tests. diff --git a/news/3 Code Health/1893.md b/news/3 Code Health/1893.md deleted file mode 100644 index 88cf91a7e1b0..000000000000 --- a/news/3 Code Health/1893.md +++ /dev/null @@ -1 +0,0 @@ -Ensure workspace information is passed into installer when determining whether a product/tool is installed. diff --git a/news/3 Code Health/1897.md b/news/3 Code Health/1897.md deleted file mode 100644 index 57d93b45447a..000000000000 --- a/news/3 Code Health/1897.md +++ /dev/null @@ -1 +0,0 @@ -Add JUnit file output to enable CI integration with VSTS. \ No newline at end of file diff --git a/news/3 Code Health/1918.md b/news/3 Code Health/1918.md deleted file mode 100644 index e49860ba62f6..000000000000 --- a/news/3 Code Health/1918.md +++ /dev/null @@ -1 +0,0 @@ -Log unhandled rejected promises when running unit tests. diff --git a/news/3 Code Health/1922.md b/news/3 Code Health/1922.md deleted file mode 100644 index ce836f31d3b9..000000000000 --- a/news/3 Code Health/1922.md +++ /dev/null @@ -1 +0,0 @@ -Add ability to run tests without having to launch VS Code. diff --git a/news/3 Code Health/1953.md b/news/3 Code Health/1953.md deleted file mode 100644 index 4749e1048d14..000000000000 --- a/news/3 Code Health/1953.md +++ /dev/null @@ -1 +0,0 @@ -Fix rename refactoring unit tests. diff --git a/news/3 Code Health/1957.md b/news/3 Code Health/1957.md deleted file mode 100644 index 9c638c49be95..000000000000 --- a/news/3 Code Health/1957.md +++ /dev/null @@ -1 +0,0 @@ -Fix failing test on Mac when validating the path of a python interperter. diff --git a/news/3 Code Health/1968.md b/news/3 Code Health/1968.md deleted file mode 100644 index b4a14a27dde1..000000000000 --- a/news/3 Code Health/1968.md +++ /dev/null @@ -1 +0,0 @@ -Display banner prompting user to complete a survey for the use of the `Experimental Debugger`. diff --git a/news/3 Code Health/256.md b/news/3 Code Health/256.md deleted file mode 100644 index 60e5ca2d1222..000000000000 --- a/news/3 Code Health/256.md +++ /dev/null @@ -1 +0,0 @@ -Use a glob pattern to look for `conda` executables. diff --git a/news/3 Code Health/932.md b/news/3 Code Health/932.md deleted file mode 100644 index 6e2f9c6ace41..000000000000 --- a/news/3 Code Health/932.md +++ /dev/null @@ -1 +0,0 @@ -Create tests to measure activation times for the extension. diff --git a/news/announce.py b/news/announce.py index f924f4189368..053b7071e13d 100644 --- a/news/announce.py +++ b/news/announce.py @@ -21,19 +21,20 @@ def NewsEntry(issue_number, description, path): """Construct a data object for a news entry.""" # TODO: replace with a dataclass in Python 3.7. - return types.SimpleNamespace(issue_number=issue_number, - description=description, path=path) + return types.SimpleNamespace( + issue_number=issue_number, description=description, path=path + ) def news_entries(directory): """Yield news entries in the directory.""" for path in directory.iterdir(): - if path.name == 'README.md': + if path.name == "README.md": continue match = FILENAME_RE.match(path.name) if match is None: - raise ValueError(f'{path} has a bad file name') - issue = int(match.group('issue')) + raise ValueError(f"{path} has a bad file name") + issue = int(match.group("issue")) entry = path.read_text("utf-8") yield NewsEntry(issue, entry, path) @@ -48,15 +49,17 @@ def sections(directory): """Yield the sections in their appropriate order.""" found = [] for path in directory.iterdir(): - if not path.is_dir() or path.name.startswith('.'): + if not path.is_dir() or path.name.startswith("."): continue - position, sep, title = path.name.partition(' ') + position, sep, title = path.name.partition(" ") if not sep: - print(f'directory name {path.name!r} is missing ranking; skipping', - file=sys.stderr) + print( + f"directory {path.name!r} is missing a ranking; skipping", + file=sys.stderr, + ) continue found.append(SectionTitle(int(position), title, path)) - return sorted(found, key=operator.attrgetter('index')) + return sorted(found, key=operator.attrgetter("index")) def gather(directory): @@ -70,17 +73,19 @@ def gather(directory): def entry_markdown(entry): """Generate the Markdown for the specified entry.""" enumerated_item = "1. " - indent = ' ' * len(enumerated_item) - issue_url = f'https://github.com/Microsoft/vscode-python/issues/{entry.issue_number}' - issue_md = f'([#{entry.issue_number}]({issue_url}))' + indent = " " * len(enumerated_item) + issue_url = ( + f"https://github.com/Microsoft/vscode-python/issues/{entry.issue_number}" + ) + issue_md = f"([#{entry.issue_number}]({issue_url}))" entry_lines = entry.description.strip().splitlines() - formatted_lines = [f'{enumerated_item}{entry_lines[0]}'] - formatted_lines.extend(f'{indent}{line}' for line in entry_lines[1:]) - formatted_lines.append(f'{indent}{issue_md}') - return '\n'.join(formatted_lines) - return ENTRY_TEMPLATE.format(entry=entry.description.strip(), - issue=entry.issue_number, - issue_url=issue_url) + formatted_lines = [f"{enumerated_item}{entry_lines[0]}"] + formatted_lines.extend(f"{indent}{line}" for line in entry_lines[1:]) + formatted_lines.append(f"{indent}{issue_md}") + return "\n".join(formatted_lines) + return ENTRY_TEMPLATE.format( + entry=entry.description.strip(), issue=entry.issue_number, issue_url=issue_url + ) def changelog_markdown(data): @@ -96,9 +101,12 @@ def changelog_markdown(data): def git_rm(path): """Run git-rm on the path.""" - status = subprocess.run(['git', 'rm', os.fspath(path.resolve())], - shell=True, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) + status = subprocess.run( + ["git", "rm", os.fspath(path.resolve())], + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) try: status.check_returncode() except Exception: @@ -126,12 +134,13 @@ def main(run_type, directory): data = gather(directory) markdown = changelog_markdown(data) if run_type != RunType.dry_run: + # XXX This can lead to mojibake; hopefully Python 3.7 will resolve this. print(markdown) if run_type == RunType.final: cleanup(data) -if __name__ == '__main__': +if __name__ == "__main__": arguments = docopt.docopt(__doc__) for possible_run_type in RunType: if arguments[f"--{possible_run_type.name}"]: diff --git a/package.json b/package.json index 4f6382ef049d..262d0b4149d7 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2018.6.0-beta", + "version": "2018.6.0", "publisher": "ms-python", "author": { "name": "Microsoft Corporation" From 452558788048bea94e25f187b4f7a5586ae02ed7 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Wed, 20 Jun 2018 12:35:01 -0700 Subject: [PATCH 364/433] Fix an embarassing typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75273b3ada27..41b4d96d5406 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -100,7 +100,7 @@ part of! ([#1811](https://github.com/Microsoft/vscode-python/issues/1811)) 1. Automatically add path mappings for remote debugging when attaching to the localhost. ([#1829](https://github.com/Microsoft/vscode-python/issues/1829)) -1. Change keyboard shortcut for `Run Selection/Line in Python Terminal` to `Shit+Enter`. +1. Change keyboard shortcut for `Run Selection/Line in Python Terminal` to `Shift+Enter`. ([#1875](https://github.com/Microsoft/vscode-python/issues/1875)) 1. Fix unhandled rejected promises in unit tests. ([#1919](https://github.com/Microsoft/vscode-python/issues/1919)) From 2d94f06b2ce70927b12a26c16a55fb30b27b1522 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Wed, 20 Jun 2018 14:42:19 -0700 Subject: [PATCH 365/433] Bump version to 2018.7.0-alpha --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 262d0b4149d7..53f16b8ed3d8 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2018.6.0", + "version": "2018.7.0-alpha", "publisher": "ms-python", "author": { "name": "Microsoft Corporation" From 938772e2f572ac3f2ac0593a2f5ff5e3139d9a45 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Wed, 20 Jun 2018 14:46:03 -0700 Subject: [PATCH 366/433] Tweak release process to minimize overhead in a hotfix release --- .github/release_plan.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/release_plan.md b/.github/release_plan.md index 249483bb52dd..927d060d073b 100644 --- a/.github/release_plan.md +++ b/.github/release_plan.md @@ -70,12 +70,13 @@ - [ ] Update the [changelog](https://github.com/Microsoft/vscode-python/blob/master/CHANGELOG.md) (including the names of external contributors & projects) - [ ] Update the [version](https://github.com/Microsoft/vscode-python/blob/master/package.json) number to be final - [ ] Make sure [CI](https://github.com/Microsoft/vscode-python/blob/master/CONTRIBUTING.md) is passing -- [ ] Create the `release-` [branch](https://github.com/Microsoft/vscode-python/) -- [ ] Generate final `.vsix` file from the `release-` branch +- [ ] Generate the final `.vsix` file - [ ] Upload the final `.vsix` file to the [marketplace](https://marketplace.visualstudio.com/items?itemName=ms-python.python) - [ ] Publish [documentation](https://code.visualstudio.com/docs/python/python-tutorial) [changes](https://github.com/microsoft/vscode-docs/pulls) - [ ] Publish the [blog](http://aka.ms/pythonblog) post - [ ] Create a [release](https://github.com/Microsoft/vscode-python/releases) on GitHub (which creates an appropriate git tag) +- [ ] Determine if a hotfix is needed +- [ ] Create the `release-` [branch](https://github.com/Microsoft/vscode-python/) ## Prep for the _next_ release - [ ] Bump the [version](https://github.com/Microsoft/vscode-python/blob/master/package.json) number to the next `alpha` From ac0877496e4703117cd0f7dcb2ea85228cff514a Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 21 Jun 2018 11:24:07 -0700 Subject: [PATCH 367/433] Fix captialization --- package.nls.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.nls.json b/package.nls.json index 23adf86f4e0b..333a94791381 100644 --- a/package.nls.json +++ b/package.nls.json @@ -24,7 +24,7 @@ "python.command.python.enableLinting.title": "Enable Linting", "python.command.python.runLinting.title": "Run Linting", "python.snippet.launch.standard.label": "Python: Current File", - "python.snippet.launch.standard.description": "Debug a Python program with standard output", + "python.snippet.launch.standard.description": "Debug a Python Program with Standard Output", "python.snippet.launch.pyspark.label": "Python: PySpark", "python.snippet.launch.pyspark.description": "Debug PySpark", "python.snippet.launch.module.label": "Python: Module", @@ -46,7 +46,7 @@ "python.snippet.launch.watson.label": "Python: Watson Application", "python.snippet.launch.watson.description": "Debug a Watson Application", "python.snippet.launch.attach.label": "Python: Attach", - "python.snippet.launch.attach.description": "Attach the debugger for remote debugging", + "python.snippet.launch.attach.description": "Attach the Debugger for Remote Debugging", "python.snippet.launch.scrapy.label": "Python: Scrapy", "python.snippet.launch.scrapy.description": "Scrapy with Integrated Terminal/Console" } From f41c40608cc8e3280adae17ce5feeb93fc2714a1 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 21 Jun 2018 12:19:50 -0700 Subject: [PATCH 368/433] Remove unit tests as being run as part of the pre-commit hook (#2032) --- gulpfile.js | 9 +-------- news/3 Code Health/1986.md | 1 + 2 files changed, 2 insertions(+), 8 deletions(-) create mode 100644 news/3 Code Health/1986.md diff --git a/gulpfile.js b/gulpfile.js index f953df251b53..c62ff6311d77 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -516,12 +516,5 @@ exports.hygiene = hygiene; // this allows us to run hygiene as a git pre-commit hook. if (require.main === module) { - const result = run({ exitOnError: true, mode: 'staged' }); - // Run unit tests and ensure they pass as well. - if (result && result.on) { - result.on('end', () => { - const main = require('./out/test/unittests'); - main.runTests(); - }) - } + run({ exitOnError: true, mode: 'staged' }); } diff --git a/news/3 Code Health/1986.md b/news/3 Code Health/1986.md new file mode 100644 index 000000000000..e20a4c024e14 --- /dev/null +++ b/news/3 Code Health/1986.md @@ -0,0 +1 @@ +Removed pre-commit hook that ran unit tests. From 6b51f2f18a5185bf9f9a0dabb4b3e2cef3a6967c Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 21 Jun 2018 15:22:49 -0700 Subject: [PATCH 369/433] Update development process --- CONTRIBUTING.md | 62 ++++++++++++++++++++----------------------------- 1 file changed, 25 insertions(+), 37 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eb92cc799572..b4f7be215b45 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -97,33 +97,37 @@ person to do (long term this project aims to automate as much of the development process as possible). The current issues being worked on for a release are tracked in a [milestone](https://github.com/Microsoft/vscode-python/milestones) -(which is actively updated as plans change). +(which is actively updated as plans change). All +[P0](https://github.com/Microsoft/vscode-python/labels/P0) are expected to +be fixed in a milestone, else the release will be blocked. +[P1](https://github.com/Microsoft/vscode-python/labels/P1) issues are a +top-priority in a milestone, but if they are not completed they will not +block a milestone. All other issues are considered best-effort for that +milestone. The overall schedule for a release is to feature freeze for on the last Monday of the month to coincide with Visual Studio Code's code freeze. We then aim to release later that week so the latest version of the -extension is already live by the time Visual Studio Code launches -their new release. This is so we are ready to use any new features +extension is already live by the time Visual Studio Code does their +release the following week. This is so we are ready to use any new features of Visual Studio Code the day they go live. We do bugfix-only releases -between scheduled releases as necessary. +between scheduled releases as necessary, but otherwise we aim to do one +release a month. All development is actively done in the `master` branch of the -repository. It is what allows us to have an -[insiders build](#insiders-build) which is expected to be stable at +repository. It is what allows us to have a +[development build](#development-build) which is expected to be stable at all times. We do keep the most recent release as a branch in case the need for a bugfix release arises. But once a new release is made we delete the older release branch (all releases are appropriately -tagged, so history is lost). +tagged, so no history is lost). ### Issue triaging -To help actively track what stage issues are at, various labels are -used. Which labels are expected to be set vary from when an issue is -open to when an issue is closed. - -When an -[issue is first opened](https://github.com/Microsoft/vscode-python/issues), -it is triaged to contain at least two types of labels: +To help actively track what stage +[issues](https://github.com/Microsoft/vscode-python/issues) +are at, various labels are used. The following label types are expected to +be set on all open issues (otherwise the issue is not considered triaged): 1. `needs` 1. `feature` @@ -137,32 +141,16 @@ the issue, and what kind of issue it is. When an issue is closed by a pull request we add a [`validate fix`](https://github.com/Microsoft/vscode-python/issues?q=label%3A%22validate+fix%22+is%3Aclosed) label in order to request people help us test the fix to validate the issue was -resolved successfully. +resolved successfully. Once the fix has been manually validated we remove the label. ### Pull request workflow -1. Check that there is an issue corresponding to what the pull request - is attempting to address - * If an issue exists, make sure it has reached the stage of - `awaiting 2-PR` - * If no issue exists, open one and wait for it to reach the - `awaiting 2-PR` stage before submitting the pull request -1. Create the pull request, mentioning the appropriate issue(s) in the - pull request message body - * The pull request is expected to have appropriate unit tests - * The pull request must pass its CI run before merging will be - considered - * Code coverage is expected to (at minimum) not worsen - * A [news entry file](https://github.com/Microsoft/vscode-python/tree/master/news) (as appropriate) -1. Make sure all status checks are green (e.g. CLA check, CI, etc.) -1. Address any review comments -1. [Maintainers only] Merge the pull request -1. [Maintainers only] Update affected issues: - 1. Add the [`validate fix`](https://github.com/Microsoft/vscode-python/issues?q=label%3A%22validate+fix%22+is%3Aclosed) - label - 1. The issue(s) are attached to the current milestone - 1. Register OSS usage - 1. Email CELA about any 3rd-party usage changes +Key details that all pull requests are expected to handle should be +in the PR template. The only key detail not covered in that template is +that any change in our dependencies must be properly reflected in our +third-party notices file and registered with the OSPO internally at +Microsoft (obviously external developers do not need to concern themselves +with these legal/technical issues). ### Versioning From d202d9754247cd4cf3e266b72951ceff84e76d99 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 21 Jun 2018 15:24:23 -0700 Subject: [PATCH 370/433] Drop AppVeyor label --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b4f7be215b45..3e5d64e27cea 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,7 +1,7 @@ # Contributing to the Python extension for Visual Studio Code -[![Build Status (Travis)](https://travis-ci.org/Microsoft/vscode-python.svg?branch=master)](https://travis-ci.org/Microsoft/vscode-python) [![Build status (AppVeyor)](https://ci.appveyor.com/api/projects/status/s0pt8d79gqw222j7?svg=true)](https://ci.appveyor.com/project/DonJayamanne/vscode-python-v3vd6) [![codecov](https://codecov.io/gh/Microsoft/vscode-python/branch/master/graph/badge.svg)](https://codecov.io/gh/Microsoft/vscode-python) +[![Build Status (Travis)](https://travis-ci.org/Microsoft/vscode-python.svg?branch=master)](https://travis-ci.org/Microsoft/vscode-python) [![codecov](https://codecov.io/gh/Microsoft/vscode-python/branch/master/graph/badge.svg)](https://codecov.io/gh/Microsoft/vscode-python) --- From df385667738ff51e597f353b219a9f9dfd9fb61b Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 21 Jun 2018 15:28:51 -0700 Subject: [PATCH 371/433] Fix VSTS links --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3e5d64e27cea..a7337eaedb61 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ | VSCode-Python-CI | VSCode-Python-Rolling-CI | VSCode-Python-ptvsd_master-CI | |-|-|-| -|[![Build status](https://vscode-python.visualstudio.com/VSCode-Python/_apis/build/status/VSCode-Python-CI)](https://vscode-python.visualstudio.com/VSCode-Python/_apis/build/latest/VSCode-Python-CI) | [![vsts](https://vscode-python.visualstudio.com/VSCode-Python/_apis/build/status/VSCode-Python-Rolling-CI)](https://vscode-python.visualstudio.com/VSCode-Python/_apis/build/latest/VSCode-Python-Rolling-CI) | [![Build status](https://vscode-python.visualstudio.com/VSCode-Python/_apis/build/status/VSCode-Python-ptvsd_master-CI)](https://vscode-python.visualstudio.com/VSCode-Python/_apis/build/latest/VSCode-Python-ptvsd_master-CI)| +|[![Build status - CI](https://vscode-python.visualstudio.com/VSCode-Python/_apis/build/status/VSCode-Python-CI)](https://vscode-python.visualstudio.com/VSCode-Python/VSCode-Python%20Team/_build/results?buildId=375&view=logs) | [![Build status - Rolling](https://vscode-python.visualstudio.com/VSCode-Python/_apis/build/status/VSCode-Python-Rolling-CI)](https://vscode-python.visualstudio.com/3dc2a9b3-d195-4dba-9886-844383409c6c/_build/index?buildId=378) | [![Build status - PTVSD](https://vscode-python.visualstudio.com/VSCode-Python/_apis/build/status/VSCode-Python-ptvsd_master-CI)](https://vscode-python.visualstudio.com/3dc2a9b3-d195-4dba-9886-844383409c6c/_build/index?buildId=361)| --- # Contributing to Microsoft Python Analysis Engine From 95b824e5a89a7a92c6c3103513fcaed2fc420194 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 21 Jun 2018 15:33:46 -0700 Subject: [PATCH 372/433] Touch up badge table --- CONTRIBUTING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a7337eaedb61..c77a0299e5e4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,13 +1,13 @@ # Contributing to the Python extension for Visual Studio Code -[![Build Status (Travis)](https://travis-ci.org/Microsoft/vscode-python.svg?branch=master)](https://travis-ci.org/Microsoft/vscode-python) [![codecov](https://codecov.io/gh/Microsoft/vscode-python/branch/master/graph/badge.svg)](https://codecov.io/gh/Microsoft/vscode-python) + --- -| VSCode-Python-CI | VSCode-Python-Rolling-CI | VSCode-Python-ptvsd_master-CI | -|-|-|-| -|[![Build status - CI](https://vscode-python.visualstudio.com/VSCode-Python/_apis/build/status/VSCode-Python-CI)](https://vscode-python.visualstudio.com/VSCode-Python/VSCode-Python%20Team/_build/results?buildId=375&view=logs) | [![Build status - Rolling](https://vscode-python.visualstudio.com/VSCode-Python/_apis/build/status/VSCode-Python-Rolling-CI)](https://vscode-python.visualstudio.com/3dc2a9b3-d195-4dba-9886-844383409c6c/_build/index?buildId=378) | [![Build status - PTVSD](https://vscode-python.visualstudio.com/VSCode-Python/_apis/build/status/VSCode-Python-ptvsd_master-CI)](https://vscode-python.visualstudio.com/3dc2a9b3-d195-4dba-9886-844383409c6c/_build/index?buildId=361)| +| macOS/Windows CI | Linux CI | Rolling CI | ptvsd master CI | Code Coverage | +|-|-|-|-|-| +|[![Build status - CI](https://vscode-python.visualstudio.com/VSCode-Python/_apis/build/status/VSCode-Python-CI)](https://vscode-python.visualstudio.com/VSCode-Python/VSCode-Python%20Team/_build/results?buildId=375&view=logs) | [![Build Status (Travis)](https://travis-ci.org/Microsoft/vscode-python.svg?branch=master)](https://travis-ci.org/Microsoft/vscode-python) | [![Build status - Rolling](https://vscode-python.visualstudio.com/VSCode-Python/_apis/build/status/VSCode-Python-Rolling-CI)](https://vscode-python.visualstudio.com/3dc2a9b3-d195-4dba-9886-844383409c6c/_build/index?buildId=378) | [![Build status - PTVSD](https://vscode-python.visualstudio.com/VSCode-Python/_apis/build/status/VSCode-Python-ptvsd_master-CI)](https://vscode-python.visualstudio.com/3dc2a9b3-d195-4dba-9886-844383409c6c/_build/index?buildId=361) | [![codecov](https://codecov.io/gh/Microsoft/vscode-python/branch/master/graph/badge.svg)](https://codecov.io/gh/Microsoft/vscode-python)| --- # Contributing to Microsoft Python Analysis Engine From a2c4aa394c573a0dcd1c398d0d67fe30bc9e204f Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Thu, 21 Jun 2018 16:14:44 -0700 Subject: [PATCH 373/433] Always display dunder variables when using new language server (#2033) Fixes #2013 --- news/2 Fixes/2013.md | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 news/2 Fixes/2013.md diff --git a/news/2 Fixes/2013.md b/news/2 Fixes/2013.md new file mode 100644 index 000000000000..4524ae66a0ea --- /dev/null +++ b/news/2 Fixes/2013.md @@ -0,0 +1 @@ +Ensure dunder variables are always displayed in code completion when using the new language server. diff --git a/package.json b/package.json index 53f16b8ed3d8..e57204baa6d5 100644 --- a/package.json +++ b/package.json @@ -1169,7 +1169,7 @@ }, "python.autoComplete.showAdvancedMembers": { "type": "boolean", - "default": false, + "default": true, "description": "Controls appearance of methods with double underscores in the completion list.", "scope": "resource" }, From 24bdfb9b19d29d4410209d5626d62a4afe0dda77 Mon Sep 17 00:00:00 2001 From: Derek Keeler Date: Fri, 22 Jun 2018 12:46:01 -0700 Subject: [PATCH 374/433] Add VSTS CI badge to our package.json manifest (#2042) --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index e57204baa6d5..3c05fae17163 100644 --- a/package.json +++ b/package.json @@ -18,16 +18,16 @@ }, "qna": "https://stackoverflow.com/questions/tagged/visual-studio-code+python", "badges": [ + { + "url": "https://vscode-python.visualstudio.com/VSCode-Python/_apis/build/status/VSCode-Python-Rolling-CI?branchName=master", + "href": "https://vscode-python.visualstudio.com/VSCode-Python/VSCode-Python%20Team/_build/index?context=allDefinitions&path=&definitionId=9", + "description": "Continuous integration (VSTS)" + }, { "url": "https://travis-ci.org/Microsoft/vscode-python.svg?branch=master", "href": "https://travis-ci.org/Microsoft/vscode-python", "description": "Continuous integration (Travis)" }, - { - "url": "https://ci.appveyor.com/api/projects/status/s0pt8d79gqw222j7?svg=true", - "href": "https://ci.appveyor.com/project/DonJayamanne/vscode-python-v3vd6/branch/master", - "description": "Continuous integration (AppVeyor)" - }, { "url": "https://codecov.io/gh/Microsoft/vscode-python/branch/master/graph/badge.svg", "href": "https://codecov.io/gh/Microsoft/vscode-python", From 13bfaa2414655dacfe1d09911c6d72755556f68f Mon Sep 17 00:00:00 2001 From: Derek Keeler Date: Fri, 22 Jun 2018 13:33:08 -0700 Subject: [PATCH 375/433] Improve reporting in VSTS CI (#2051) - extend functionality of mocha-junit-reporter - produce console output for VSTS to report --- .mocha-reporter/mocha-vsts-reporter.js | 63 ++++++++++++++++++++++++++ package.json | 1 + src/test/ciConstants.ts | 26 +++++++++++ src/test/constants.ts | 26 ++++------- src/test/index.ts | 5 +- src/test/initialize.ts | 2 +- src/test/unittests.ts | 26 ++++++++++- 7 files changed, 128 insertions(+), 21 deletions(-) create mode 100644 .mocha-reporter/mocha-vsts-reporter.js create mode 100644 src/test/ciConstants.ts diff --git a/.mocha-reporter/mocha-vsts-reporter.js b/.mocha-reporter/mocha-vsts-reporter.js new file mode 100644 index 000000000000..8bfdc15670d3 --- /dev/null +++ b/.mocha-reporter/mocha-vsts-reporter.js @@ -0,0 +1,63 @@ +'use-strict'; + +var mocha = require('mocha'); +var MochaJUnitReporter = require('mocha-junit-reporter'); +module.exports = MochaVstsReporter; + +function MochaVstsReporter(runner, options) { + MochaJUnitReporter.call(this, runner, options); + var INDENT_BASE = ' '; + var indenter = ''; + var indentLevel = 0; + var passes = 0; + var failures = 0; + var skipped = 0; + + runner.on('suite', function(suite){ + if (suite.root === true){ + console.log('Begin test run.............'); + indentLevel++; + indenter = INDENT_BASE.repeat(indentLevel); + } else { + console.log('%sStart "%s"', indenter, suite.title); + indentLevel++; + indenter = INDENT_BASE.repeat(indentLevel); + } + }); + + runner.on('suite end', function(suite){ + if (suite.root === true) { + indentLevel=0; + indenter = ''; + console.log('.............End test run.'); + } else { + console.log('%sEnd "%s"', indenter, suite.title); + indentLevel--; + indenter = INDENT_BASE.repeat(indentLevel); + // ##vso[task.setprogress]current operation + } + }); + + runner.on('pass', function(test){ + passes++; + console.log('%s✓ %s (%dms)', indenter, test.title, test.duration); + }); + + runner.on('pending', function(test){ + skipped++; + console.log('%s- %s', indenter, test.title); + console.log('##vso[task.logissue type=warning;sourcepath=%s;]SKIPPED TEST %s :: %s', test.file, test.parent.title, test.title); + }); + + runner.on('fail', function(test, err){ + failures++; + console.log('%s✖ %s -- error: %s', indenter, test.title, err.message); + console.log('##vso[task.logissue type=warning;sourcepath=%s;]SKIPPED TEST %s :: %s', test.file, test.parent.title, test.title); + }); + + runner.on('end', function(){ + console.log('SUMMARY: %d/%d passed, %d skipped', passes, passes + failures, skipped); + }); +} + +mocha.utils.inherits(MochaVstsReporter, MochaJUnitReporter); diff --git a/package.json b/package.json index 3c05fae17163..145fc4b9b4c8 100644 --- a/package.json +++ b/package.json @@ -1999,6 +1999,7 @@ "typemoq": "^2.1.0", "typescript": "^2.9.1", "typescript-formatter": "^7.1.0", + "uuid": "^3.2.1", "vscode": "^1.1.5", "vscode-debugadapter-testsupport": "^1.27.0" }, diff --git a/src/test/ciConstants.ts b/src/test/ciConstants.ts new file mode 100644 index 000000000000..083c4bbf0aa9 --- /dev/null +++ b/src/test/ciConstants.ts @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// +// Constants that pertain to CI processes/tests only. No dependencies on vscode! +// + +export const IS_APPVEYOR = process.env.APPVEYOR === 'true'; +export const IS_TRAVIS = process.env.TRAVIS === 'true'; +export const IS_VSTS = process.env.TF_BUILD !== undefined; +export const IS_CI_SERVER = IS_TRAVIS || IS_APPVEYOR || IS_VSTS; + +// Control JUnit-style output logging for reporting purposes. +let reportJunit: boolean = false; +if (IS_CI_SERVER && process.env.MOCHA_REPORTER_JUNIT !== undefined) { + reportJunit = process.env.MOCHA_REPORTER_JUNIT.toLowerCase() === 'true'; +} +export const MOCHA_REPORTER_JUNIT: boolean = reportJunit; +export const MOCHA_CI_REPORTFILE: string = MOCHA_REPORTER_JUNIT && process.env.MOCHA_CI_REPORTFILE !== undefined ? + process.env.MOCHA_CI_REPORTFILE : './junit-out.xml'; +export const MOCHA_CI_PROPERTIES: string = MOCHA_REPORTER_JUNIT && process.env.MOCHA_CI_PROPERTIES !== undefined ? + process.env.MOCHA_CI_PROPERTIES : ''; + +export const IS_CI_SERVER_TEST_DEBUGGER = process.env.IS_CI_SERVER_TEST_DEBUGGER === '1'; diff --git a/src/test/constants.ts b/src/test/constants.ts index d66b46013b8a..5ce47d49001d 100644 --- a/src/test/constants.ts +++ b/src/test/constants.ts @@ -3,29 +3,21 @@ import { workspace } from 'vscode'; import { PythonSettings } from '../client/common/configSettings'; - -export const IS_APPVEYOR = process.env.APPVEYOR === 'true'; -export const IS_TRAVIS = process.env.TRAVIS === 'true'; -export const IS_VSTS = process.env.TF_BUILD !== undefined; -export const IS_CI_SERVER = IS_TRAVIS || IS_APPVEYOR || IS_VSTS; - -// allow the CI server to specify JUnit output... -let reportJunit: boolean = false; -if (IS_CI_SERVER && process.env.MOCHA_REPORTER_JUNIT !== undefined) { - reportJunit = process.env.MOCHA_REPORTER_JUNIT.toLowerCase() === 'true'; -} -export const MOCHA_REPORTER_JUNIT: boolean = reportJunit; -export const MOCHA_CI_REPORTFILE: string = MOCHA_REPORTER_JUNIT && process.env.MOCHA_CI_REPORTFILE !== undefined ? - process.env.MOCHA_CI_REPORTFILE : './junit-out.xml'; -export const MOCHA_CI_PROPERTIES: string = MOCHA_REPORTER_JUNIT && process.env.MOCHA_CI_PROPERTIES !== undefined ? - process.env.MOCHA_CI_PROPERTIES : ''; +// import { IS_APPVEYOR, IS_CI_SERVER, IS_CI_SERVER_TEST_DEBUGGER, +// IS_TRAVIS, IS_VSTS, MOCHA_CI_PROPERTIES, MOCHA_CI_REPORTFILE, +// MOCHA_REPORTER_JUNIT } from './ciConstants'; +import { IS_CI_SERVER, IS_CI_SERVER_TEST_DEBUGGER, IS_TRAVIS } from './ciConstants'; export const TEST_TIMEOUT = 25000; export const IS_MULTI_ROOT_TEST = isMultitrootTest(); -export const IS_CI_SERVER_TEST_DEBUGGER = process.env.IS_CI_SERVER_TEST_DEBUGGER === '1'; + // If running on CI server, then run debugger tests ONLY if the corresponding flag is enabled. export const TEST_DEBUGGER = IS_CI_SERVER ? IS_CI_SERVER_TEST_DEBUGGER : true; +// export { IS_APPVEYOR, IS_CI_SERVER, IS_CI_SERVER_TEST_DEBUGGER, +// IS_TRAVIS, IS_VSTS, MOCHA_CI_PROPERTIES, MOCHA_CI_REPORTFILE, +// MOCHA_REPORTER_JUNIT }; + function isMultitrootTest() { return Array.isArray(workspace.workspaceFolders) && workspace.workspaceFolders.length > 1; } diff --git a/src/test/index.ts b/src/test/index.ts index 58eb65ffd46c..a647b50e44f7 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -6,9 +6,10 @@ if ((Reflect as any).metadata === undefined) { import { IS_CI_SERVER, IS_CI_SERVER_TEST_DEBUGGER, - IS_MULTI_ROOT_TEST, IS_VSTS, MOCHA_CI_PROPERTIES, + IS_VSTS, MOCHA_CI_PROPERTIES, MOCHA_CI_REPORTFILE, MOCHA_REPORTER_JUNIT -} from './constants'; +} from './ciConstants'; +import { IS_MULTI_ROOT_TEST } from './constants'; import * as testRunner from './testRunner'; process.env.VSC_PYTHON_CI_TEST = '1'; diff --git a/src/test/initialize.ts b/src/test/initialize.ts index edaa7324f6ce..ff764d7c917e 100644 --- a/src/test/initialize.ts +++ b/src/test/initialize.ts @@ -1,6 +1,5 @@ // tslint:disable:no-string-literal -import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; import { PythonSettings } from '../client/common/configSettings'; @@ -8,6 +7,7 @@ import { activated } from '../client/extension'; import { clearPythonPathInWorkspaceFolder, PYTHON_PATH, resetGlobalPythonPathSetting, setPythonPathInWorkspaceRoot } from './common'; export * from './constants'; +export * from './ciConstants'; const dummyPythonFile = path.join(__dirname, '..', '..', 'src', 'test', 'pythonFiles', 'dummy.py'); const multirootPath = path.join(__dirname, '..', '..', 'src', 'testMultiRootWkspc'); diff --git a/src/test/unittests.ts b/src/test/unittests.ts index 1b6a6b6f0f6d..bce375e5787d 100644 --- a/src/test/unittests.ts +++ b/src/test/unittests.ts @@ -12,6 +12,7 @@ import * as glob from 'glob'; import * as Mocha from 'mocha'; import * as path from 'path'; import { MochaSetupOptions } from 'vscode/lib/testrunner'; +import { MOCHA_CI_REPORTFILE, MOCHA_REPORTER_JUNIT } from './ciConstants'; import * as vscodeMoscks from './vscode-mock'; export function runTests(testOptions?: { grep?: string; timeout?: number }) { @@ -19,13 +20,36 @@ export function runTests(testOptions?: { grep?: string; timeout?: number }) { const grep: string | undefined = testOptions ? testOptions.grep : undefined; const timeout: number | undefined = testOptions ? testOptions.timeout : undefined; + const options: MochaSetupOptions = { ui: 'tdd', useColors: true, timeout, grep }; - const mocha = new Mocha(options); + + let temp_mocha: Mocha | undefined; + + if (MOCHA_REPORTER_JUNIT === true) { + temp_mocha = new Mocha({ + grep: undefined, + ui: 'tdd', + timeout, + reporter: '../../../.mocha-reporter/mocha-vsts-reporter.js', + reporterOptions: { + useColors: false, + mochaFile: MOCHA_CI_REPORTFILE, + bail: false + }, + slow: undefined + }); + } else { + // we are running on the command line or debugger... + temp_mocha = new Mocha(options); + } + + const mocha: Mocha = temp_mocha; + require('source-map-support').install(); const testsRoot = __dirname; glob('**/**.unit.test.js', { cwd: testsRoot }, (error, files) => { From 13414a829a766f539333c9e84dabd28a40eaefa3 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Mon, 25 Jun 2018 11:55:22 -0700 Subject: [PATCH 376/433] Add typeshed submodule and pass it down to LS (#2043) * LS symbol providers * Typeshed paths * Typeshed submodule * Add submodule --- .gitmodules | 4 ++++ package.json | 9 +++++++++ src/client/activation/analysis.ts | 19 ++++++++++++++----- src/client/common/configSettings.ts | 3 ++- src/client/common/types.ts | 2 ++ typeshed | 1 + 6 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 .gitmodules create mode 160000 typeshed diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000000..a57bae098756 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "typeshed"] + path = typeshed + url = https://github.com/python/typeshed.git + branch = master \ No newline at end of file diff --git a/package.json b/package.json index 145fc4b9b4c8..d09a1617fca7 100644 --- a/package.json +++ b/package.json @@ -1173,6 +1173,15 @@ "description": "Controls appearance of methods with double underscores in the completion list.", "scope": "resource" }, + "python.autoComplete.typeshedPaths": { + "type": "array", + "items": { + "type": "string" + }, + "default": [], + "description": "Specifies paths to local typeshed repository clone(s) for the Python language server.", + "scope": "resource" + }, "python.disableInstallationCheck": { "type": "boolean", "default": false, diff --git a/src/client/activation/analysis.ts b/src/client/activation/analysis.ts index 536530d74050..01d2e911e902 100644 --- a/src/client/activation/analysis.ts +++ b/src/client/activation/analysis.ts @@ -190,7 +190,7 @@ export class AnalysisExtensionActivator implements IExtensionActivator { const interpreterDataService = new InterpreterDataService(this.context, this.services); interpreterData = await interpreterDataService.getInterpreterData(); } catch (ex) { - this.appShell.showErrorMessage('Unable to determine path to the Python interpreter. IntelliSense will be limited.'); + this.appShell.showWarningMessage('Unable to determine path to the Python interpreter. IntelliSense will be limited.'); } this.interpreterHash = interpreterData ? interpreterData.hash : ''; @@ -204,13 +204,16 @@ export class AnalysisExtensionActivator implements IExtensionActivator { properties['PrefixPath'] = interpreterData.prefix; } - let searchPaths = interpreterData ? interpreterData.searchPaths : ''; + let searchPathsString = interpreterData ? interpreterData.searchPaths : ''; + let typeshedPaths: string[] = []; + const settings = this.configuration.getSettings(); if (settings.autoComplete) { const extraPaths = settings.autoComplete.extraPaths; if (extraPaths && extraPaths.length > 0) { - searchPaths = `${searchPaths};${extraPaths.join(';')}`; + searchPathsString = `${searchPathsString};${extraPaths.join(';')}`; } + typeshedPaths = settings.autoComplete.typeshedPaths; } // tslint:disable-next-line:no-string-literal @@ -219,9 +222,13 @@ export class AnalysisExtensionActivator implements IExtensionActivator { // Make sure paths do not contain multiple slashes so file URIs // in VS Code (Node.js) and in the language server (.NET) match. // Note: for the language server paths separator is always ; - searchPaths = searchPaths.split(path.delimiter).map(p => path.normalize(p)).join(';'); + const searchPaths = searchPathsString.split(path.delimiter).map(p => path.normalize(p)); // tslint:disable-next-line:no-string-literal - properties['SearchPaths'] = `${searchPaths};${pythonPath}`; + properties['SearchPaths'] = `${searchPaths.join(';')};${pythonPath}`; + + if (!typeshedPaths || typeshedPaths.length === 0) { + typeshedPaths = [path.join(this.context.extensionPath, 'typeshed')]; + } const selector = [{ language: PYTHON, scheme: 'file' }]; const excludeFiles = this.getExcludedFiles(); @@ -245,6 +252,8 @@ export class AnalysisExtensionActivator implements IExtensionActivator { trimDocumentationText: false, maxDocumentationTextLength: 0 }, + searchPaths, + typeStubSearchPaths: typeshedPaths, asyncStartup: true, excludeFiles: excludeFiles, testEnvironment: isTestExecution() diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index d15bfefee183..4c55c4a2b3a3 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -233,7 +233,8 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { extraPaths: [], addBrackets: false, preloadModules: [], - showAdvancedMembers: false + showAdvancedMembers: false, + typeshedPaths: [] }; // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 733f0ffbee84..c671ae4e3bd5 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -221,6 +221,7 @@ export interface IAutoCompleteSettings { readonly extraPaths: string[]; readonly preloadModules: string[]; readonly showAdvancedMembers: boolean; + readonly typeshedPaths: string[]; } export interface IWorkspaceSymbolSettings { readonly enabled: boolean; @@ -237,6 +238,7 @@ export interface ITerminalSettings { } export interface IPythonAnalysisEngineSettings { readonly showAdvancedMembers: boolean; + readonly typeshedPaths: string[]; } export const IConfigurationService = Symbol('IConfigurationService'); diff --git a/typeshed b/typeshed new file mode 160000 index 000000000000..95eff73ab209 --- /dev/null +++ b/typeshed @@ -0,0 +1 @@ +Subproject commit 95eff73ab2092f2c3158198d404a921447172418 From d5b038b4a339efa60d5847dc78897c9c02c99e92 Mon Sep 17 00:00:00 2001 From: Derek Keeler Date: Mon, 25 Jun 2018 13:18:06 -0700 Subject: [PATCH 377/433] Coverage reporting in codecove & in VSTS (1st pass) (#2059) - Add cobertura reports to the coverage config - Add new gulp task to inline code coverage report CSS - Change mocha reporter id an env var MOCHA_CI_REPORTER_ID --- .gitignore | 1 + coverconfig.json | 3 +- gulpfile.js | 16 ++- package-lock.json | 220 +++++++++++++++++++++++++++++++++++++++- package.json | 4 +- src/test/ciConstants.ts | 3 +- src/test/index.ts | 4 +- src/test/unittests.ts | 5 +- 8 files changed, 247 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 428c08f2a9d2..c3e5da568e27 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ bin/** obj/** .pytest_cache tmp/** +.python-version diff --git a/coverconfig.json b/coverconfig.json index a84634e92ecc..201980b0fd63 100644 --- a/coverconfig.json +++ b/coverconfig.json @@ -11,7 +11,8 @@ "json", "html", "lcov", - "lcovonly" + "lcovonly", + "cobertura" ], "verbose": false } diff --git a/gulpfile.js b/gulpfile.js index c62ff6311d77..708c3cb132ea 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -3,6 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +/* jshint node: true */ +/* jshint esversion: 6 */ + 'use strict'; const gulp = require('gulp'); @@ -29,6 +32,7 @@ const os = require('os'); const _ = require('lodash'); const nativeDependencyChecker = require('node-has-native-dependencies'); const flat = require('flat'); +const inlinesource = require('gulp-inline-source'); /** * Hygiene works by creating cascading subsets of all our files and @@ -118,12 +122,22 @@ gulp.task('cover:enable', () => { gulp.task('cover:disable', () => { return gulp.src("./coverconfig.json") .pipe(jeditor((json) => { - json.enabled = true; + json.enabled = false; return json; })) .pipe(gulp.dest("./out", { 'overwrite': true })); }); +/** + * Inline CSS into the coverage report for better visualizations on + * the VSTS report page for code coverage. + */ +gulp.task('inlinesource', () => { + return gulp.src('./coverage/lcov-report/*.html') + .pipe(inlinesource({attribute: false})) + .pipe(gulp.dest('./coverage/lcov-report-inline')); +}); + function hasNativeDependencies() { let nativeDependencies = nativeDependencyChecker.check(path.join(__dirname, 'node_modules')); if (!Array.isArray(nativeDependencies) || nativeDependencies.length === 0) { diff --git a/package-lock.json b/package-lock.json index 12cd5948e3f9..9e1243f56fc9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "python", - "version": "2018.6.0-beta", + "version": "2018.7.0-alpha", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1235,6 +1235,15 @@ "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", "dev": true }, + "clap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz", + "integrity": "sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA==", + "dev": true, + "requires": { + "chalk": "^1.1.3" + } + }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -1354,6 +1363,15 @@ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" }, + "coa": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/coa/-/coa-1.0.4.tgz", + "integrity": "sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0=", + "dev": true, + "requires": { + "q": "^1.1.2" + } + }, "codecov": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/codecov/-/codecov-3.0.2.tgz", @@ -1525,6 +1543,25 @@ } } }, + "css-tree": { + "version": "1.0.0-alpha25", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha25.tgz", + "integrity": "sha512-XC6xLW/JqIGirnZuUWHXCHRaAjje2b3OIB0Vj5RIJo6mIi/AdJo30quQl5LxUl0gkXDIrTrFGbMlcZjyFplz1A==", + "dev": true, + "requires": { + "mdn-data": "^1.0.0", + "source-map": "^0.5.3" + } + }, + "csso": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-3.4.0.tgz", + "integrity": "sha1-V7J+9VPMy/WqlkxkF0hkHprxE/M=", + "dev": true, + "requires": { + "css-tree": "1.0.0-alpha25" + } + }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -1930,6 +1967,49 @@ } } }, + "dom-serializer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", + "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", + "dev": true, + "requires": { + "domelementtype": "~1.1.1", + "entities": "~1.1.1" + }, + "dependencies": { + "domelementtype": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", + "dev": true + } + } + }, + "domelementtype": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", + "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", + "dev": true + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, "dotenv": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-5.0.1.tgz", @@ -2071,6 +2151,12 @@ } } }, + "entities": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", + "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", + "dev": true + }, "error-ex": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", @@ -3975,6 +4061,31 @@ } } }, + "gulp-inline-source": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/gulp-inline-source/-/gulp-inline-source-3.2.0.tgz", + "integrity": "sha512-Ky5SKDgM517QZ6Jw9rKhYz+ynX9T4sr72iUW2fbOhqzN74D37DzWZDZnbKLSwdlTbbqt7JFq/JmUmT12qroW+A==", + "dev": true, + "requires": { + "inline-source": "~5.2.6", + "plugin-error": "~1.0.1", + "through2": "~2.0.0" + }, + "dependencies": { + "plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + } + } + } + }, "gulp-json-editor": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/gulp-json-editor/-/gulp-json-editor-2.4.1.tgz", @@ -5218,6 +5329,20 @@ "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==", "dev": true }, + "htmlparser2": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz", + "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", + "dev": true, + "requires": { + "domelementtype": "^1.3.0", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^2.0.2" + } + }, "http-cache-semantics": { "version": "3.8.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", @@ -5302,6 +5427,38 @@ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true }, + "inline-source": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/inline-source/-/inline-source-5.2.7.tgz", + "integrity": "sha512-RvMOGMXxAqqve4ld128B7TYyNR2aP1LB38dcSpWFmqXrhKPuey1+yFU6kFUgyH8IWX+gRZdWtHN4eQ9d0IpFZg==", + "dev": true, + "requires": { + "csso": "3.4.x", + "htmlparser2": "3.9.x", + "is-plain-obj": "1.1.x", + "object-assign": "4.1.x", + "svgo": "0.7.x", + "uglify-js": "3.3.x" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "uglify-js": { + "version": "3.3.28", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.28.tgz", + "integrity": "sha512-68Rc/aA6cswiaQ5SrE979UJcXX+ADA1z33/ZsPd+fbAiVdjZ16OXdbtGO+rJUUBgK6qdf3SOPhQf3K/ybF5Miw==", + "dev": true, + "requires": { + "commander": "~2.15.0", + "source-map": "~0.6.1" + } + } + } + }, "interpret": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", @@ -6421,6 +6578,12 @@ "inherits": "^2.0.1" } }, + "mdn-data": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-1.1.4.tgz", + "integrity": "sha512-FSYbp3lyKjyj3E7fMl6rYvUdX0FBXaluGqlFoYESWQlyUTq8R+wp0rkFxoYFqZlHCvsUXGjyJmLQSnXToYhOSA==", + "dev": true + }, "memoizee": { "version": "0.4.12", "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.12.tgz", @@ -7422,6 +7585,12 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", @@ -8380,6 +8549,49 @@ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", "dev": true }, + "svgo": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-0.7.2.tgz", + "integrity": "sha1-n1dyQTlSE1xv779Ar+ak+qiLS7U=", + "dev": true, + "requires": { + "coa": "~1.0.1", + "colors": "~1.1.2", + "csso": "~2.3.1", + "js-yaml": "~3.7.0", + "mkdirp": "~0.5.1", + "sax": "~1.2.1", + "whet.extend": "~0.9.9" + }, + "dependencies": { + "colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", + "dev": true + }, + "csso": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/csso/-/csso-2.3.2.tgz", + "integrity": "sha1-3dUsWHAz9J6Utx/FVWnyUuj/X4U=", + "dev": true, + "requires": { + "clap": "^1.0.9", + "source-map": "^0.5.3" + } + }, + "js-yaml": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz", + "integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^2.6.0" + } + } + } + }, "symbol-observable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", @@ -9480,6 +9692,12 @@ "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.1.tgz", "integrity": "sha1-Eahr7+rDxKo+wIYjZRo8gabQu8g=" }, + "whet.extend": { + "version": "0.9.9", + "resolved": "https://registry.npmjs.org/whet.extend/-/whet.extend-0.9.9.tgz", + "integrity": "sha1-+HfVv2SMl+WqVC+twW1qJZucEaE=", + "dev": true + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", diff --git a/package.json b/package.json index d09a1617fca7..ebf7f0cb38fc 100644 --- a/package.json +++ b/package.json @@ -1903,7 +1903,8 @@ "clean": "gulp clean", "clean:ptvsd": "gulp clean:ptvsd", "cover:enable": "gulp cover:enable", - "debugger-coverage": "gulp debugger-coverage" + "debugger-coverage": "gulp debugger-coverage", + "cover:inlinesource": "gulp inlinesource" }, "dependencies": { "arch": "2.1.0", @@ -1987,6 +1988,7 @@ "gulp-debounced-watch": "^1.0.4", "gulp-filter": "^5.1.0", "gulp-gitmodified": "^1.1.1", + "gulp-inline-source": "^3.2.0", "gulp-json-editor": "^2.2.2", "gulp-sourcemaps": "^2.6.4", "gulp-typescript": "^4.0.1", diff --git a/src/test/ciConstants.ts b/src/test/ciConstants.ts index 083c4bbf0aa9..e1fc989e5fd8 100644 --- a/src/test/ciConstants.ts +++ b/src/test/ciConstants.ts @@ -22,5 +22,6 @@ export const MOCHA_CI_REPORTFILE: string = MOCHA_REPORTER_JUNIT && process.env.M process.env.MOCHA_CI_REPORTFILE : './junit-out.xml'; export const MOCHA_CI_PROPERTIES: string = MOCHA_REPORTER_JUNIT && process.env.MOCHA_CI_PROPERTIES !== undefined ? process.env.MOCHA_CI_PROPERTIES : ''; - +export const MOCHA_CI_REPORTER_ID: string = MOCHA_REPORTER_JUNIT && process.env.MOCHA_CI_REPORTER_ID !== undefined ? + process.env.MOCHA_CI_REPORTER_ID : 'mocha-junit-reporter'; export const IS_CI_SERVER_TEST_DEBUGGER = process.env.IS_CI_SERVER_TEST_DEBUGGER === '1'; diff --git a/src/test/index.ts b/src/test/index.ts index a647b50e44f7..b63fe1ba47b4 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -6,7 +6,7 @@ if ((Reflect as any).metadata === undefined) { import { IS_CI_SERVER, IS_CI_SERVER_TEST_DEBUGGER, - IS_VSTS, MOCHA_CI_PROPERTIES, + IS_VSTS, MOCHA_CI_PROPERTIES, MOCHA_CI_REPORTER_ID, MOCHA_CI_REPORTFILE, MOCHA_REPORTER_JUNIT } from './ciConstants'; import { IS_MULTI_ROOT_TEST } from './constants'; @@ -42,7 +42,7 @@ if (IS_VSTS) { // 'MOCHA_REPORTER_JUNIT' is defined, further control is afforded // by other 'MOCHA_CI_...' variables. See constants.ts for info. if (MOCHA_REPORTER_JUNIT) { - options.reporter = 'mocha-junit-reporter'; + options.reporter = MOCHA_CI_REPORTER_ID; options.reporterOptions = { mochaFile: MOCHA_CI_REPORTFILE, properties: MOCHA_CI_PROPERTIES diff --git a/src/test/unittests.ts b/src/test/unittests.ts index bce375e5787d..8a8c41bfcb69 100644 --- a/src/test/unittests.ts +++ b/src/test/unittests.ts @@ -12,7 +12,8 @@ import * as glob from 'glob'; import * as Mocha from 'mocha'; import * as path from 'path'; import { MochaSetupOptions } from 'vscode/lib/testrunner'; -import { MOCHA_CI_REPORTFILE, MOCHA_REPORTER_JUNIT } from './ciConstants'; +import { MOCHA_CI_REPORTER_ID, MOCHA_CI_REPORTFILE, + MOCHA_REPORTER_JUNIT } from './ciConstants'; import * as vscodeMoscks from './vscode-mock'; export function runTests(testOptions?: { grep?: string; timeout?: number }) { @@ -35,7 +36,7 @@ export function runTests(testOptions?: { grep?: string; timeout?: number }) { grep: undefined, ui: 'tdd', timeout, - reporter: '../../../.mocha-reporter/mocha-vsts-reporter.js', + reporter: MOCHA_CI_REPORTER_ID, reporterOptions: { useColors: false, mochaFile: MOCHA_CI_REPORTFILE, From 2d9f3432eae7110924a658b26d7f7181d4bb0303 Mon Sep 17 00:00:00 2001 From: Derek Keeler Date: Thu, 28 Jun 2018 08:19:52 -0700 Subject: [PATCH 378/433] Correct unittest discovery for n-depth test source trees (#2066) * Include full relative namespace to each test selected - for file-only test select - for suite-only test select --- news/2 Fixes/2044.md | 1 + .../visualstudio_py_testlauncher.py | 41 +-- src/client/unittests/common/types.ts | 2 + src/client/unittests/main.ts | 1 + .../unittest/services/parserService.ts | 17 +- src/client/unittests/unittest/socketServer.ts | 2 +- .../unittest/unittest.discovery.test.ts | 8 +- .../unittest/unittest.discovery.unit.test.ts | 253 +++++++++++++++++- src/test/unittests/unittest/unittest.test.ts | 2 +- 9 files changed, 290 insertions(+), 37 deletions(-) create mode 100644 news/2 Fixes/2044.md diff --git a/news/2 Fixes/2044.md b/news/2 Fixes/2044.md new file mode 100644 index 000000000000..6118357acaa2 --- /dev/null +++ b/news/2 Fixes/2044.md @@ -0,0 +1 @@ +Store testId for files & suites during unittest discovery diff --git a/pythonFiles/PythonTools/visualstudio_py_testlauncher.py b/pythonFiles/PythonTools/visualstudio_py_testlauncher.py index 7ed86ecaa320..270d87be3399 100644 --- a/pythonFiles/PythonTools/visualstudio_py_testlauncher.py +++ b/pythonFiles/PythonTools/visualstudio_py_testlauncher.py @@ -1,16 +1,16 @@ # Python Tools for Visual Studio # Copyright(c) Microsoft Corporation # All rights reserved. -# +# # Licensed under the Apache License, Version 2.0 (the License); you may not use # this file except in compliance with the License. You may obtain a copy of the # License at http://www.apache.org/licenses/LICENSE-2.0 -# +# # THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS # OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY # IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, # MERCHANTABLITY OR NON-INFRINGEMENT. -# +# # See the Apache Version 2.0 License for specific language governing # permissions and limitations under the License. @@ -43,11 +43,11 @@ def __init__(self, old_out, is_stdout): def flush(self): if self.old_out: self.old_out.flush() - + def writelines(self, lines): for line in lines: self.write(line) - + @property def encoding(self): return 'utf8' @@ -58,13 +58,13 @@ def write(self, value): self.old_out.write(value) # flush immediately, else things go wonky and out of order self.flush() - + def isatty(self): return True def next(self): pass - + @property def name(self): if self.is_stdout: @@ -84,7 +84,7 @@ def write(self, data): _channel.send_event('stdout' if self.is_stdout else 'stderr', content=data) self.buffer.write(data) - def flush(self): + def flush(self): self.buffer.flush() def truncate(self, pos = None): @@ -137,7 +137,7 @@ def startTest(self, test): super(VsTestResult, self).startTest(test) if _channel is not None: _channel.send_event( - name='start', + name='start', test = test.id() ) @@ -177,7 +177,7 @@ def sendResult(self, test, outcome, trace = None): tb = ''.join(formatted) message = str(trace[1]) _channel.send_event( - name='result', + name='result', outcome=outcome, traceback = tb, message = message, @@ -196,7 +196,7 @@ def stopTests(): class ExitCommand(Exception): pass -def signal_handler(signal, frame): +def signal_handler(signal, frame): raise ExitCommand() def main(): @@ -223,7 +223,7 @@ def main(): if opts.debug: from ptvsd.visualstudio_py_debugger import DONT_DEBUG, DEBUG_ENTRYPOINTS, get_code - + sys.path[0] = os.getcwd() if opts.result_port: try: @@ -243,7 +243,7 @@ def main(): pass elif opts.mixed_mode: - # For mixed-mode attach, there's no ptvsd and hence no wait_for_attach(), + # For mixed-mode attach, there's no ptvsd and hence no wait_for_attach(), # so we have to use Win32 API in a loop to do the same thing. from time import sleep from ctypes import windll, c_char @@ -278,43 +278,44 @@ def main(): opts.up = 'test*.py' tests = unittest.defaultTestLoader.discover(opts.us, opts.up) else: - # loadTestsFromNames doesn't work well (with duplicate file names or class names) + # loadTestsFromNames doesn't work well (with duplicate file names or class names) # Easier approach is find the test suite and use that for running loader = unittest.TestLoader() # opts.us will be passed in suites = loader.discover(opts.us, pattern=os.path.basename(opts.testFile)) suite = None - tests = None + tests = None if opts.tests is None: # Run everything in the test file tests = suites else: # Run a specific test class or test method - for suite in suites._tests: - for cls in suite._tests: + for test_suite in suites._tests: + for cls in test_suite._tests: try: for m in cls._tests: testId = m.id() if testId.startswith(opts.tests[0]): suite = cls + break if testId == opts.tests[0]: tests = m break except Exception as err: - errorMessage = traceback.format_exception() + errorMessage = traceback.format_exception() pass if tests is None: tests = suite if tests is None and suite is None: _channel.send_event( - name='error', + name='error', outcome='', traceback = '', message = 'Failed to identify the test', test = '' ) if opts.uvInt is None: - opts.uvInt = 0 + opts.uvInt = 0 if opts.uf is not None: runner = unittest.TextTestRunner(verbosity=opts.uvInt, resultclass=VsTestResult, failfast=True) else: diff --git a/src/client/unittests/common/types.ts b/src/client/unittests/common/types.ts index 67e2663efab9..6f11f95d490f 100644 --- a/src/client/unittests/common/types.ts +++ b/src/client/unittests/common/types.ts @@ -24,6 +24,8 @@ export type TestRunOptions = { debug?: boolean; }; +export type UnitTestParserOptions = TestDiscoveryOptions & { startDirectory: string }; + export type TestFolder = TestResult & { name: string; testFiles: TestFile[]; diff --git a/src/client/unittests/main.ts b/src/client/unittests/main.ts index 2f76ad448f9f..c803425ec1fa 100644 --- a/src/client/unittests/main.ts +++ b/src/client/unittests/main.ts @@ -91,6 +91,7 @@ export class UnitTestManagementService implements IUnitTestManagementService, Di if (this.testResultDisplay) { this.testResultDisplay.enabled = false; } + // tslint:disable-next-line:no-suspicious-comment // TODO: Why are we disposing, what happens when tests are enabled. if (this.workspaceTestManagerService) { this.workspaceTestManagerService.dispose(); diff --git a/src/client/unittests/unittest/services/parserService.ts b/src/client/unittests/unittest/services/parserService.ts index 1b4acdd194c9..6e99f1044d3a 100644 --- a/src/client/unittests/unittest/services/parserService.ts +++ b/src/client/unittests/unittest/services/parserService.ts @@ -3,9 +3,9 @@ import { inject, injectable } from 'inversify'; import * as path from 'path'; -import { ITestsHelper, ITestsParser, TestDiscoveryOptions, TestFile, TestFunction, Tests, TestStatus } from '../../common/types'; - -type UnitTestParserOptions = TestDiscoveryOptions & { startDirectory: string }; +import { ITestsHelper, ITestsParser, TestFile, + TestFunction, Tests, TestStatus, + UnitTestParserOptions } from '../../common/types'; @injectable() export class TestsParser implements ITestsParser { @@ -60,6 +60,7 @@ export class TestsParser implements ITestsParser { const paths = testIdParts.slice(0, testIdParts.length - 2); const filePath = `${path.join(rootDirectory, ...paths)}.py`; const functionName = testIdParts.pop()!; + const suiteToRun = testIdParts.join('.'); const className = testIdParts.pop()!; // Check if we already have this test file @@ -70,7 +71,7 @@ export class TestsParser implements ITestsParser { fullPath: filePath, functions: [], suites: [], - nameToRun: `${className}.${functionName}`, + nameToRun: `${suiteToRun}.${functionName}`, xmlName: '', status: TestStatus.Idle, time: 0 @@ -78,9 +79,9 @@ export class TestsParser implements ITestsParser { testFiles.push(testFile); } - // Check if we already have this test file - const classNameToRun = className; - let testSuite = testFile.suites.find(cls => cls.nameToRun === classNameToRun); + // Check if we already have this suite + // nameToRun = testId - method name + let testSuite = testFile.suites.find(cls => cls.nameToRun === suiteToRun); if (!testSuite) { testSuite = { name: className, @@ -88,7 +89,7 @@ export class TestsParser implements ITestsParser { suites: [], isUnitTest: true, isInstance: false, - nameToRun: `${path.parse(filePath).name}.${classNameToRun}`, + nameToRun: suiteToRun, xmlName: '', status: TestStatus.Idle, time: 0 diff --git a/src/client/unittests/unittest/socketServer.ts b/src/client/unittests/unittest/socketServer.ts index da83a350e61a..c75e33c46c4a 100644 --- a/src/client/unittests/unittest/socketServer.ts +++ b/src/client/unittests/unittest/socketServer.ts @@ -29,7 +29,7 @@ export class UnitTestSocketServer extends EventEmitter implements IUnitTestSocke this.server = undefined; } } - public start(options: { port?: number, host?: string } = { port: 0, host: 'localhost' }): Promise { + public start(options: { port?: number; host?: string } = { port: 0, host: 'localhost' }): Promise { this.startedDef = createDeferred(); this.server = net.createServer(this.connectionListener.bind(this)); this.server!.maxConnections = MaxConnections; diff --git a/src/test/unittests/unittest/unittest.discovery.test.ts b/src/test/unittests/unittest/unittest.discovery.test.ts index d3e8c5b477b5..12427c955f5a 100644 --- a/src/test/unittests/unittest/unittest.discovery.test.ts +++ b/src/test/unittests/unittest/unittest.discovery.test.ts @@ -87,7 +87,7 @@ suite('Unit Tests - unittest - discovery with mocked process output', () => { assert.equal(tests.testFiles.length, 1, 'Incorrect number of test files'); assert.equal(tests.testFunctions.length, 3, 'Incorrect number of test functions'); assert.equal(tests.testSuites.length, 1, 'Incorrect number of test suites'); - assert.equal(tests.testFiles.some(t => t.name === 'test_one.py' && t.nameToRun === 'Test_test1.test_A'), true, 'Test File not found'); + assert.equal(tests.testFiles.some(t => t.name === 'test_one.py' && t.nameToRun === 'test_one.Test_test1.test_A'), true, 'Test File not found'); }); test('Discover Tests', async () => { @@ -110,8 +110,8 @@ suite('Unit Tests - unittest - discovery with mocked process output', () => { assert.equal(tests.testFiles.length, 2, 'Incorrect number of test files'); assert.equal(tests.testFunctions.length, 9, 'Incorrect number of test functions'); assert.equal(tests.testSuites.length, 3, 'Incorrect number of test suites'); - assert.equal(tests.testFiles.some(t => t.name === 'test_unittest_one.py' && t.nameToRun === 'Test_test1.test_A'), true, 'Test File not found'); - assert.equal(tests.testFiles.some(t => t.name === 'test_unittest_two.py' && t.nameToRun === 'Test_test2.test_A2'), true, 'Test File not found'); + assert.equal(tests.testFiles.some(t => t.name === 'test_unittest_one.py' && t.nameToRun === 'test_unittest_one.Test_test1.test_A'), true, 'Test File not found'); + assert.equal(tests.testFiles.some(t => t.name === 'test_unittest_two.py' && t.nameToRun === 'test_unittest_two.Test_test2.test_A2'), true, 'Test File not found'); }); test('Discover Tests (pattern = *_test_*.py)', async () => { @@ -127,7 +127,7 @@ suite('Unit Tests - unittest - discovery with mocked process output', () => { assert.equal(tests.testFiles.length, 1, 'Incorrect number of test files'); assert.equal(tests.testFunctions.length, 2, 'Incorrect number of test functions'); assert.equal(tests.testSuites.length, 1, 'Incorrect number of test suites'); - assert.equal(tests.testFiles.some(t => t.name === 'unittest_three_test.py' && t.nameToRun === 'Test_test3.test_A'), true, 'Test File not found'); + assert.equal(tests.testFiles.some(t => t.name === 'unittest_three_test.py' && t.nameToRun === 'unittest_three_test.Test_test3.test_A'), true, 'Test File not found'); }); test('Setting cwd should return tests', async () => { diff --git a/src/test/unittests/unittest/unittest.discovery.unit.test.ts b/src/test/unittests/unittest/unittest.discovery.unit.test.ts index b70ad76845d5..fe54b33f7b1b 100644 --- a/src/test/unittests/unittest/unittest.discovery.unit.test.ts +++ b/src/test/unittests/unittest/unittest.discovery.unit.test.ts @@ -9,12 +9,16 @@ import { expect, use } from 'chai'; import * as chaipromise from 'chai-as-promised'; import * as path from 'path'; import * as typeMoq from 'typemoq'; -import { CancellationToken } from 'vscode'; +import { CancellationToken, Uri } from 'vscode'; import { IServiceContainer } from '../../../client/ioc/types'; import { UNITTEST_PROVIDER } from '../../../client/unittests/common/constants'; -import { ITestDiscoveryService, ITestRunner, ITestsParser, Options, TestDiscoveryOptions, Tests } from '../../../client/unittests/common/types'; +import { TestsHelper } from '../../../client/unittests/common/testUtils'; +import { TestFlatteningVisitor } from '../../../client/unittests/common/testVisitors/flatteningVisitor'; +import { ITestDiscoveryService, ITestRunner, ITestsParser, + Options, TestDiscoveryOptions, Tests, UnitTestParserOptions } from '../../../client/unittests/common/types'; import { IArgumentsHelper } from '../../../client/unittests/types'; import { TestDiscoveryService } from '../../../client/unittests/unittest/services/discoveryService'; +import { TestsParser } from '../../../client/unittests/unittest/services/parserService'; use(chaipromise); @@ -23,10 +27,11 @@ suite('Unit Tests - Unittest - Discovery', () => { let argsHelper: typeMoq.IMock; let testParser: typeMoq.IMock; let runner: typeMoq.IMock; + let serviceContainer: typeMoq.IMock; const dir = path.join('a', 'b', 'c'); const pattern = 'Pattern_To_Search_For'; setup(() => { - const serviceContainer = typeMoq.Mock.ofType(); + serviceContainer = typeMoq.Mock.ofType(); argsHelper = typeMoq.Mock.ofType(); testParser = typeMoq.Mock.ofType(); runner = typeMoq.Mock.ofType(); @@ -300,4 +305,246 @@ suite('Unit Tests - Unittest - Discovery', () => { runner.verifyAll(); testParser.verifyAll(); }); + test('Ensure discovery resolves test suites in n-depth directories', async () => { + const testHelper: TestsHelper = new TestsHelper(new TestFlatteningVisitor(), serviceContainer.object); + + const testsParser: TestsParser = new TestsParser(testHelper); + + const opts = typeMoq.Mock.ofType(); + const token = typeMoq.Mock.ofType(); + const wspace = typeMoq.Mock.ofType(); + opts.setup(o => o.token).returns(() => token.object); + opts.setup(o => o.workspaceFolder).returns(() => wspace.object); + token.setup(t => t.isCancellationRequested) + .returns(() => true); + opts.setup(o => o.cwd).returns(() => '/home/user/dev'); + opts.setup(o => o.startDirectory).returns(() => '/home/user/dev/tests'); + + const discoveryOutput: string = ['start', + 'apptests.debug.class_name.RootClassName.test_root', + 'apptests.debug.class_name.RootClassName.test_root_other', + 'apptests.debug.first.class_name.FirstLevelClassName.test_first', + 'apptests.debug.first.class_name.FirstLevelClassName.test_first_other', + 'apptests.debug.first.second.class_name.SecondLevelClassName.test_second', + 'apptests.debug.first.second.class_name.SecondLevelClassName.test_second_other', + ''].join('\n'); + + const tests: Tests = testsParser.parse(discoveryOutput, opts.object); + + expect(tests.testFiles.length).to.be.equal(3); + expect(tests.testFunctions.length).to.be.equal(6); + expect(tests.testSuites.length).to.be.equal(3); + expect(tests.testFolders.length).to.be.equal(1); + + // now ensure that each test function belongs within a single test suite... + tests.testFunctions.forEach(fn => { + if (fn.parentTestSuite) { + const testPrefix: boolean = fn.testFunction.nameToRun.startsWith(fn.parentTestSuite.nameToRun); + expect(testPrefix).to.equal(true, + [`function ${fn.testFunction.name} has a parent suite ${fn.parentTestSuite.name}, `, + `but the parent suite 'nameToRun' (${fn.parentTestSuite.nameToRun}) isn't the `, + `prefix to the functions 'nameToRun' (${fn.testFunction.nameToRun})`].join('')); + } + }); + }); + test('Ensure discovery resolves test files in n-depth directories', async () => { + const testHelper: TestsHelper = new TestsHelper(new TestFlatteningVisitor(), serviceContainer.object); + + const testsParser: TestsParser = new TestsParser(testHelper); + + const opts = typeMoq.Mock.ofType(); + const token = typeMoq.Mock.ofType(); + const wspace = typeMoq.Mock.ofType(); + opts.setup(o => o.token).returns(() => token.object); + opts.setup(o => o.workspaceFolder).returns(() => wspace.object); + token.setup(t => t.isCancellationRequested) + .returns(() => true); + opts.setup(o => o.cwd).returns(() => '/home/user/dev'); + opts.setup(o => o.startDirectory).returns(() => '/home/user/dev/tests'); + + const discoveryOutput: string = ['start', + 'apptests.debug.class_name.RootClassName.test_root', + 'apptests.debug.class_name.RootClassName.test_root_other', + 'apptests.debug.first.class_name.FirstLevelClassName.test_first', + 'apptests.debug.first.class_name.FirstLevelClassName.test_first_other', + 'apptests.debug.first.second.class_name.SecondLevelClassName.test_second', + 'apptests.debug.first.second.class_name.SecondLevelClassName.test_second_other', + ''].join('\n'); + + const tests: Tests = testsParser.parse(discoveryOutput, opts.object); + + expect(tests.testFiles.length).to.be.equal(3); + expect(tests.testFunctions.length).to.be.equal(6); + expect(tests.testSuites.length).to.be.equal(3); + expect(tests.testFolders.length).to.be.equal(1); + + // now ensure that the 'nameToRun' for each test function begins with its file's a single test suite... + tests.testFunctions.forEach(fn => { + if (fn.parentTestSuite) { + const testPrefix: boolean = fn.testFunction.nameToRun.startsWith(fn.parentTestFile.nameToRun); + expect(testPrefix).to.equal(true, + [`function ${fn.testFunction.name} was found in file ${fn.parentTestFile.name}, `, + `but the parent file 'nameToRun' (${fn.parentTestFile.nameToRun}) isn't the `, + `prefix to the functions 'nameToRun' (${fn.testFunction.nameToRun})`].join('')); + } + }); + }); + test('Ensure discovery resolves test suites in n-depth directories when no start directory is given', async () => { + const testHelper: TestsHelper = new TestsHelper(new TestFlatteningVisitor(), serviceContainer.object); + + const testsParser: TestsParser = new TestsParser(testHelper); + + const opts = typeMoq.Mock.ofType(); + const token = typeMoq.Mock.ofType(); + const wspace = typeMoq.Mock.ofType(); + opts.setup(o => o.token).returns(() => token.object); + opts.setup(o => o.workspaceFolder).returns(() => wspace.object); + token.setup(t => t.isCancellationRequested) + .returns(() => true); + opts.setup(o => o.cwd).returns(() => '/home/user/dev'); + opts.setup(o => o.startDirectory).returns(() => ''); + + const discoveryOutput: string = ['start', + 'apptests.debug.class_name.RootClassName.test_root', + 'apptests.debug.class_name.RootClassName.test_root_other', + 'apptests.debug.first.class_name.FirstLevelClassName.test_first', + 'apptests.debug.first.class_name.FirstLevelClassName.test_first_other', + 'apptests.debug.first.second.class_name.SecondLevelClassName.test_second', + 'apptests.debug.first.second.class_name.SecondLevelClassName.test_second_other', + ''].join('\n'); + + const tests: Tests = testsParser.parse(discoveryOutput, opts.object); + + expect(tests.testFiles.length).to.be.equal(3); + expect(tests.testFunctions.length).to.be.equal(6); + expect(tests.testSuites.length).to.be.equal(3); + expect(tests.testFolders.length).to.be.equal(1); + + // now ensure that each test function belongs within a single test suite... + tests.testFunctions.forEach(fn => { + if (fn.parentTestSuite) { + const testPrefix: boolean = fn.testFunction.nameToRun.startsWith(fn.parentTestSuite.nameToRun); + expect(testPrefix).to.equal(true, + [`function ${fn.testFunction.name} has a parent suite ${fn.parentTestSuite.name}, `, + `but the parent suite 'nameToRun' (${fn.parentTestSuite.nameToRun}) isn't the `, + `prefix to the functions 'nameToRun' (${fn.testFunction.nameToRun})`].join('')); + } + }); + }); + test('Ensure discovery resolves test suites in n-depth directories when a relative start directory is given', async () => { + const testHelper: TestsHelper = new TestsHelper(new TestFlatteningVisitor(), serviceContainer.object); + + const testsParser: TestsParser = new TestsParser(testHelper); + + const opts = typeMoq.Mock.ofType(); + const token = typeMoq.Mock.ofType(); + const wspace = typeMoq.Mock.ofType(); + opts.setup(o => o.token).returns(() => token.object); + opts.setup(o => o.workspaceFolder).returns(() => wspace.object); + token.setup(t => t.isCancellationRequested) + .returns(() => true); + opts.setup(o => o.cwd).returns(() => '/home/user/dev'); + opts.setup(o => o.startDirectory).returns(() => './tests'); + + const discoveryOutput: string = ['start', + 'apptests.debug.class_name.RootClassName.test_root', + 'apptests.debug.class_name.RootClassName.test_root_other', + 'apptests.debug.first.class_name.FirstLevelClassName.test_first', + 'apptests.debug.first.class_name.FirstLevelClassName.test_first_other', + 'apptests.debug.first.second.class_name.SecondLevelClassName.test_second', + 'apptests.debug.first.second.class_name.SecondLevelClassName.test_second_other', + ''].join('\n'); + + const tests: Tests = testsParser.parse(discoveryOutput, opts.object); + + expect(tests.testFiles.length).to.be.equal(3); + expect(tests.testFunctions.length).to.be.equal(6); + expect(tests.testSuites.length).to.be.equal(3); + expect(tests.testFolders.length).to.be.equal(1); + + // now ensure that each test function belongs within a single test suite... + tests.testFunctions.forEach(fn => { + if (fn.parentTestSuite) { + const testPrefix: boolean = fn.testFunction.nameToRun.startsWith(fn.parentTestSuite.nameToRun); + expect(testPrefix).to.equal(true, + [`function ${fn.testFunction.name} has a parent suite ${fn.parentTestSuite.name}, `, + `but the parent suite 'nameToRun' (${fn.parentTestSuite.nameToRun}) isn't the `, + `prefix to the functions 'nameToRun' (${fn.testFunction.nameToRun})`].join('')); + } + }); + }); + test('Ensure discovery will not fail with blank content' , async () => { + const testHelper: TestsHelper = new TestsHelper(new TestFlatteningVisitor(), serviceContainer.object); + + const testsParser: TestsParser = new TestsParser(testHelper); + + const opts = typeMoq.Mock.ofType(); + const token = typeMoq.Mock.ofType(); + const wspace = typeMoq.Mock.ofType(); + opts.setup(o => o.token).returns(() => token.object); + opts.setup(o => o.workspaceFolder).returns(() => wspace.object); + token.setup(t => t.isCancellationRequested) + .returns(() => true); + opts.setup(o => o.cwd).returns(() => '/home/user/dev'); + opts.setup(o => o.startDirectory).returns(() => './tests'); + + const tests: Tests = testsParser.parse('', opts.object); + + expect(tests.testFiles.length).to.be.equal(0); + expect(tests.testFunctions.length).to.be.equal(0); + expect(tests.testSuites.length).to.be.equal(0); + expect(tests.testFolders.length).to.be.equal(0); + }); + test('Ensure discovery will not fail with corrupt content', async () => { + const testHelper: TestsHelper = new TestsHelper(new TestFlatteningVisitor(), serviceContainer.object); + + const testsParser: TestsParser = new TestsParser(testHelper); + + const opts = typeMoq.Mock.ofType(); + const token = typeMoq.Mock.ofType(); + const wspace = typeMoq.Mock.ofType(); + opts.setup(o => o.token).returns(() => token.object); + opts.setup(o => o.workspaceFolder).returns(() => wspace.object); + token.setup(t => t.isCancellationRequested) + .returns(() => true); + opts.setup(o => o.cwd).returns(() => '/home/user/dev'); + opts.setup(o => o.startDirectory).returns(() => './tests'); + + const discoveryOutput: string = ['a;lskdjfa', + 'allikbrilkpdbfkdfbalk;nfm', + '', + ';;h,spmn,nlikmslkjls.bmnl;klkjna;jdfngad,lmvnjkldfhb', + ''].join('\n'); + + const tests: Tests = testsParser.parse(discoveryOutput, opts.object); + + expect(tests.testFiles.length).to.be.equal(0); + expect(tests.testFunctions.length).to.be.equal(0); + expect(tests.testSuites.length).to.be.equal(0); + expect(tests.testFolders.length).to.be.equal(0); + }); + test('Ensure discovery resolves when no tests are found in the given path', async () => { + const testHelper: TestsHelper = new TestsHelper(new TestFlatteningVisitor(), serviceContainer.object); + + const testsParser: TestsParser = new TestsParser(testHelper); + + const opts = typeMoq.Mock.ofType(); + const token = typeMoq.Mock.ofType(); + const wspace = typeMoq.Mock.ofType(); + opts.setup(o => o.token).returns(() => token.object); + opts.setup(o => o.workspaceFolder).returns(() => wspace.object); + token.setup(t => t.isCancellationRequested) + .returns(() => true); + opts.setup(o => o.cwd).returns(() => '/home/user/dev'); + opts.setup(o => o.startDirectory).returns(() => './tests'); + + const discoveryOutput: string = 'start'; + + const tests: Tests = testsParser.parse(discoveryOutput, opts.object); + + expect(tests.testFiles.length).to.be.equal(0); + expect(tests.testFunctions.length).to.be.equal(0); + expect(tests.testSuites.length).to.be.equal(0); + expect(tests.testFolders.length).to.be.equal(0); + }); }); diff --git a/src/test/unittests/unittest/unittest.test.ts b/src/test/unittests/unittest/unittest.test.ts index ebad6a6b5d18..4152ef540be9 100644 --- a/src/test/unittests/unittest/unittest.test.ts +++ b/src/test/unittests/unittest/unittest.test.ts @@ -58,6 +58,6 @@ suite('Unit Tests - unittest - discovery against actual python process', () => { assert.equal(tests.testFiles.length, 1, 'Incorrect number of test files'); assert.equal(tests.testFunctions.length, 3, 'Incorrect number of test functions'); assert.equal(tests.testSuites.length, 1, 'Incorrect number of test suites'); - assert.equal(tests.testFiles.some(t => t.name === 'test_one.py' && t.nameToRun === 'Test_test1.test_A'), true, 'Test File not found'); + assert.equal(tests.testFiles.some(t => t.name === 'test_one.py' && t.nameToRun === 'test_one.Test_test1.test_A'), true, 'Test File not found'); }); }); From 1f5b5ca7643de612f3a931cc474a24d477d5515f Mon Sep 17 00:00:00 2001 From: Derek Keeler Date: Thu, 28 Jun 2018 10:41:05 -0700 Subject: [PATCH 379/433] Correct error message in VSTS mocha reporter (#2070) --- .mocha-reporter/mocha-vsts-reporter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.mocha-reporter/mocha-vsts-reporter.js b/.mocha-reporter/mocha-vsts-reporter.js index 8bfdc15670d3..7ac83371527c 100644 --- a/.mocha-reporter/mocha-vsts-reporter.js +++ b/.mocha-reporter/mocha-vsts-reporter.js @@ -37,7 +37,7 @@ function MochaVstsReporter(runner, options) { // ##vso[task.setprogress]current operation } }); - + runner.on('pass', function(test){ passes++; console.log('%s✓ %s (%dms)', indenter, test.title, test.duration); @@ -52,7 +52,7 @@ function MochaVstsReporter(runner, options) { runner.on('fail', function(test, err){ failures++; console.log('%s✖ %s -- error: %s', indenter, test.title, err.message); - console.log('##vso[task.logissue type=warning;sourcepath=%s;]SKIPPED TEST %s :: %s', test.file, test.parent.title, test.title); + console.log('##vso[task.logissue type=error;sourcepath=%s;]FAILED %s :: %s', test.file, test.parent.title, test.title); }); runner.on('end', function(){ From 92a4421f3b95b08e380c0ceea6b420c546741643 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Fri, 29 Jun 2018 09:46:30 -0700 Subject: [PATCH 380/433] LS settings for typeshed and diagnostics (#2065) * LS symbol providers * Typeshed paths * Typeshed submodule * Add submodule * New settings * Add diagnostics control settings * Add typeshed paths change check * Exclude some typeshed files from packages --- .vscodeignore | 8 +++ package.json | 51 +++++++++++++++ src/client/activation/analysis.ts | 98 ++++++++++++++++++----------- src/client/common/configSettings.ts | 10 +++ src/client/common/types.ts | 10 ++- 5 files changed, 138 insertions(+), 39 deletions(-) diff --git a/.vscodeignore b/.vscodeignore index f6ba7c39ff26..2bae3e5be984 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -46,5 +46,13 @@ scripts/** src/** test/** tmp/** +typeshed/tests/** +typeshed/.flake8 +typeshed/.git +typeshed/.gitignore +typeshed/.travis.yml +typeshed/CONTRIBUTING.md +typeshed/README.md +typeshed/*.txt typings/** vsc-extension-quickstart.md diff --git a/package.json b/package.json index ebf7f0cb38fc..4e36d3de2415 100644 --- a/package.json +++ b/package.json @@ -1275,6 +1275,57 @@ "description": "Path to directory containing the Jedi library (this path will contain the 'Jedi' sub directory).", "scope": "resource" }, + "python.analysis.openFilesOnly": { + "type": "boolean", + "default": false, + "description": "Only show errors and warnings for open files rather than for the entire workspace.", + "scope": "resource" + }, + "python.analysis.typeshedPaths": { + "type": "array", + "default": [], + "items": { + "type": "string" + }, + "description": "Paths to look for typeshed modules.", + "scope": "resource" + }, + "python.analysis.errors": { + "type": "array", + "default": [], + "items": { + "type": "string" + }, + "description": "List of diagnostics messages to be shown as errors.", + "scope": "resource" + }, + "python.analysis.warnings": { + "type": "array", + "default": [], + "items": { + "type": "string" + }, + "description": "List of diagnostics messages to be shown as warnings.", + "scope": "resource" + }, + "python.analysis.information": { + "type": "array", + "default": [], + "items": { + "type": "string" + }, + "description": "List of diagnostics messages to be shown as information.", + "scope": "resource" + }, + "python.analysis.disabled": { + "type": "array", + "default": [], + "items": { + "type": "string" + }, + "description": "List of suppressed diagnostic messages.", + "scope": "resource" + }, "python.linting.enabled": { "type": "boolean", "default": true, diff --git a/src/client/activation/analysis.ts b/src/client/activation/analysis.ts index 01d2e911e902..3c50866ae56c 100644 --- a/src/client/activation/analysis.ts +++ b/src/client/activation/analysis.ts @@ -6,12 +6,12 @@ import * as path from 'path'; import { OutputChannel, Uri } from 'vscode'; import { Disposable, LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient'; import { IApplicationShell, ICommandManager, IWorkspaceService } from '../common/application/types'; +import { PythonSettings } from '../common/configSettings'; import { isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; import { createDeferred, Deferred } from '../common/helpers'; import { IFileSystem, IPlatformService } from '../common/platform/types'; import { StopWatch } from '../common/stopWatch'; -import { IConfigurationService, IExtensionContext, IOutputChannel } from '../common/types'; -import { IInterpreterService } from '../interpreter/contracts'; +import { IConfigurationService, IExtensionContext, IOutputChannel, IPythonSettings } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { PYTHON_ANALYSIS_ENGINE_DOWNLOADED, @@ -38,7 +38,6 @@ export class AnalysisExtensionActivator implements IExtensionActivator { private readonly fs: IFileSystem; private readonly sw = new StopWatch(); private readonly platformData: PlatformData; - private readonly interpreterService: IInterpreterService; private readonly startupCompleted: Deferred; private readonly disposables: Disposable[] = []; private readonly context: IExtensionContext; @@ -47,6 +46,8 @@ export class AnalysisExtensionActivator implements IExtensionActivator { private languageClient: LanguageClient | undefined; private interpreterHash: string = ''; + private excludedFiles: string[] = []; + private typeshedPaths: string[] = []; private loadExtensionArgs: {} | undefined; constructor(@inject(IServiceContainer) private readonly services: IServiceContainer) { @@ -56,7 +57,6 @@ export class AnalysisExtensionActivator implements IExtensionActivator { this.output = this.services.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); this.fs = this.services.get(IFileSystem); this.platformData = new PlatformData(services.get(IPlatformService), this.fs); - this.interpreterService = this.services.get(IInterpreterService); this.workspace = this.services.get(IWorkspaceService); // Currently only a single root. Multi-root support is future. @@ -76,6 +76,8 @@ export class AnalysisExtensionActivator implements IExtensionActivator { } } )); + + (this.configuration.getSettings() as PythonSettings).addListener('change', this.onSettingsChanged); } public async activate(): Promise { @@ -84,7 +86,6 @@ export class AnalysisExtensionActivator implements IExtensionActivator { if (!clientOptions) { return false; } - this.disposables.push(this.interpreterService.onDidChangeInterpreter(() => this.restartLanguageServer())); return this.startLanguageServer(clientOptions); } @@ -96,19 +97,7 @@ export class AnalysisExtensionActivator implements IExtensionActivator { for (const d of this.disposables) { d.dispose(); } - } - - private async restartLanguageServer(): Promise { - if (!this.context) { - return; - } - const ids = new InterpreterDataService(this.context, this.services); - const idata = await ids.getInterpreterData(); - if (!idata || idata.hash !== this.interpreterHash) { - this.interpreterHash = idata ? idata.hash : ''; - await this.deactivate(); - await this.activate(); - } + (this.configuration.getSettings() as PythonSettings).removeListener('change', this.onSettingsChanged); } private async startLanguageServer(clientOptions: LanguageClientOptions): Promise { @@ -204,34 +193,27 @@ export class AnalysisExtensionActivator implements IExtensionActivator { properties['PrefixPath'] = interpreterData.prefix; } - let searchPathsString = interpreterData ? interpreterData.searchPaths : ''; - let typeshedPaths: string[] = []; + // tslint:disable-next-line:no-string-literal + properties['DatabasePath'] = path.join(this.context.extensionPath, analysisEngineFolder); + let searchPaths = interpreterData ? interpreterData.searchPaths.split(path.delimiter) : []; const settings = this.configuration.getSettings(); if (settings.autoComplete) { const extraPaths = settings.autoComplete.extraPaths; if (extraPaths && extraPaths.length > 0) { - searchPathsString = `${searchPathsString};${extraPaths.join(';')}`; + searchPaths.push(...extraPaths); } - typeshedPaths = settings.autoComplete.typeshedPaths; } - // tslint:disable-next-line:no-string-literal - properties['DatabasePath'] = path.join(this.context.extensionPath, analysisEngineFolder); - // Make sure paths do not contain multiple slashes so file URIs // in VS Code (Node.js) and in the language server (.NET) match. // Note: for the language server paths separator is always ; - const searchPaths = searchPathsString.split(path.delimiter).map(p => path.normalize(p)); - // tslint:disable-next-line:no-string-literal - properties['SearchPaths'] = `${searchPaths.join(';')};${pythonPath}`; - - if (!typeshedPaths || typeshedPaths.length === 0) { - typeshedPaths = [path.join(this.context.extensionPath, 'typeshed')]; - } + searchPaths.push(pythonPath); + searchPaths = searchPaths.map(p => path.normalize(p)); const selector = [{ language: PYTHON, scheme: 'file' }]; - const excludeFiles = this.getExcludedFiles(); + this.excludedFiles = this.getExcludedFiles(); + this.typeshedPaths = this.getTypeshedPaths(settings); // Options to control the language client return { @@ -253,9 +235,8 @@ export class AnalysisExtensionActivator implements IExtensionActivator { maxDocumentationTextLength: 0 }, searchPaths, - typeStubSearchPaths: typeshedPaths, - asyncStartup: true, - excludeFiles: excludeFiles, + typeStubSearchPaths: this.typeshedPaths, + excludeFiles: this.excludedFiles, testEnvironment: isTestExecution() } }; @@ -289,4 +270,49 @@ export class AnalysisExtensionActivator implements IExtensionActivator { .forEach(p => list.push(p)); } } + + private getTypeshedPaths(settings: IPythonSettings): string[] { + return settings.analysis.typeshedPaths && settings.analysis.typeshedPaths.length > 0 + ? settings.analysis.typeshedPaths + : [path.join(this.context.extensionPath, 'typeshed')]; + } + + private async onSettingsChanged(): Promise { + const ids = new InterpreterDataService(this.context, this.services); + const idata = await ids.getInterpreterData(); + if (!idata || idata.hash !== this.interpreterHash) { + this.interpreterHash = idata ? idata.hash : ''; + await this.restartLanguageServer(); + return; + } + + const excludedFiles = this.getExcludedFiles(); + await this.restartLanguageServerIfArrayChanged(this.excludedFiles, excludedFiles); + + const settings = this.configuration.getSettings(); + const typeshedPaths = this.getTypeshedPaths(settings); + await this.restartLanguageServerIfArrayChanged(this.typeshedPaths, typeshedPaths); + } + + private async restartLanguageServerIfArrayChanged(oldArray: string[], newArray: string[]): Promise { + if (newArray.length !== oldArray.length) { + await this.restartLanguageServer(); + return; + } + + for (let i = 0; i < oldArray.length; i += 1) { + if (oldArray[i] !== newArray[i]) { + await this.restartLanguageServer(); + return; + } + } + } + + private async restartLanguageServer(): Promise { + if (!this.context) { + return; + } + await this.deactivate(); + await this.activate(); + } } diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index 4c55c4a2b3a3..fe3b4d570b7b 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -8,6 +8,7 @@ import { sendTelemetryEvent } from '../telemetry'; import { COMPLETION_ADD_BRACKETS, FORMAT_ON_TYPE } from '../telemetry/constants'; import { isTestExecution } from './constants'; import { + IAnalysisSettings, IAutoCompleteSettings, IFormattingSettings, ILintingSettings, @@ -44,6 +45,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { public workspaceSymbols!: IWorkspaceSymbolSettings; public disableInstallationChecks = false; public globalModuleInstallation = false; + public analysis!: IAnalysisSettings; private workspaceRoot: Uri; private disposables: Disposable[] = []; @@ -147,6 +149,14 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { this.linting = lintingSettings; } + // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion + const analysisSettings = systemVariables.resolveAny(pythonSettings.get('analysis'))!; + if (this.analysis) { + Object.assign(this.analysis, analysisSettings); + } else { + this.analysis = analysisSettings; + } + this.disableInstallationChecks = pythonSettings.get('disableInstallationCheck') === true; this.globalModuleInstallation = pythonSettings.get('globalModuleInstallation') === true; diff --git a/src/client/common/types.ts b/src/client/common/types.ts index c671ae4e3bd5..8af84dd5d772 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -133,6 +133,7 @@ export interface IPythonSettings { readonly envFile: string; readonly disableInstallationChecks: boolean; readonly globalModuleInstallation: boolean; + readonly analysis: IAnalysisSettings; } export interface ISortImportSettings { readonly path: string; @@ -236,13 +237,16 @@ export interface ITerminalSettings { readonly launchArgs: string[]; readonly activateEnvironment: boolean; } -export interface IPythonAnalysisEngineSettings { - readonly showAdvancedMembers: boolean; +export interface IAnalysisSettings { + readonly openFilesOnly: boolean; readonly typeshedPaths: string[]; + readonly errors: string[]; + readonly warnings: string[]; + readonly information: string[]; + readonly disabled: string[]; } export const IConfigurationService = Symbol('IConfigurationService'); - export interface IConfigurationService { getSettings(resource?: Uri): IPythonSettings; isTestExecution(): boolean; From 5a9d16aa5f54c063f937b1c7d2df6df0bf142b4f Mon Sep 17 00:00:00 2001 From: Derek Keeler Date: Mon, 2 Jul 2018 14:54:46 -0700 Subject: [PATCH 381/433] Ensure method parameter tooltip/popup never occurs within strings or comments (#2072) - stop function signature popups in comments - Add tests for signature provider - incorporate Don's vscMock for SignatureHelp - previously expecting signatures within a string...? --- news/2 Fixes/2057.md | 1 + src/client/providers/completionSource.ts | 15 +- src/client/providers/providerUtilities.ts | 19 +- src/client/providers/signatureProvider.ts | 10 +- .../pythonSignatureProvider.unit.test.ts | 248 ++++++++++++++++++ src/test/signature/signature.jedi.test.ts | 10 +- src/test/vscode-mock.ts | 1 + 7 files changed, 276 insertions(+), 28 deletions(-) create mode 100644 news/2 Fixes/2057.md create mode 100644 src/test/providers/pythonSignatureProvider.unit.test.ts diff --git a/news/2 Fixes/2057.md b/news/2 Fixes/2057.md new file mode 100644 index 000000000000..34db4fed4207 --- /dev/null +++ b/news/2 Fixes/2057.md @@ -0,0 +1 @@ +Fix bug where tooltips would popup whenever a comma is typed within a string. diff --git a/src/client/providers/completionSource.ts b/src/client/providers/completionSource.ts index 764f9e570cfd..c287d4fa858c 100644 --- a/src/client/providers/completionSource.ts +++ b/src/client/providers/completionSource.ts @@ -65,25 +65,18 @@ export class CompletionSource { private async getCompletionResult(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken) : Promise { - if (position.character <= 0) { - return undefined; - } - const filename = document.fileName; - const lineText = document.lineAt(position.line).text; - if (lineText.match(/^\s*\/\//)) { - return undefined; - } - // Suppress completion inside string and comments. - if (isPositionInsideStringOrComment(document, position)) { + if (position.character <= 0 || + isPositionInsideStringOrComment(document, position)) { return undefined; } + const type = proxy.CommandType.Completions; const columnIndex = position.character; const source = document.getText(); const cmd: proxy.ICommand = { command: type, - fileName: filename, + fileName: document.fileName, columnIndex: columnIndex, lineIndex: position.line, source: source diff --git a/src/client/providers/providerUtilities.ts b/src/client/providers/providerUtilities.ts index 0a4f8274144d..7ee45ab8e25a 100644 --- a/src/client/providers/providerUtilities.ts +++ b/src/client/providers/providerUtilities.ts @@ -1,31 +1,28 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import * as vscode from 'vscode'; +import { Position, Range, TextDocument } from 'vscode'; import { Tokenizer } from '../language/tokenizer'; import { ITextRangeCollection, IToken, TokenizerMode, TokenType } from '../language/types'; -export function getDocumentTokens(document: vscode.TextDocument, tokenizeTo: vscode.Position, mode: TokenizerMode): ITextRangeCollection { - const text = document.getText(new vscode.Range(new vscode.Position(0, 0), tokenizeTo)); +export function getDocumentTokens(document: TextDocument, tokenizeTo: Position, mode: TokenizerMode): ITextRangeCollection { + const text = document.getText(new Range(new Position(0, 0), tokenizeTo)); return new Tokenizer().tokenize(text, 0, text.length, mode); } -export function isPositionInsideStringOrComment(document: vscode.TextDocument, position: vscode.Position): boolean { +export function isPositionInsideStringOrComment(document: TextDocument, position: Position): boolean { const tokenizeTo = position.translate(1, 0); const tokens = getDocumentTokens(document, tokenizeTo, TokenizerMode.CommentsAndStrings); const offset = document.offsetAt(position); - let index = tokens.getItemContaining(offset); + const index = tokens.getItemContaining(offset - 1); if (index >= 0) { const token = tokens.getItemAt(index); return token.type === TokenType.String || token.type === TokenType.Comment; } - if (offset > 0) { + if (offset > 0 && index >= 0) { // In case position is at the every end of the comment or unterminated string - index = tokens.getItemContaining(offset - 1); - if (index >= 0) { - const token = tokens.getItemAt(index); - return token.end === offset && token.type === TokenType.Comment; - } + const token = tokens.getItemAt(index); + return token.end === offset && token.type === TokenType.Comment; } return false; } diff --git a/src/client/providers/signatureProvider.ts b/src/client/providers/signatureProvider.ts index cf1014296519..f6ea0d65fd6e 100644 --- a/src/client/providers/signatureProvider.ts +++ b/src/client/providers/signatureProvider.ts @@ -14,6 +14,7 @@ import { JediFactory } from '../languageServices/jediProxyFactory'; import { captureTelemetry } from '../telemetry'; import { SIGNATURE } from '../telemetry/constants'; import * as proxy from './jediProxy'; +import { isPositionInsideStringOrComment } from './providerUtilities'; const DOCSTRING_PARAM_PATTERNS = [ '\\s*:type\\s*PARAMNAME:\\s*([^\\n, ]+)', // Sphinx @@ -71,7 +72,7 @@ export class PythonSignatureProvider implements SignatureHelpProvider { if (validParamInfo) { const docLines = def.docstring.splitLines(); - label = docLines.shift().trim(); + label = docLines.shift()!.trim(); documentation = docLines.join(EOL).trim(); } else { if (def.params && def.params.length > 0) { @@ -111,6 +112,13 @@ export class PythonSignatureProvider implements SignatureHelpProvider { } @captureTelemetry(SIGNATURE) public provideSignatureHelp(document: TextDocument, position: Position, token: CancellationToken): Thenable { + // early exit if we're in a string or comment (or in an undefined position) + if (position.character <= 0 || + isPositionInsideStringOrComment(document, position)) + { + return Promise.resolve(new SignatureHelp()); + } + const cmd: proxy.ICommand = { command: proxy.CommandType.Arguments, fileName: document.fileName, diff --git a/src/test/providers/pythonSignatureProvider.unit.test.ts b/src/test/providers/pythonSignatureProvider.unit.test.ts new file mode 100644 index 000000000000..490eff19e9f9 --- /dev/null +++ b/src/test/providers/pythonSignatureProvider.unit.test.ts @@ -0,0 +1,248 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:max-func-body-length + +import { assert, expect, use } from 'chai'; +import * as chaipromise from 'chai-as-promised'; +import * as TypeMoq from 'typemoq'; +import { CancellationToken, Position, SignatureHelp, + TextDocument, TextLine, Uri } from 'vscode'; +import { JediFactory } from '../../client/languageServices/jediProxyFactory'; +import { IArgumentsResult, JediProxyHandler } from '../../client/providers/jediProxy'; +import { isPositionInsideStringOrComment } from '../../client/providers/providerUtilities'; +import { PythonSignatureProvider } from '../../client/providers/signatureProvider'; + +use(chaipromise); + +suite('Signature Provider unit tests', () => { + let pySignatureProvider: PythonSignatureProvider; + let jediHandler: TypeMoq.IMock>; + let argResultItems: IArgumentsResult; + setup(() => { + const jediFactory = TypeMoq.Mock.ofType(JediFactory); + jediHandler = TypeMoq.Mock.ofType>(); + jediFactory.setup(j => j.getJediProxyHandler(TypeMoq.It.isAny())) + .returns(() => jediHandler.object); + pySignatureProvider = new PythonSignatureProvider(jediFactory.object); + argResultItems = { + definitions: [ + { + description: 'The result', + docstring: 'Some docstring goes here.', + name: 'print', + paramindex: 0, + params: [ + { + description: 'Some parameter', + docstring: 'gimme docs', + name: 'param', + value: 'blah' + } + ] + } + ], + requestId: 1 + }; + }); + + function testSignatureReturns(source: string, pos: number): Thenable { + const doc = TypeMoq.Mock.ofType(); + const position = new Position(0, pos); + const lineText = TypeMoq.Mock.ofType(); + const argsResult = TypeMoq.Mock.ofType(); + const cancelToken = TypeMoq.Mock.ofType(); + cancelToken.setup(ct => ct.isCancellationRequested).returns(() => false); + + doc.setup(d => d.fileName).returns(() => ''); + doc.setup(d => d.getText(TypeMoq.It.isAny())).returns(() => source); + doc.setup(d => d.lineAt(TypeMoq.It.isAny())).returns(() => lineText.object); + doc.setup(d => d.offsetAt(TypeMoq.It.isAny())).returns(() => pos - 1); // pos is 1-based + const docUri = TypeMoq.Mock.ofType(); + docUri.setup(u => u.scheme).returns(() => 'http'); + doc.setup(d => d.uri).returns(() => docUri.object); + lineText.setup(l => l.text).returns(() => source); + argsResult.setup(c => c.requestId).returns(() => 1); + argsResult.setup(c => c.definitions).returns(() => argResultItems[0].definitions); + jediHandler.setup(j => j.sendCommand(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => { + return Promise.resolve(argResultItems); + }); + + return pySignatureProvider.provideSignatureHelp(doc.object, position, cancelToken.object); + } + + function testIsInsideStringOrComment(sourceLine: string, sourcePos: number) : boolean { + const textLine: TypeMoq.IMock = TypeMoq.Mock.ofType(); + textLine.setup(t => t.text).returns(() => sourceLine); + const doc: TypeMoq.IMock = TypeMoq.Mock.ofType(); + const pos: Position = new Position(1, sourcePos); + + doc.setup(d => d.fileName).returns(() => ''); + doc.setup(d => d.getText(TypeMoq.It.isAny())).returns(() => sourceLine); + doc.setup(d => d.lineAt(TypeMoq.It.isAny())).returns(() => textLine.object); + doc.setup(d => d.offsetAt(TypeMoq.It.isAny())).returns(() => sourcePos); + + return isPositionInsideStringOrComment(doc.object, pos); + } + + test('Ensure no signature is given within a string.', async () => { + const source = ' print(\'Python is awesome,\')\n'; + const sigHelp: SignatureHelp = await testSignatureReturns(source, 27); + expect(sigHelp).to.not.be.equal(undefined, 'Expected to get a blank signature item back - did the pattern change here?'); + expect(sigHelp.signatures.length).to.equal(0, 'Signature provided for symbols within a string?'); + }); + test('Ensure no signature is given within a line comment.', async () => { + const source = '# print(\'Python is awesome,\')\n'; + const sigHelp: SignatureHelp = await testSignatureReturns(source, 28); + expect(sigHelp).to.not.be.equal(undefined, 'Expected to get a blank signature item back - did the pattern change here?'); + expect(sigHelp.signatures.length).to.equal(0, 'Signature provided for symbols within a full-line comment?'); + }); + test('Ensure no signature is given within a comment tailing a command.', async () => { + const source = ' print(\'Python\') # print(\'is awesome,\')\n'; + const sigHelp: SignatureHelp = await testSignatureReturns(source, 38); + expect(sigHelp).to.not.be.equal(undefined, 'Expected to get a blank signature item back - did the pattern change here?'); + expect(sigHelp.signatures.length).to.equal(0, 'Signature provided for symbols within a trailing comment?'); + }); + test('Ensure signature is given for built-in print command.', async () => { + const source = ' print(\'Python\',)\n'; + let sigHelp: SignatureHelp; + try { + sigHelp = await testSignatureReturns(source, 18); + expect(sigHelp).to.not.equal(undefined, 'Expected to get a blank signature item back - did the pattern change here?'); + expect(sigHelp.signatures.length).to.not.equal(0, 'Expected dummy argresult back from testing our print signature.'); + expect(sigHelp.activeParameter).to.be.equal(0, 'Parameter for print should be the first member of the test argresult\'s params object.'); + expect(sigHelp.activeSignature).to.be.equal(0, 'The signature for print should be the first member of the test argresult.'); + expect(sigHelp.signatures[sigHelp.activeSignature].label).to.be.equal('print(param)', `Expected arg result calls for specific returned signature of \'print(param)\' but we got ${sigHelp.signatures[sigHelp.activeSignature].label}`); + } catch (error) { + assert(false, `Caught exception ${error}`); + } + }); + test('Ensure isPositionInsideStringOrComment is behaving as expected.', () => { + const sourceLine: string = ' print(\'Hello world!\')\n'; + const sourcePos: number = sourceLine.length - 1; + const isInsideStrComment: boolean = testIsInsideStringOrComment(sourceLine, sourcePos); + + expect(isInsideStrComment).to.not.be.equal(true, [ + `Position set to the end of ${sourceLine} but `, + 'is reported as being within a string or comment.'].join('')); + }); + test('Ensure isPositionInsideStringOrComment is behaving as expected at end of source.', () => { + const sourceLine: string = ' print(\'Hello world!\')\n'; + const sourcePos: number = 0; + const isInsideStrComment: boolean = testIsInsideStringOrComment(sourceLine, sourcePos); + + expect(isInsideStrComment).to.not.be.equal(true, [ + `Position set to the end of ${sourceLine} but `, + 'is reported as being within a string or comment.'].join('')); + }); + test('Ensure isPositionInsideStringOrComment is behaving as expected at beginning of source.', () => { + const sourceLine: string = ' print(\'Hello world!\')\n'; + const sourcePos: number = 0; + const isInsideStrComment: boolean = testIsInsideStringOrComment(sourceLine, sourcePos); + + expect(isInsideStrComment).to.not.be.equal(true, [ + `Position set to the beginning of ${sourceLine} but `, + 'is reported as being within a string or comment.'].join('')); + }); + test('Ensure isPositionInsideStringOrComment is behaving as expected within a string.', () => { + const sourceLine: string = ' print(\'Hello world!\')\n'; + const sourcePos: number = 16; + const isInsideStrComment: boolean = testIsInsideStringOrComment(sourceLine, sourcePos); + + expect(isInsideStrComment).to.be.equal(true, [ + `Position set within the string in ${sourceLine} (position ${sourcePos}) but `, + 'is reported as NOT being within a string or comment.'].join('')); + }); + test('Ensure isPositionInsideStringOrComment is behaving as expected immediately before a string.', () => { + const sourceLine: string = ' print(\'Hello world!\')\n'; + const sourcePos: number = 8; + const isInsideStrComment: boolean = testIsInsideStringOrComment(sourceLine, sourcePos); + + expect(isInsideStrComment).to.be.equal(false, [ + `Position set to just before the string in ${sourceLine} (position ${sourcePos}) but `, + 'is reported as being within a string or comment.'].join('')); + }); + test('Ensure isPositionInsideStringOrComment is behaving as expected immediately in a string.', () => { + const sourceLine: string = ' print(\'Hello world!\')\n'; + const sourcePos: number = 9; + const isInsideStrComment: boolean = testIsInsideStringOrComment(sourceLine, sourcePos); + + expect(isInsideStrComment).to.be.equal(true, [ + `Position set to the start of the string in ${sourceLine} (position ${sourcePos}) but `, + 'is reported as being within a string or comment.'].join('')); + }); + test('Ensure isPositionInsideStringOrComment is behaving as expected within a comment.', () => { + const sourceLine: string = '# print(\'Hello world!\')\n'; + const sourcePos: number = 16; + const isInsideStrComment: boolean = testIsInsideStringOrComment(sourceLine, sourcePos); + + expect(isInsideStrComment).to.be.equal(true, [ + `Position set within a full line comment ${sourceLine} (position ${sourcePos}) but `, + 'is reported as NOT being within a string or comment.'].join('')); + }); + test('Ensure isPositionInsideStringOrComment is behaving as expected within a trailing comment.', () => { + const sourceLine: string = ' print(\'Hello world!\') # some comment...\n'; + const sourcePos: number = 34; + const isInsideStrComment: boolean = testIsInsideStringOrComment(sourceLine, sourcePos); + + expect(isInsideStrComment).to.be.equal(true, [ + `Position set within a trailing line comment ${sourceLine} (position ${sourcePos}) but `, + 'is reported as NOT being within a string or comment.'].join('')); + }); + test('Ensure isPositionInsideStringOrComment is behaving as expected at the very end of a trailing comment.', () => { + const sourceLine: string = ' print(\'Hello world!\') # some comment...\n'; + const sourcePos: number = sourceLine.length - 1; + const isInsideStrComment: boolean = testIsInsideStringOrComment(sourceLine, sourcePos); + + expect(isInsideStrComment).to.be.equal(true, [ + `Position set within a trailing line comment ${sourceLine} (position ${sourcePos}) but `, + 'is reported as NOT being within a string or comment.'].join('')); + }); + test('Ensure isPositionInsideStringOrComment is behaving as expected within a multiline string.', () => { + const sourceLine: string = ' stringVal = \'\'\'This is a multiline\nstring that you can use\nto test this stuff out with\neveryday!\'\'\'\n'; + const sourcePos: number = 48; + const isInsideStrComment: boolean = testIsInsideStringOrComment(sourceLine, sourcePos); + + expect(isInsideStrComment).to.be.equal(true, [ + `Position set within a multi-line string ${sourceLine} (position ${sourcePos}) but `, + 'is reported as NOT being within a string or comment.'].join('')); + }); + test('Ensure isPositionInsideStringOrComment is behaving as expected at the very last quote on a multiline string.', () => { + const sourceLine: string = ' stringVal = \'\'\'This is a multiline\nstring that you can use\nto test this stuff out with\neveryday!\'\'\'\n'; + const sourcePos: number = sourceLine.length - 2; // just at the last ' + const isInsideStrComment: boolean = testIsInsideStringOrComment(sourceLine, sourcePos); + + expect(isInsideStrComment).to.be.equal(true, [ + `Position set within a multi-line string ${sourceLine} (position ${sourcePos}) but `, + 'is reported as NOT being within a string or comment.'].join('')); + }); + test('Ensure isPositionInsideStringOrComment is behaving as expected within a multiline string (double-quoted).', () => { + const sourceLine: string = ' stringVal = """This is a multiline\nstring that you can use\nto test this stuff out with\neveryday!"""\n'; + const sourcePos: number = 48; + const isInsideStrComment: boolean = testIsInsideStringOrComment(sourceLine, sourcePos); + + expect(isInsideStrComment).to.be.equal(true, [ + `Position set within a multi-line string ${sourceLine} (position ${sourcePos}) but `, + 'is reported as NOT being within a string or comment.'].join('')); + }); + test('Ensure isPositionInsideStringOrComment is behaving as expected at the very last quote on a multiline string (double-quoted).', () => { + const sourceLine: string = ' stringVal = """This is a multiline\nstring that you can use\nto test this stuff out with\neveryday!"""\n'; + const sourcePos: number = sourceLine.length - 2; // just at the last ' + const isInsideStrComment: boolean = testIsInsideStringOrComment(sourceLine, sourcePos); + + expect(isInsideStrComment).to.be.equal(true, [ + `Position set within a multi-line string ${sourceLine} (position ${sourcePos}) but `, + 'is reported as NOT being within a string or comment.'].join('')); + }); + test('Ensure isPositionInsideStringOrComment is behaving as expected during construction of a multiline string (double-quoted).', () => { + const sourceLine: string = ' stringVal = """This is a multiline\nstring that you can use\nto test this stuff'; + const sourcePos: number = sourceLine.length - 1; // just at the last position in the string before it's termination + const isInsideStrComment: boolean = testIsInsideStringOrComment(sourceLine, sourcePos); + + expect(isInsideStrComment).to.be.equal(true, [ + `Position set within a multi-line string ${sourceLine} (position ${sourcePos}) but `, + 'is reported as NOT being within a string or comment.'].join('')); + }); +}); diff --git a/src/test/signature/signature.jedi.test.ts b/src/test/signature/signature.jedi.test.ts index 1c1d27f57a15..5805f7b56b04 100644 --- a/src/test/signature/signature.jedi.test.ts +++ b/src/test/signature/signature.jedi.test.ts @@ -50,11 +50,11 @@ suite('Signatures (Jedi)', () => { const expected = [ new SignatureHelpResult(5, 11, 0, 0, null), new SignatureHelpResult(5, 12, 1, 0, 'name'), - new SignatureHelpResult(5, 13, 1, 0, 'name'), - new SignatureHelpResult(5, 14, 1, 0, 'name'), - new SignatureHelpResult(5, 15, 1, 0, 'name'), - new SignatureHelpResult(5, 16, 1, 0, 'name'), - new SignatureHelpResult(5, 17, 1, 0, 'name'), + new SignatureHelpResult(5, 13, 0, 0, null), + new SignatureHelpResult(5, 14, 0, 0, null), + new SignatureHelpResult(5, 15, 0, 0, null), + new SignatureHelpResult(5, 16, 0, 0, null), + new SignatureHelpResult(5, 17, 0, 0, null), new SignatureHelpResult(5, 18, 1, 1, 'age'), new SignatureHelpResult(5, 19, 1, 1, 'age'), new SignatureHelpResult(5, 20, 0, 0, null) diff --git a/src/test/vscode-mock.ts b/src/test/vscode-mock.ts index eb401d8a4d54..1500ca249eae 100644 --- a/src/test/vscode-mock.ts +++ b/src/test/vscode-mock.ts @@ -63,6 +63,7 @@ mockedVSCode.SnippetString = vscodeMocks.vscMockExtHostedTypes.SnippetString; mockedVSCode.EventEmitter = vscodeMocks.vscMock.EventEmitter; mockedVSCode.ConfigurationTarget = vscodeMocks.vscMockExtHostedTypes.ConfigurationTarget; mockedVSCode.StatusBarAlignment = vscodeMocks.vscMockExtHostedTypes.StatusBarAlignment; +mockedVSCode.SignatureHelp = vscodeMocks.vscMockExtHostedTypes.SignatureHelp; // This API is used in src/client/telemetry/telemetry.ts const extensions = TypeMoq.Mock.ofType(); From 8c24c44e5f70071528d838eb4f0ab295ee03f688 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 5 Jul 2018 10:44:49 -0700 Subject: [PATCH 382/433] Pin a dependency --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4e36d3de2415..df539d0fd09b 100644 --- a/package.json +++ b/package.json @@ -1960,7 +1960,7 @@ "dependencies": { "arch": "2.1.0", "diff-match-patch": "1.0.0", - "dotenv": "^5.0.1", + "dotenv": "5.0.1", "fs-extra": "4.0.3", "fuzzy": "0.1.3", "get-port": "3.2.0", From 04446721b015a6021f3c0d14ef7a95b480f0f1bb Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Thu, 5 Jul 2018 16:52:04 -0700 Subject: [PATCH 383/433] Update what we need to report to CELA --- .github/release_plan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/release_plan.md b/.github/release_plan.md index 927d060d073b..b8bcf01d536c 100644 --- a/.github/release_plan.md +++ b/.github/release_plan.md @@ -35,7 +35,7 @@ ## Legal - [ ] Announce the lock-down of dependencies for this release -- [ ] Notify CELA of all changes to the [repository](https://github.com/Microsoft/vscode-python/tree/master/pythonFiles) and [distribution dependencies](https://github.com/Microsoft/vscode-python/blob/master/package.json) +- [ ] Notify CELA of all changes to the [repository](https://github.com/Microsoft/vscode-python/tree/master/pythonFiles), [distribution dependencies](https://github.com/Microsoft/vscode-python/blob/master/package.json) (including [ptvsd](https://pypi.org/project/ptvsd/), and [git submodules](https://github.com/Microsoft/vscode-python) (e.g. Typeshed) ## Release a beta version for testing - [ ] Update the [version](https://github.com/Microsoft/vscode-python/blob/master/package.json) to be a `beta` & update the [changelog](https://github.com/Microsoft/vscode-python/blob/master/CHANGELOG.md) From d214d368e4a670976968b51a41fb4d27c5db777d Mon Sep 17 00:00:00 2001 From: Derek Keeler Date: Mon, 9 Jul 2018 11:23:02 -0600 Subject: [PATCH 384/433] Change keymapping for run selection/line in Python terminal (#2077) Make use of 'when-clause' to not conflict with find/replace widgets --- news/2 Fixes/2068.md | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 news/2 Fixes/2068.md diff --git a/news/2 Fixes/2068.md b/news/2 Fixes/2068.md new file mode 100644 index 000000000000..c6ad22b3d449 --- /dev/null +++ b/news/2 Fixes/2068.md @@ -0,0 +1 @@ +Change keyboard shortcut for `Run Selection/Line in Python Terminal` to `Ctrl+Shift+Enter`. diff --git a/package.json b/package.json index df539d0fd09b..8b3e9a372b9b 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ { "command": "python.execSelectionInTerminal", "key": "shift+enter", - "when": "editorFocus && editorLangId == python" + "when": "editorFocus && editorLangId == python && !findInputFocussed && !replaceInputFocussed" } ], "commands": [ From d506bdfd02de9701ec198cf84ebe5c6870083c09 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 9 Jul 2018 16:11:04 -0700 Subject: [PATCH 385/433] Drop the no-response bot --- .github/no-response.yml | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .github/no-response.yml diff --git a/.github/no-response.yml b/.github/no-response.yml deleted file mode 100644 index 75f82d73cb54..000000000000 --- a/.github/no-response.yml +++ /dev/null @@ -1,2 +0,0 @@ -daysUntilClose: 28 -responseRequiredLabel: "needs more info" From e937e8530dbe31448920271c05e39a2a23df8bda Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 9 Jul 2018 16:13:36 -0700 Subject: [PATCH 386/433] Add back in cleaning up `needs more info` issues --- .github/release_plan.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/release_plan.md b/.github/release_plan.md index b8bcf01d536c..891155186b01 100644 --- a/.github/release_plan.md +++ b/.github/release_plan.md @@ -88,3 +88,4 @@ - [ ] Clean up any straggling [fixed issues needing validation](https://github.com/Microsoft/vscode-python/issues?q=label%3A%22validate+fix%22+is%3Aclosed) - [ ] Close the (now) old [milestone](https://github.com/Microsoft/vscode-python/milestones) - [ ] Delete the previous releases' [branch](https://github.com/Microsoft/vscode-python/branches) +- [ ] Go through [`needs more info` issues](https://github.com/Microsoft/vscode-python/issues?q=is%3Aopen+label%3A%22needs+more+info%22+sort%3Aupdated-asc) and close any that have no activity for over a month From 91f557973aec0b5fa20a065ed56c2f3e8708d842 Mon Sep 17 00:00:00 2001 From: Armin Sebastian Date: Tue, 10 Jul 2018 21:03:51 +0300 Subject: [PATCH 387/433] Tweak formatting of lock bot config (#2054) --- .github/lock.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/lock.yml b/.github/lock.yml index 8947d433b798..dde77291a4ef 100644 --- a/.github/lock.yml +++ b/.github/lock.yml @@ -1,3 +1,3 @@ -daysUntilLock:28 +daysUntilLock: 28 lockComment: false only: issues From 38d1dd38da381c3b50cc08f5aa8a203fcfdab1c5 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Tue, 10 Jul 2018 12:30:46 -0700 Subject: [PATCH 388/433] Fix 'formatOnType add unwanted space to *args' (#2083) * LS symbol providers * formatOnType add unwanted space to *args * Add news * Fix floating point number tokenization * Add test * Add news --- news/2 Fixes/2048.md | 1 + news/2 Fixes/2079.md | 1 + src/client/formatters/lineFormatter.ts | 41 +++++++++++++--- src/client/language/tokenizer.ts | 48 +++++++++++++++---- .../format/extension.lineFormatter.test.ts | 8 ++++ src/test/language/tokenizer.test.ts | 20 ++++++++ 6 files changed, 103 insertions(+), 16 deletions(-) create mode 100644 news/2 Fixes/2048.md create mode 100644 news/2 Fixes/2079.md diff --git a/news/2 Fixes/2048.md b/news/2 Fixes/2048.md new file mode 100644 index 000000000000..116b902905e8 --- /dev/null +++ b/news/2 Fixes/2048.md @@ -0,0 +1 @@ +Format on type no longer adds space after * in multiline arguments. \ No newline at end of file diff --git a/news/2 Fixes/2079.md b/news/2 Fixes/2079.md new file mode 100644 index 000000000000..fac1a58aa08b --- /dev/null +++ b/news/2 Fixes/2079.md @@ -0,0 +1 @@ +Format on type is more reliable handling floating point numbers. \ No newline at end of file diff --git a/src/client/formatters/lineFormatter.ts b/src/client/formatters/lineFormatter.ts index 4a5142d873ce..867a115c1a64 100644 --- a/src/client/formatters/lineFormatter.ts +++ b/src/client/formatters/lineFormatter.ts @@ -149,19 +149,16 @@ export class LineFormatter { this.builder.append('*'); return; } + if (this.handleStarOperator(t, prev)) { + return; + } break; default: break; } } else if (t.length === 2) { if (this.text.charCodeAt(t.start) === Char.Asterisk && this.text.charCodeAt(t.start + 1) === Char.Asterisk) { - if (!prev || (prev.type !== TokenType.Identifier && prev.type !== TokenType.Number)) { - this.builder.append('**'); - return; - } - if (prev && this.isKeyword(prev, 'lambda')) { - this.builder.softAppendSpace(); - this.builder.append('**'); + if (this.handleStarOperator(t, prev)) { return; } } @@ -185,6 +182,28 @@ export class LineFormatter { this.builder.softAppendSpace(); } + private handleStarOperator(current: IToken, prev: IToken): boolean { + if (this.text.charCodeAt(current.start) === Char.Asterisk && this.text.charCodeAt(current.start + 1) === Char.Asterisk) { + if (!prev || (prev.type !== TokenType.Identifier && prev.type !== TokenType.Number)) { + this.builder.append('**'); + return true; + } + if (prev && this.isKeyword(prev, 'lambda')) { + this.builder.softAppendSpace(); + this.builder.append('**'); + return true; + } + } + // Check previous line for the **/* condition + const lastLine = this.getPreviousLineTokens(); + const lastToken = lastLine && lastLine.count > 0 ? lastLine.getItemAt(lastLine.count - 1) : undefined; + if (lastToken && (this.isOpenBraceType(lastToken.type) || lastToken.type === TokenType.Comma)) { + this.builder.append(this.text.substring(current.start, current.end)); + return true; + } + return false; + } + private handleEqual(t: IToken, index: number): void { if (this.isMultipleStatements(index) && !this.braceCounter.isOpened(TokenType.OpenBrace)) { // x = 1; x, y = y, x @@ -411,4 +430,12 @@ export class LineFormatter { } return -1; } + + private getPreviousLineTokens(): ITextRangeCollection | undefined { + if (!this.document || this.lineNumber === 0) { + return undefined; // unable to determine + } + const line = this.document.lineAt(this.lineNumber - 1); + return new Tokenizer().tokenize(line.text); + } } diff --git a/src/client/language/tokenizer.ts b/src/client/language/tokenizer.ts index 52a3599f132b..50269ab27638 100644 --- a/src/client/language/tokenizer.ts +++ b/src/client/language/tokenizer.ts @@ -279,15 +279,15 @@ export class Tokenizer implements ITokenizer { } } - // Floating point - if ((this.cs.currentChar >= Char._0 && this.cs.currentChar <= Char._9) || this.cs.currentChar === Char.Period) { - while (!isWhiteSpace(this.cs.currentChar)) { - this.cs.moveNext(); - } - const text = this.cs.getText().substr(start, this.cs.position - start); - if (!isNaN(parseFloat(text))) { - this.tokens.push(new Token(TokenType.Number, start, this.cs.position - start)); - return true; + // Floating point. Sign was already skipped over. + if ((this.cs.currentChar >= Char._0 && this.cs.currentChar <= Char._9) || + (this.cs.currentChar === Char.Period && this.cs.nextChar >= Char._0 && this.cs.nextChar <= Char._9)) { + if (this.skipFloatingPointCandidate(false)) { + const text = this.cs.getText().substr(start, this.cs.position - start); + if (!isNaN(parseFloat(text))) { + this.tokens.push(new Token(TokenType.Number, start, this.cs.position - start)); + return true; + } } } @@ -467,4 +467,34 @@ export class Tokenizer implements ITokenizer { } this.cs.advance(3); } + + private skipFloatingPointCandidate(allowSign: boolean): boolean { + // Determine end of the potential floating point number + const start = this.cs.position; + this.skipFractionalNumber(allowSign); + if (this.cs.position > start) { + if (this.cs.currentChar === Char.e || this.cs.currentChar === Char.E) { + this.cs.moveNext(); // Optional exponent sign + } + this.skipDecimalNumber(true); // skip exponent value + } + return this.cs.position > start; + } + + private skipFractionalNumber(allowSign: boolean): void { + this.skipDecimalNumber(allowSign); + if (this.cs.currentChar === Char.Period) { + this.cs.moveNext(); // Optional period + } + this.skipDecimalNumber(false); + } + + private skipDecimalNumber(allowSign: boolean): void { + if (allowSign && (this.cs.currentChar === Char.Hyphen || this.cs.currentChar === Char.Plus)) { + this.cs.moveNext(); // Optional sign + } + while (isDecimal(this.cs.currentChar)) { + this.cs.moveNext(); // skip integer part + } + } } diff --git a/src/test/format/extension.lineFormatter.test.ts b/src/test/format/extension.lineFormatter.test.ts index 2332aa52d7d8..0cfaaaa180f4 100644 --- a/src/test/format/extension.lineFormatter.test.ts +++ b/src/test/format/extension.lineFormatter.test.ts @@ -134,6 +134,14 @@ suite('Formatting - line formatter', () => { test('lambda arguments', () => { testFormatMultiline('l4= lambda x =lambda y =lambda z= 1: z: y(): x()', 0, 'l4 = lambda x=lambda y=lambda z=1: z: y(): x()'); }); + test('star in multiline arguments', () => { + testFormatMultiline('x = [\n * param1,\n * param2\n]', 1, ' *param1,'); + testFormatMultiline('x = [\n * param1,\n * param2\n]', 2, ' *param2'); + }); + test('arrow operator', () => { + //testFormatMultiline('def f(a, b: 1, e: 3 = 4, f =5, * g: 6, ** k: 11) -> 12: pass', 0, 'def f(a, b: 1, e: 3 = 4, f=5, *g: 6, **k: 11) -> 12: pass'); + testFormatMultiline('def f(a, \n ** k: 11) -> 12: pass', 1, ' **k: 11) -> 12: pass'); + }); test('Multiline function call', () => { testFormatMultiline('def foo(x = 1)', 0, 'def foo(x=1)'); diff --git a/src/test/language/tokenizer.test.ts b/src/test/language/tokenizer.test.ts index 7713b019ab0b..f90da3eeffd0 100644 --- a/src/test/language/tokenizer.test.ts +++ b/src/test/language/tokenizer.test.ts @@ -310,6 +310,26 @@ suite('Language.Tokenizer', () => { assert.equal(tokens.getItemAt(5).type, TokenType.Number); assert.equal(tokens.getItemAt(5).length, 5); }); + test('Floating point numbers with braces', () => { + const t = new Tokenizer(); + const tokens = t.tokenize('(3.0) (.2) (+.3e+12, .4e1; 0)'); + assert.equal(tokens.count, 13); + + assert.equal(tokens.getItemAt(1).type, TokenType.Number); + assert.equal(tokens.getItemAt(1).length, 3); + + assert.equal(tokens.getItemAt(4).type, TokenType.Number); + assert.equal(tokens.getItemAt(4).length, 2); + + assert.equal(tokens.getItemAt(7).type, TokenType.Number); + assert.equal(tokens.getItemAt(7).length, 7); + + assert.equal(tokens.getItemAt(9).type, TokenType.Number); + assert.equal(tokens.getItemAt(9).length, 4); + + assert.equal(tokens.getItemAt(11).type, TokenType.Number); + assert.equal(tokens.getItemAt(11).length, 1); + }); test('Underscore numbers', () => { const t = new Tokenizer(); const tokens = t.tokenize('+1_0_0_0 0_0 .5_00_3e-4 0xCAFE_F00D 10_000_000.0 0b_0011_1111_0100_1110'); From 3a452d64ff483b597559d3645fd06e2ea08c402b Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 10 Jul 2018 15:10:08 -0700 Subject: [PATCH 389/433] Add a step for updating dependencies --- .github/release_plan.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/release_plan.md b/.github/release_plan.md index 891155186b01..e6ae17fd41bf 100644 --- a/.github/release_plan.md +++ b/.github/release_plan.md @@ -5,6 +5,7 @@ - [ ] Go through all [merged pull requests](https://github.com/Microsoft/vscode-python/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged) and add the [`validate fix` label](https://github.com/Microsoft/vscode-python/labels/validate%20fix) as appropriate as well as checking for news entries - [ ] Validate [fixed issues](https://github.com/Microsoft/vscode-python/issues?q=label%3A%22validate+fix%22+is%3Aclosed) - [ ] Triage [unverified issues](https://github.com/Microsoft/vscode-python/labels/needs%20verification) +- [ ] Update pre-existing dependencies as appropriate (npm, Python, git submodule, or otherwise; requires updating CELA) ## Planning - [ ] Evaluate if TypeScript usage needs updating to sync with VS Code's usage From 20621c28b8db4afa5e007f0056eca7cf5706294f Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Tue, 10 Jul 2018 15:12:43 -0700 Subject: [PATCH 390/433] Provide LS analysis progress display in the status bar (#2099) * LS symbol providers * Different ready wait * Progress reporting * Options hookup * Add diagnostic trace logging setting (hidden) --- news/1 Enhancements/1591.md | 1 + src/client/activation/analysis.ts | 32 ++++++++++++--------- src/client/activation/progress.ts | 46 +++++++++++++++++++++++++++++++ src/client/common/types.ts | 1 + 4 files changed, 67 insertions(+), 13 deletions(-) create mode 100644 news/1 Enhancements/1591.md create mode 100644 src/client/activation/progress.ts diff --git a/news/1 Enhancements/1591.md b/news/1 Enhancements/1591.md new file mode 100644 index 000000000000..758c2b0b7234 --- /dev/null +++ b/news/1 Enhancements/1591.md @@ -0,0 +1 @@ +Language server now reports code analysis progress in the status bar. diff --git a/src/client/activation/analysis.ts b/src/client/activation/analysis.ts index 3c50866ae56c..62c040f47e93 100644 --- a/src/client/activation/analysis.ts +++ b/src/client/activation/analysis.ts @@ -22,6 +22,7 @@ import { getTelemetryReporter } from '../telemetry/telemetry'; import { AnalysisEngineDownloader } from './downloader'; import { InterpreterData, InterpreterDataService } from './interpreterDataService'; import { PlatformData } from './platformData'; +import { ProgressReporting } from './progress'; import { IExtensionActivator } from './types'; const PYTHON = 'python'; @@ -49,6 +50,8 @@ export class AnalysisExtensionActivator implements IExtensionActivator { private excludedFiles: string[] = []; private typeshedPaths: string[] = []; private loadExtensionArgs: {} | undefined; + // tslint:disable-next-line:no-unused-variable + private progressReporting: ProgressReporting | undefined; constructor(@inject(IServiceContainer) private readonly services: IServiceContainer) { this.context = this.services.get(IExtensionContext); @@ -134,20 +137,19 @@ export class AnalysisExtensionActivator implements IExtensionActivator { } private async startLanguageClient(): Promise { - this.languageClient!.onReady() - .then(() => { - this.startupCompleted.resolve(); - if (this.loadExtensionArgs) { - this.languageClient!.sendRequest('python/loadExtension', this.loadExtensionArgs); - this.loadExtensionArgs = undefined; - } - }) - .catch(error => this.startupCompleted.reject(error)); - this.context.subscriptions.push(this.languageClient!.start()); - if (isTestExecution()) { - await this.startupCompleted.promise; + await this.serverReady(); + this.progressReporting = new ProgressReporting(this.languageClient!); + } + + private async serverReady(): Promise { + while (!this.languageClient!.initializeResult) { + await new Promise(resolve => setTimeout(resolve, 100)); + } + if (this.loadExtensionArgs) { + this.languageClient!.sendRequest('python/loadExtension', this.loadExtensionArgs); } + this.startupCompleted.resolve(); } private createSimpleLanguageClient(clientOptions: LanguageClientOptions): LanguageClient { @@ -215,6 +217,8 @@ export class AnalysisExtensionActivator implements IExtensionActivator { this.excludedFiles = this.getExcludedFiles(); this.typeshedPaths = this.getTypeshedPaths(settings); + const traceLogging = (settings.analysis && settings.analysis.traceLogging) ? settings.analysis.traceLogging : false; + // Options to control the language client return { // Register the server for Python documents @@ -237,7 +241,9 @@ export class AnalysisExtensionActivator implements IExtensionActivator { searchPaths, typeStubSearchPaths: this.typeshedPaths, excludeFiles: this.excludedFiles, - testEnvironment: isTestExecution() + testEnvironment: isTestExecution(), + analysisUpdates: true, + traceLogging } }; } diff --git a/src/client/activation/progress.ts b/src/client/activation/progress.ts new file mode 100644 index 000000000000..a9c7fa916fcc --- /dev/null +++ b/src/client/activation/progress.ts @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { Progress, ProgressLocation, window } from 'vscode'; +import { Disposable, LanguageClient } from 'vscode-languageclient'; +import { createDeferred, Deferred } from '../common/helpers'; + +export class ProgressReporting { + private statusBarMessage: Disposable | undefined; + private progress: Progress<{ message?: string; increment?: number }> | undefined; + private progressDeferred: Deferred | undefined; + + constructor(private readonly languageClient: LanguageClient) { + this.languageClient.onNotification('python/setStatusBarMessage', (m: string) => { + if (this.statusBarMessage) { + this.statusBarMessage.dispose(); + } + this.statusBarMessage = window.setStatusBarMessage(m); + }); + + this.languageClient.onNotification('python/beginProgress', async _ => { + this.progressDeferred = createDeferred(); + window.withProgress({ + location: ProgressLocation.Window, + title: '' + }, progress => { + this.progress = progress; + return this.progressDeferred!.promise; + }); + }); + + this.languageClient.onNotification('python/reportProgress', (m: string) => { + if (!this.progress) { + return; + } + this.progress.report({ message: m }); + }); + + this.languageClient.onNotification('python/endProgress', _ => { + if (this.progressDeferred) { + this.progressDeferred.resolve(); + this.progressDeferred = undefined; + } + }); + } +} diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 8af84dd5d772..21939d784d23 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -244,6 +244,7 @@ export interface IAnalysisSettings { readonly warnings: string[]; readonly information: string[]; readonly disabled: string[]; + readonly traceLogging: boolean; } export const IConfigurationService = Symbol('IConfigurationService'); From 6b3c80a9500d881840db1332936bd2481e114d5a Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 10 Jul 2018 15:15:34 -0700 Subject: [PATCH 391/433] Add Typeshed license --- ThirdPartyNotices-Distribution.txt | 241 +++++++++++++++++++++++++++++ 1 file changed, 241 insertions(+) diff --git a/ThirdPartyNotices-Distribution.txt b/ThirdPartyNotices-Distribution.txt index 8570d92e913c..762ca4aa3577 100644 --- a/ThirdPartyNotices-Distribution.txt +++ b/ThirdPartyNotices-Distribution.txt @@ -61,6 +61,7 @@ Microsoft Python extension for Visual Studio Code incorporates components from t 46. vscode-extension-telemetry (https://github.com/Microsoft/vscode-extension-telemetry) 47. vscode-languageclient (https://github.com/Microsoft/vscode-languageserver-node) 48. vscode-languageserver (https://github.com/Microsoft/vscode-languageserver-node/) +49. Typeshed (https://github.com/python/typeshed/tree/95eff73ab2092f2c3158198d404a921447172418) %% Arch NOTICES AND INFORMATION BEGIN HERE @@ -2347,6 +2348,246 @@ THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI ========================================= END OF vscode-languageserver NOTICES AND INFORMATION +%% Typeshed NOTICES AND INFORMATION BEGIN HERE +========================================= + +The "typeshed" project is licensed under the terms of the Apache license, as +reproduced below. + += = = = = + +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + += = = = = +Parts of typeshed are licensed under different licenses (like the MIT +license), reproduced below. += = = = = + +The MIT License + +Copyright (c) 2015 Jukka Lehtosalo and contributors + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + += = = = = +========================================= +END OF Typeshed NOTICES AND INFORMATION From d8ceaec11e7a8c3c4052f24ccc622b1bd6b7d97f Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 10 Jul 2018 15:21:41 -0700 Subject: [PATCH 392/433] Have git fetch submodules --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c77a0299e5e4..366ac7a7fb60 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,7 +28,7 @@ ### Setup ```shell -git clone https://github.com/microsoft/vscode-python +git clone --recurse-submodules https://github.com/microsoft/vscode-python cd vscode-python npm install ``` From 31139789f4b0cace76912ab20272978dad6b9021 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 10 Jul 2018 15:50:49 -0700 Subject: [PATCH 393/433] Fixes restarting of debugger (#2105) --- src/client/debugger/mainV2.ts | 99 ++++++++++++++++++++++++++--------- 1 file changed, 75 insertions(+), 24 deletions(-) diff --git a/src/client/debugger/mainV2.ts b/src/client/debugger/mainV2.ts index 2349dee97445..e06382d36ae0 100644 --- a/src/client/debugger/mainV2.ts +++ b/src/client/debugger/mainV2.ts @@ -14,9 +14,8 @@ import { EOL } from 'os'; import * as path from 'path'; import { PassThrough, Writable } from 'stream'; import { Disposable } from 'vscode'; -import { DebugSession, ErrorDestination, logger, OutputEvent, TerminatedEvent } from 'vscode-debugadapter'; +import { DebugSession, ErrorDestination, Event, logger, OutputEvent, Response, TerminatedEvent } from 'vscode-debugadapter'; import { LogLevel } from 'vscode-debugadapter/lib/logger'; -import { Event } from 'vscode-debugadapter/lib/messages'; import { DebugProtocol } from 'vscode-debugprotocol'; import '../../client/common/extensions'; import { noop, sleep } from '../common/core.utils'; @@ -201,6 +200,9 @@ class DebugManager implements Disposable { private ptvsdProcessId?: number; private launchOrAttach?: 'launch' | 'attach'; private terminatedEventSent: boolean = false; + private disconnectResponseSent: boolean = false; + private disconnectRequest?: DebugProtocol.DisconnectRequest; + private restart: boolean = false; private readonly initializeRequestDeferred: Deferred; private get initializeRequest(): Promise { return this.initializeRequestDeferred.promise; @@ -245,6 +247,7 @@ class DebugManager implements Disposable { this.attachRequestDeferred = createDeferred(); } public dispose() { + logger.verbose('main dispose'); this.shutdown().ignoreErrors(); } public async start() { @@ -256,7 +259,11 @@ class DebugManager implements Disposable { this.inputStream.pause(); if (!this.isServerMode) { const currentProcess = this.serviceContainer.get(ICurrentProcess); - currentProcess.on('SIGTERM', this.shutdown); + currentProcess.on('SIGTERM', () => { + if (!this.restart) { + this.shutdown().ignoreErrors(); + } + }); } this.interceptProtocolMessages(); this.startDebugSession(); @@ -269,6 +276,7 @@ class DebugManager implements Disposable { * @private * @memberof DebugManager */ + // tslint:disable-next-line:cyclomatic-complexity private shutdown = async () => { logger.verbose('check and shutdown'); if (this.hasShutdown) { @@ -277,13 +285,8 @@ class DebugManager implements Disposable { this.hasShutdown = true; logger.verbose('shutdown'); - if (this.socket) { - this.throughInputStream.unpipe(this.socket); - this.socket.unpipe(this.throughOutputStream); - } - - if (!this.terminatedEventSent) { - // Possible VS Code has closed its stream. + if (!this.terminatedEventSent && !this.restart) { + // Possible PTVSD died before sending message back. try { logger.verbose('Sending Terminated Event'); this.sendMessage(new TerminatedEvent(), this.outputStream); @@ -295,6 +298,19 @@ class DebugManager implements Disposable { this.terminatedEventSent = true; } + if (!this.disconnectResponseSent && this.restart && this.disconnectRequest) { + // This is a work around for PTVSD bug, else this entire block is unnecessary. + try { + logger.verbose('Sending Disconnect Response'); + this.sendMessage(new Response(this.disconnectRequest, ''), this.outputStream); + } catch (err) { + const message = `Error in sending Disconnect Response: ${err && err.message ? err.message : err.toString()}`; + const details = [message, err && err.name ? err.name : '', err && err.stack ? err.stack : ''].join(EOL); + logger.error(`${message}${EOL}${details}`); + } + this.disconnectResponseSent = true; + } + if (this.launchOrAttach === 'launch' && this.ptvsdProcessId) { logger.verbose('killing process'); try { @@ -303,20 +319,23 @@ class DebugManager implements Disposable { // 2. Also, its possible we manually sent the `Terminated` event above. // Hence we need to wait till VSC receives the above event. await sleep(100); + logger.verbose('Kill process now'); killProcessTree(this.ptvsdProcessId!); } catch { } this.ptvsdProcessId = undefined; } - if (this.debugSession) { - logger.verbose('Shutting down debug session'); - this.debugSession.shutdown(); - } + if (!this.restart) { + if (this.debugSession) { + logger.verbose('Shutting down debug session'); + this.debugSession.shutdown(); + } - logger.verbose('disposing'); - await sleep(100); - // Dispose last, we don't want to dispose the protocol loggers too early. - this.disposables.forEach(disposable => disposable.dispose()); + logger.verbose('disposing'); + await sleep(100); + // Dispose last, we don't want to dispose the protocol loggers too early. + this.disposables.forEach(disposable => disposable.dispose()); + } } private sendMessage(message: DebugProtocol.ProtocolMessage, outputStream: Socket | PassThrough | NodeJS.WriteStream): void { this.protocolMessageWriter.write(outputStream, message); @@ -344,6 +363,7 @@ class DebugManager implements Disposable { this.inputProtocolParser.once('request_initialize', this.onRequestInitialize); this.inputProtocolParser.once('request_launch', this.onRequestLaunch); this.inputProtocolParser.once('request_attach', this.onRequestAttach); + this.inputProtocolParser.once('request_disconnect', this.onRequestDisconnect); this.outputProtocolParser.once('event_terminated', this.onEventTerminated); this.outputProtocolParser.once('response_disconnect', this.onResponseDisconnect); @@ -361,9 +381,14 @@ class DebugManager implements Disposable { // We need to handle both end and error, sometimes the socket will error out without ending (if debugee is killed). // Note, we need a handler for the error event, else nodejs complains when socket gets closed and there are no error handlers. - this.socket.on('end', this.shutdown); - this.socket.on('error', this.shutdown); - + this.socket.on('end', () => { + logger.verbose('Socket End'); + this.shutdown().ignoreErrors(); + }); + this.socket.on('error', () => { + logger.verbose('Socket Error'); + this.shutdown().ignoreErrors(); + }); // Keep track of processid for killing it. if (this.launchOrAttach === 'launch') { const debugSoketProtocolParser = this.serviceContainer.get(IProtocolParser); @@ -377,9 +402,15 @@ class DebugManager implements Disposable { (this.inputStream as any as NodeJS.ReadStream).unpipe(this.debugSessionInputStream); this.debugSessionOutputStream.unpipe(this.outputStream); - this.inputStream.pipe(this.socket!); - this.socket!.pipe(this.throughOutputStream); - this.socket!.pipe(this.outputStream); + // Do not pipe. When restarting the debugger, the socket gets closed, + // In which case, VSC will see this and shutdown the debugger completely. + (this.inputStream as any as NodeJS.ReadStream).on('data', data => { + this.socket.write(data); + }); + this.socket.on('data', (data: string | Buffer) => { + this.throughOutputStream.write(data); + this.outputStream.write(data as string); + }); // Send the launch/attach request to PTVSD and wait for it to reply back. this.sendMessage(attachOrLaunchRequest, this.socket); @@ -388,6 +419,11 @@ class DebugManager implements Disposable { this.sendMessage(await this.initializeRequest, this.socket); } private onRequestInitialize = (request: DebugProtocol.InitializeRequest) => { + this.hasShutdown = false; + this.terminatedEventSent = false; + this.disconnectResponseSent = false; + this.restart = false; + this.disconnectRequest = undefined; this.initializeRequestDeferred.resolve(request); } private onRequestLaunch = (request: DebugProtocol.LaunchRequest) => { @@ -400,6 +436,20 @@ class DebugManager implements Disposable { this.loggingEnabled = (request.arguments as AttachRequestArguments).logToFile === true; this.attachRequestDeferred.resolve(request); } + private onRequestDisconnect = (request: DebugProtocol.DisconnectRequest) => { + this.disconnectRequest = request; + if (this.launchOrAttach === 'attach') { + return; + } + const args = request.arguments as { restart: boolean } | undefined; + if (args && args.restart) { + this.restart = true; + } + + // When VS Code sends a disconnect request, PTVSD replies back with a response. + // Wait for sometime, untill the messages are sent out (remember, we're just intercepting streams here). + setTimeout(this.shutdown, 500); + } private onEventTerminated = async () => { logger.verbose('onEventTerminated'); this.terminatedEventSent = true; @@ -407,6 +457,7 @@ class DebugManager implements Disposable { setTimeout(this.shutdown, 300); } private onResponseDisconnect = async () => { + this.disconnectResponseSent = true; logger.verbose('onResponseDisconnect'); // When VS Code sends a disconnect request, PTVSD replies back with a response, but its upto us to kill the process. // Wait for sometime, untill the messages are sent out (remember, we're just intercepting streams here). From caebcaeab3e2fa47e213e506046225a673dafefa Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 10 Jul 2018 15:53:32 -0700 Subject: [PATCH 394/433] 2018.7.0-beta (#2131) --- CHANGELOG.md | 81 ++++++++++++++++++++++++++++++++++++++++++++ news/2 Fixes/2044.md | 2 +- news/2 Fixes/2048.md | 2 +- news/2 Fixes/2068.md | 3 +- news/2 Fixes/2079.md | 2 +- news/announce.py | 25 ++++++++------ package-lock.json | 2 +- package.json | 2 +- 8 files changed, 103 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41b4d96d5406..d57d36b8f11d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,86 @@ # Changelog +## 2018.7.0-beta (10 July 2018) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: +- [isort 4.3.4](https://pypi.org/project/isort/4.3.4/) +- [jedi 0.12.0](https://pypi.org/project/jedi/0.12.0/) + and [parso 0.2.1](https://pypi.org/project/parso/0.2.1/) +- [ptvsd 3.0.0](https://pypi.org/project/ptvsd/3.0.0/) and [4.1.11a5](https://pypi.org/project/ptvsd/4.1.11a5/) +- [exuberant ctags](http://ctags.sourceforge.net/) (user-installed) +- [rope](https://pypi.org/project/rope/) (user-installed) + +Also thanks to the various projects we provide integrations with which help +make this extension useful: +- Debugging support: + [Django](https://pypi.org/project/Django/), + [Flask](https://pypi.org/project/Flask/), + [gevent](https://pypi.org/project/gevent/), + [Jinja](https://pypi.org/project/Jinja/), + [Pyramid](https://pypi.org/project/pyramid/), + [PySpark](https://pypi.org/project/pyspark/), + [Scrapy](https://pypi.org/project/Scrapy/), + [Watson](https://pypi.org/project/Watson/) +- Formatting: + [autopep8](https://pypi.org/project/autopep8/), + [black](https://pypi.org/project/black/), + [yapf](https://pypi.org/project/yapf/) +- Interpreter support: + [conda](https://conda.io/), + [direnv](https://direnv.net/), + [pipenv](https://pypi.org/project/pipenv/), + [pyenv](https://github.com/pyenv/pyenv), + [venv](https://docs.python.org/3/library/venv.html#module-venv), + [virtualenv](https://pypi.org/project/virtualenv/) +- Linting: + [flake8](https://pypi.org/project/flake8/), + [mypy](https://pypi.org/project/mypy/), + [prospector](https://pypi.org/project/prospector/), + [pylint](https://pypi.org/project/pylint/), + [pydocstyle](https://pypi.org/project/pydocstyle/), + [pylama](https://pypi.org/project/pylama/) +- Testing: + [nose](https://pypi.org/project/nose/), + [pytest](https://pypi.org/project/pytest/), + [unittest](https://docs.python.org/3/library/unittest.html#module-unittest) + +And finally thanks to the [Python](https://www.python.org/) development team and +community for creating a fantastic programming language and community to be a +part of! + +### Enhancements + +1. Language server now reports code analysis progress in the status bar. + ([#1591](https://github.com/Microsoft/vscode-python/issues/1591)) + +### Fixes + +1. Ensure dunder variables are always displayed in code completion when using the new language server. + ([#2013](https://github.com/Microsoft/vscode-python/issues/2013)) +1. Store testId for files & suites during unittest discovery. + ([#2044](https://github.com/Microsoft/vscode-python/issues/2044)) +1. `editor.formatOnType` no longer adds space after `*` in multi-line arguments. + ([#2048](https://github.com/Microsoft/vscode-python/issues/2048)) +1. Fix bug where tooltips would popup whenever a comma is typed within a string. + ([#2057](https://github.com/Microsoft/vscode-python/issues/2057)) +1. Change keyboard shortcut for `Run Selection/Line in Python Terminal` to not + interfere with the Find/Replace dialog box. + ([#2068](https://github.com/Microsoft/vscode-python/issues/2068)) +1. `editor.formatOnType` is more reliable handling floating point numbers. + ([#2079](https://github.com/Microsoft/vscode-python/issues/2079)) + +### Code Health + +1. Removed pre-commit hook that ran unit tests. + ([#1986](https://github.com/Microsoft/vscode-python/issues/1986)) + + + + + ## 2018.6.0 (20 June 2018) ### Thanks diff --git a/news/2 Fixes/2044.md b/news/2 Fixes/2044.md index 6118357acaa2..122287640547 100644 --- a/news/2 Fixes/2044.md +++ b/news/2 Fixes/2044.md @@ -1 +1 @@ -Store testId for files & suites during unittest discovery +Store testId for files & suites during unittest discovery. diff --git a/news/2 Fixes/2048.md b/news/2 Fixes/2048.md index 116b902905e8..1ef400d6de06 100644 --- a/news/2 Fixes/2048.md +++ b/news/2 Fixes/2048.md @@ -1 +1 @@ -Format on type no longer adds space after * in multiline arguments. \ No newline at end of file +`editor.formatOnType` no longer adds space after `*` in multi-line arguments. diff --git a/news/2 Fixes/2068.md b/news/2 Fixes/2068.md index c6ad22b3d449..062dfb4d3654 100644 --- a/news/2 Fixes/2068.md +++ b/news/2 Fixes/2068.md @@ -1 +1,2 @@ -Change keyboard shortcut for `Run Selection/Line in Python Terminal` to `Ctrl+Shift+Enter`. +Change keyboard shortcut for `Run Selection/Line in Python Terminal` to not +interfere with the Find/Replace dialog box. diff --git a/news/2 Fixes/2079.md b/news/2 Fixes/2079.md index fac1a58aa08b..6b0366702a18 100644 --- a/news/2 Fixes/2079.md +++ b/news/2 Fixes/2079.md @@ -1 +1 @@ -Format on type is more reliable handling floating point numbers. \ No newline at end of file +`editor.formatOnType` is more reliable handling floating point numbers. diff --git a/news/announce.py b/news/announce.py index 053b7071e13d..0666a53a2f0c 100644 --- a/news/announce.py +++ b/news/announce.py @@ -3,6 +3,7 @@ Usage: announce [--dry_run | --interim | --final] [] """ +import dataclasses import enum import operator import os @@ -10,7 +11,6 @@ import re import subprocess import sys -import types import docopt @@ -18,12 +18,14 @@ FILENAME_RE = re.compile(r"(?P\d+)(?P-\S+)?\.md") -def NewsEntry(issue_number, description, path): - """Construct a data object for a news entry.""" - # TODO: replace with a dataclass in Python 3.7. - return types.SimpleNamespace( - issue_number=issue_number, description=description, path=path - ) + +@dataclasses.dataclass +class NewsEntry: + """Representation of a news entry.""" + + issue_number: int + description: str + path: pathlib.Path def news_entries(directory): @@ -39,10 +41,13 @@ def news_entries(directory): yield NewsEntry(issue, entry, path) -def SectionTitle(index, title, path): +@dataclasses.dataclass +class SectionTitle: """Create a data object for a section of the changelog.""" - # TODO: replace with a dataclass in Python 3.7. - return types.SimpleNamespace(index=index, title=title, path=path) + + index: int + title: str + path: pathlib.Path def sections(directory): diff --git a/package-lock.json b/package-lock.json index 9e1243f56fc9..e42cc4b1a58e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "python", - "version": "2018.7.0-alpha", + "version": "2018.7.0-beta", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 8b3e9a372b9b..4bc0621b780b 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2018.7.0-alpha", + "version": "2018.7.0-beta", "publisher": "ms-python", "author": { "name": "Microsoft Corporation" From 914bda92c2ea00b04f2f65e89b5ff090e2caed3a Mon Sep 17 00:00:00 2001 From: Derek Keeler Date: Tue, 10 Jul 2018 16:54:56 -0600 Subject: [PATCH 395/433] Fixup badges to point at latest builds in CI (#2130) --- CONTRIBUTING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 366ac7a7fb60..056d551c42ea 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,13 +1,13 @@ # Contributing to the Python extension for Visual Studio Code - + --- -| macOS/Windows CI | Linux CI | Rolling CI | ptvsd master CI | Code Coverage | -|-|-|-|-|-| -|[![Build status - CI](https://vscode-python.visualstudio.com/VSCode-Python/_apis/build/status/VSCode-Python-CI)](https://vscode-python.visualstudio.com/VSCode-Python/VSCode-Python%20Team/_build/results?buildId=375&view=logs) | [![Build Status (Travis)](https://travis-ci.org/Microsoft/vscode-python.svg?branch=master)](https://travis-ci.org/Microsoft/vscode-python) | [![Build status - Rolling](https://vscode-python.visualstudio.com/VSCode-Python/_apis/build/status/VSCode-Python-Rolling-CI)](https://vscode-python.visualstudio.com/3dc2a9b3-d195-4dba-9886-844383409c6c/_build/index?buildId=378) | [![Build status - PTVSD](https://vscode-python.visualstudio.com/VSCode-Python/_apis/build/status/VSCode-Python-ptvsd_master-CI)](https://vscode-python.visualstudio.com/3dc2a9b3-d195-4dba-9886-844383409c6c/_build/index?buildId=361) | [![codecov](https://codecov.io/gh/Microsoft/vscode-python/branch/master/graph/badge.svg)](https://codecov.io/gh/Microsoft/vscode-python)| +| macOS/Windows CI | Linux CI | Rolling CI (macOS/Windows) | Code Coverage | +|-|-|-|-| +|[![Build status](https://vscode-python.visualstudio.com/VSCode-Python/_apis/build/status/VSCode-Python-CI)](https://vscode-python.visualstudio.com/VSCode-Python/_build/latest?definitionId=4) | [![Build Status (Travis)](https://travis-ci.org/Microsoft/vscode-python.svg?branch=master)](https://travis-ci.org/Microsoft/vscode-python) | [![Build status](https://vscode-python.visualstudio.com/VSCode-Python/_apis/build/status/VSCode-Python-Rolling-CI)](https://vscode-python.visualstudio.com/VSCode-Python/_build/latest?definitionId=9) | [![codecov](https://codecov.io/gh/Microsoft/vscode-python/branch/master/graph/badge.svg)](https://codecov.io/gh/Microsoft/vscode-python)| --- # Contributing to Microsoft Python Analysis Engine From 6841d4315e4accbd054262f20a37285a364d10d2 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 10 Jul 2018 15:56:33 -0700 Subject: [PATCH 396/433] Drop label to Travis for development build --- CONTRIBUTING.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 056d551c42ea..14bee772eea4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -172,8 +172,7 @@ build of the extension onto a cloud storage provider. If you are interested in helping us test our development builds or would like to stay ahead of the curve, then please feel free to download and install the extension from the following -[location](https://pvsc.blob.core.windows.net/extension-builds/ms-python-insiders.vsix) -(if the CI build is passing: [![Build Status](https://travis-ci.org/Microsoft/vscode-python.svg?branch=master)](https://travis-ci.org/Microsoft/vscode-python)). +[location](https://pvsc.blob.core.windows.net/extension-builds/ms-python-insiders.vsix). Once you have downloaded the [ms-python-insiders.vsix](https://pvsc.blob.core.windows.net/extension-builds/ms-python-insiders.vsix) file, please follow the instructions on From feecfc1ae68c4b7d1776f73d5ece55e1614fb53d Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 10 Jul 2018 17:07:24 -0700 Subject: [PATCH 397/433] Pass OS type to the debugger (#2129) --- news/3 Code Health/2128.md | 1 + src/client/debugger/Common/Contracts.ts | 3 ++- .../debugger/configProviders/pythonV2Provider.ts | 2 ++ .../debugger/configProvider/provider.attach.test.ts | 10 +++++++--- 4 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 news/3 Code Health/2128.md diff --git a/news/3 Code Health/2128.md b/news/3 Code Health/2128.md new file mode 100644 index 000000000000..22ac6aecbc7a --- /dev/null +++ b/news/3 Code Health/2128.md @@ -0,0 +1 @@ +Pass OS type to the debugger. diff --git a/src/client/debugger/Common/Contracts.ts b/src/client/debugger/Common/Contracts.ts index 78590415714b..416f257dfe70 100644 --- a/src/client/debugger/Common/Contracts.ts +++ b/src/client/debugger/Common/Contracts.ts @@ -48,7 +48,8 @@ export enum DebugOptions { Sudo = 'Sudo', Pyramid = 'Pyramid', FixFilePathCase = 'FixFilePathCase', - WindowsClient = 'WindowsClient' + WindowsClient = 'WindowsClient', + UnixClient = 'UnixClient' } export interface ExceptionHandling { diff --git a/src/client/debugger/configProviders/pythonV2Provider.ts b/src/client/debugger/configProviders/pythonV2Provider.ts index c7be4d331e1a..19f37e07798c 100644 --- a/src/client/debugger/configProviders/pythonV2Provider.ts +++ b/src/client/debugger/configProviders/pythonV2Provider.ts @@ -82,6 +82,8 @@ export class PythonV2DebugConfigurationProvider extends BaseConfigurationProvide } if (this.serviceContainer.get(IPlatformService).isWindows) { this.debugOption(debugOptions, DebugOptions.WindowsClient); + } else { + this.debugOption(debugOptions, DebugOptions.UnixClient); } if (!debugConfiguration.pathMappings) { diff --git a/src/test/debugger/configProvider/provider.attach.test.ts b/src/test/debugger/configProvider/provider.attach.test.ts index d0e358591352..1d2f695daa9a 100644 --- a/src/test/debugger/configProvider/provider.attach.test.ts +++ b/src/test/debugger/configProvider/provider.attach.test.ts @@ -33,9 +33,13 @@ enum OS { let platformService: TypeMoq.IMock; let fileSystem: TypeMoq.IMock; const debugOptionsAvailable = [DebugOptions.RedirectOutput]; - if (os.value === OS.Windows && provider.debugType === 'pythonExperimental') { - debugOptionsAvailable.push(DebugOptions.FixFilePathCase); - debugOptionsAvailable.push(DebugOptions.WindowsClient); + if (provider.debugType === 'pythonExperimental') { + if (os.value === OS.Windows) { + debugOptionsAvailable.push(DebugOptions.FixFilePathCase); + debugOptionsAvailable.push(DebugOptions.WindowsClient); + } else { + debugOptionsAvailable.push(DebugOptions.UnixClient); + } } setup(() => { serviceContainer = TypeMoq.Mock.ofType(); From c20b7e92e96006e6d7b05e8bba6669f93437116d Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Wed, 11 Jul 2018 10:17:39 -0700 Subject: [PATCH 398/433] Only run news tests on Python 3.7 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6c50843fbcaa..57068911313b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -108,7 +108,7 @@ script: yarn run vscode:prepublish; yarn run testPerformance --silent; fi - - if [ "$TRAVIS_PYTHON_VERSION" != "2.7" ]; then + - if [ "$TRAVIS_PYTHON_VERSION" == "3.7" ]; then python3 -m pip install --upgrade -r news/requirements.txt; python3 news/announce.py --dry_run; fi From 9220db5f05173416e41c69ab5f85bad6b3cf5a83 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Wed, 11 Jul 2018 13:03:17 -0700 Subject: [PATCH 399/433] Delete .appveyor.yml --- .appveyor.yml | 90 --------------------------------------------------- 1 file changed, 90 deletions(-) delete mode 100644 .appveyor.yml diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index 08045af6336d..000000000000 --- a/.appveyor.yml +++ /dev/null @@ -1,90 +0,0 @@ -#image: Visual Studio 2017 -#shallow_clone: true - -environment: - matrix: - - PYTHON: "C:\\Python36" - PYTHON_VERSION: "3.6.3" - PYTHON_ARCH: "32" - nodejs_version: "8.9.1" - APPVEYOR: "true" - DEBUGGER_TEST: "true" - - PYTHON: "C:\\Python36" - PYTHON_VERSION: "3.6.3" - PYTHON_ARCH: "32" - nodejs_version: "8.9.1" - APPVEYOR: "true" - DEBUGGER_TEST_RELEASE: "true" - - PYTHON: "C:\\Python36" - PYTHON_VERSION: "3.6.3" - PYTHON_ARCH: "32" - nodejs_version: "8.9.1" - APPVEYOR: "true" - SINGLE_WORKSPACE_TEST: "true" - - PYTHON: "C:\\Python36" - PYTHON_VERSION: "3.6.3" - PYTHON_ARCH: "32" - nodejs_version: "8.9.1" - APPVEYOR: "true" - MULTIROOT_WORKSPACE_TEST: "true" - - PYTHON: "C:\\Python36" - PYTHON_VERSION: "3.6.3" - PYTHON_ARCH: "32" - nodejs_version: "8.9.1" - APPVEYOR: "true" - ANALYSIS_TEST: "true" - -matrix: - allow_failures: - - PYTHON: "C:\\Python36" - PYTHON_VERSION: "3.6.3" - PYTHON_ARCH: "32" - nodejs_version: "8.9.1" - APPVEYOR: "true" - DEBUGGER_TEST: "true" - - PYTHON: "C:\\Python36" - PYTHON_VERSION: "3.6.3" - PYTHON_ARCH: "32" - nodejs_version: "8.9.1" - APPVEYOR: "true" - DEBUGGER_TEST_RELEASE: "true" - -init: - - "ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH%" - -install: - - ps: Install-Product node $env:nodejs_version - - npm i - - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - - python -m pip install -U pip - - pip install -t ./pythonFiles/experimental/ptvsd git+https://github.com/Microsoft/ptvsd/ - - python --version - - python -m easy_install -U setuptools - - "%PYTHON%/Scripts/pip.exe install --upgrade -r requirements.txt" - -build: off -# build_script: -# - git clone https://github.com/MikhailArkhipov/PTVS.git c:/projects/PTVS -# - "cd c:\\projects\\PTVS" -# - git checkout origin/vsc -# - "cd Python\\Product\\VSCode\\AnalysisVsc" -# - "dotnet --info" -# - "dotnet build" -# - "cd c:\\projects\\vscode-python" -# - "xcopy /S /I c:\\projects\\PTVS\\BuildOutput\\VsCode\\raw analysis" - -test_script: - - npm run clean - - npm run vscode:prepublish - - if [%DEBUGGER_TEST%]==[true] ( - npm run testDebugger --silent) - - npm run clean:ptvsd - - pip install -t ./pythonFiles/experimental/ptvsd ptvsd --pre --no-cache-dir - - if [%DEBUGGER_TEST_RELEASE%]==[true] ( - npm run testDebugger --silent) - - if [%SINGLE_WORKSPACE_TEST%]==[true] ( - npm run testSingleWorkspace --silent) - - if [%MULTIROOT_WORKSPACE_TEST%]==[true] ( - npm run testMultiWorkspace --silent) - # - if [%ANALYSIS_TEST%]==[true] ( - # npm run testAnalysisEngine --silent) From d8b750bf51eb7f9ba601bb4de316bc4b5a3c35c7 Mon Sep 17 00:00:00 2001 From: Derek Keeler Date: Thu, 12 Jul 2018 11:23:11 -0600 Subject: [PATCH 400/433] Enable Python 3.7 in Linux (#2082) - Hover over capitalize() text changes in 3.7 - handle floating promise problem (was missing await) - add -k to array in unittest/argsService - pycodestyle is writing future warning to stderr - Add `getMajorMinorVerString` and `getMajorMinorVersion` unittest method - More specific version-test logic for Black test --- .travis.yml | 18 ++++++ src/client/formatters/baseFormatter.ts | 2 +- src/client/linters/baseLinter.ts | 2 +- src/client/unittests/common/types.ts | 5 ++ .../unittest/services/argsService.ts | 2 +- src/test/definitions/hover.jedi.test.ts | 16 ++--- src/test/format/extension.format.test.ts | 28 +++++---- src/test/linters/lint.test.ts | 2 +- src/test/pythonFiles/hover/functionHover.py | 9 +++ .../extension.refactor.extract.var.test.ts | 59 +++++++++++-------- src/test/unittests/serviceRegistry.ts | 23 +++++++- 11 files changed, 118 insertions(+), 48 deletions(-) create mode 100644 src/test/pythonFiles/hover/functionHover.py diff --git a/.travis.yml b/.travis.yml index 57068911313b..52cf8962505f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,6 +30,18 @@ matrix: - os: linux python: "3.6-dev" env: PERFORMANCE_TEST=true + - os: linux + python: "3.7-dev" + env: DEBUGGER_TEST=true + - os: linux + python: "3.7-dev" + env: DEBUGGER_TEST_RELEASE=true + - os: linux + python: "3.7-dev" + env: SINGLE_WORKSPACE_TEST=true + - os: linux + python: "3.7-dev" + env: MULTIROOT_WORKSPACE_TEST=true allow_failures: - os: linux python: "2.7" @@ -43,6 +55,12 @@ matrix: - os: linux python: "3.6-dev" env: DEBUGGER_TEST_RELEASE=true + - os: linux + python: "3.7-dev" + env: DEBUGGER_TEST=true + - os: linux + python: "3.7-dev" + env: DEBUGGER_TEST_RELEASE=true before_install: | if [ $TRAVIS_OS_NAME == "linux" ]; then export CXX="g++-4.9" CC="gcc-4.9" DISPLAY=:99.0; diff --git a/src/client/formatters/baseFormatter.ts b/src/client/formatters/baseFormatter.ts index 21a0ca067145..7fd32abae119 100644 --- a/src/client/formatters/baseFormatter.ts +++ b/src/client/formatters/baseFormatter.ts @@ -57,7 +57,7 @@ export abstract class BaseFormatter { const executionInfo = this.helper.getExecutionInfo(this.product, args, document.uri); executionInfo.args.push(tempFile); const pythonToolsExecutionService = this.serviceContainer.get(IPythonToolExecutionService); - const promise = pythonToolsExecutionService.exec(executionInfo, { cwd, throwOnStdErr: true, token }, document.uri) + const promise = pythonToolsExecutionService.exec(executionInfo, { cwd, throwOnStdErr: false, token }, document.uri) .then(output => output.stdout) .then(data => { if (this.checkCancellation(document.fileName, tempFile, token)) { diff --git a/src/client/linters/baseLinter.ts b/src/client/linters/baseLinter.ts index c163a3366faf..51319c0f581b 100644 --- a/src/client/linters/baseLinter.ts +++ b/src/client/linters/baseLinter.ts @@ -108,7 +108,7 @@ export abstract class BaseLinter implements ILinter { const cwd = this.getWorkspaceRootPath(document); const pythonToolsExecutionService = this.serviceContainer.get(IPythonToolExecutionService); try { - const result = await pythonToolsExecutionService.exec(executionInfo, { cwd, token: cancellation, mergeStdOutErr: true }, document.uri); + const result = await pythonToolsExecutionService.exec(executionInfo, { cwd, token: cancellation, mergeStdOutErr: false }, document.uri); this.displayLinterResultHeader(result.stdout); return await this.parseMessages(result.stdout, document, cancellation, regEx); } catch (error) { diff --git a/src/client/unittests/common/types.ts b/src/client/unittests/common/types.ts index 6f11f95d490f..21995b62655e 100644 --- a/src/client/unittests/common/types.ts +++ b/src/client/unittests/common/types.ts @@ -271,3 +271,8 @@ export const IXUnitParser = Symbol('IXUnitParser'); export interface IXUnitParser { updateResultsFromXmlLogFile(tests: Tests, outputXmlFile: string, passCalculationFormulae: PassCalculationFormulae): Promise; } + +export type PythonVersionInformation = { + major: number; + minor: number; +}; diff --git a/src/client/unittests/unittest/services/argsService.ts b/src/client/unittests/unittest/services/argsService.ts index 6a8cf7b1d625..26b530da23d7 100644 --- a/src/client/unittests/unittest/services/argsService.ts +++ b/src/client/unittests/unittest/services/argsService.ts @@ -7,7 +7,7 @@ import { inject, injectable } from 'inversify'; import { IServiceContainer } from '../../../ioc/types'; import { IArgumentsHelper, IArgumentsService, TestFilter } from '../../types'; -const OptionsWithArguments = ['-p', '-s', '-t', '--pattern', +const OptionsWithArguments = ['-k', '-p', '-s', '-t', '--pattern', '--start-directory', '--top-level-directory']; const OptionsWithoutArguments = ['-b', '-c', '-f', '-h', '-q', '-v', diff --git a/src/test/definitions/hover.jedi.test.ts b/src/test/definitions/hover.jedi.test.ts index 969d7c7f780b..0b47c9425386 100644 --- a/src/test/definitions/hover.jedi.test.ts +++ b/src/test/definitions/hover.jedi.test.ts @@ -13,7 +13,7 @@ const fileThree = path.join(autoCompPath, 'three.py'); const fileEncoding = path.join(autoCompPath, 'four.py'); const fileEncodingUsed = path.join(autoCompPath, 'five.py'); const fileHover = path.join(autoCompPath, 'hoverTest.py'); -const fileStringFormat = path.join(hoverPath, 'stringFormat.py'); +const fileStringFormat = path.join(hoverPath, 'functionHover.py'); // tslint:disable-next-line:max-func-body-length suite('Hover Definition (Jedi)', () => { @@ -264,20 +264,20 @@ suite('Hover Definition (Jedi)', () => { }).then(done, done); }); - test('format().capitalize()', async () => { + test('Hover over method shows proper text.', async () => { const textDocument = await vscode.workspace.openTextDocument(fileStringFormat); await vscode.window.showTextDocument(textDocument); - const position = new vscode.Position(5, 41); + const position = new vscode.Position(8, 4); const def = (await vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position))!; assert.equal(def.length, 1, 'Definition length is incorrect'); assert.equal(def[0].contents.length, 1, 'Only expected one result'); const contents = normalizeMarkedString(def[0].contents[0]); - if (contents.indexOf('def capitalize') === -1) { - assert.fail(contents, '', '\'def capitalize\' is missing', 'compare'); + if (contents.indexOf('def my_func') === -1) { + assert.fail(contents, '', '\'def my_func\' is missing', 'compare'); } - if (contents.indexOf('Return a capitalized version of S') === -1 && - contents.indexOf('Return a copy of the string S with only its first character') === -1) { - assert.fail(contents, '', '\'Return a capitalized version of S/Return a copy of the string S with only its first character\' message missing', 'compare'); + if (contents.indexOf('This is a test.') === -1 && + contents.indexOf('It also includes this text, too.') === -1) { + assert.fail(contents, '', 'Expected custom function text missing', 'compare'); } }); }); diff --git a/src/test/format/extension.format.test.ts b/src/test/format/extension.format.test.ts index db99ec32de41..3fd753f2222b 100644 --- a/src/test/format/extension.format.test.ts +++ b/src/test/format/extension.format.test.ts @@ -1,11 +1,11 @@ import * as fs from 'fs-extra'; import * as path from 'path'; -import * as vscode from 'vscode'; import { CancellationTokenSource, Position, Uri, window, workspace } from 'vscode'; import { IProcessServiceFactory, IPythonExecutionFactory } from '../../client/common/process/types'; import { AutoPep8Formatter } from '../../client/formatters/autoPep8Formatter'; import { BlackFormatter } from '../../client/formatters/blackFormatter'; import { YapfFormatter } from '../../client/formatters/yapfFormatter'; +import { PythonVersionInformation } from '../../client/unittests/common/types'; import { closeActiveWindows, initialize, initializeTest } from '../initialize'; import { MockProcessService } from '../mocks/proc'; import { compareFiles } from '../textUtils'; @@ -36,8 +36,8 @@ suite('Formatting', () => { fs.copySync(originalUnformattedFile, file, { overwrite: true }); }); fs.ensureDirSync(path.dirname(autoPep8FileToFormat)); - const pythonProcess = await ioc.serviceContainer.get(IPythonExecutionFactory).create({ resource: vscode.Uri.file(workspaceRootPath) }); - const py2 = await ioc.getPythonMajorVersion(vscode.Uri.parse(originalUnformattedFile)) === 2; + const pythonProcess = await ioc.serviceContainer.get(IPythonExecutionFactory).create({ resource: Uri.file(workspaceRootPath) }); + const py2 = await ioc.getPythonMajorVersion(Uri.parse(originalUnformattedFile)) === 2; const yapf = pythonProcess.execModule('yapf', [originalUnformattedFile], { cwd: workspaceRootPath }); const autoPep8 = pythonProcess.execModule('autopep8', [originalUnformattedFile], { cwd: workspaceRootPath }); const formatters = [yapf, autoPep8]; @@ -99,26 +99,34 @@ suite('Formatting', () => { } async function testFormatting(formatter: AutoPep8Formatter | BlackFormatter | YapfFormatter, formattedContents: string, fileToFormat: string, outputFileName: string) { - const textDocument = await vscode.workspace.openTextDocument(fileToFormat); - const textEditor = await vscode.window.showTextDocument(textDocument); + const textDocument = await workspace.openTextDocument(fileToFormat); + const textEditor = await window.showTextDocument(textDocument); const options = { insertSpaces: textEditor.options.insertSpaces! as boolean, tabSize: textEditor.options.tabSize! as number }; await injectFormatOutput(outputFileName); - const edits = await formatter.formatDocument(textDocument, options, new vscode.CancellationTokenSource().token); + const edits = await formatter.formatDocument(textDocument, options, new CancellationTokenSource().token); await textEditor.edit(editBuilder => { edits.forEach(edit => editBuilder.replace(edit.range, edit.newText)); }); compareFiles(formattedContents, textEditor.document.getText()); } - test('AutoPep8', async () => testFormatting(new AutoPep8Formatter(ioc.serviceContainer), formattedAutoPep8, autoPep8FileToFormat, 'autopep8.output')); + test('AutoPep8', async () => { + await testFormatting( + new AutoPep8Formatter(ioc.serviceContainer), + formattedAutoPep8, + autoPep8FileToFormat, + 'autopep8.output'); + }); + // tslint:disable-next-line:no-function-expression test('Black', async function () { - if (await ioc.getPythonMajorVersion(vscode.Uri.parse(blackFileToFormat)) === 2) { + const pyVersion: PythonVersionInformation = await ioc.getPythonMajorMinorVersion(Uri.parse(blackFileToFormat)); + + if (pyVersion && (pyVersion.major < 3 || (pyVersion.major === 3 && pyVersion.minor < 6))) { // tslint:disable-next-line:no-invalid-this return this.skip(); } - await testFormatting(new BlackFormatter(ioc.serviceContainer), formattedBlack, blackFileToFormat, 'black.output'); }); test('Yapf', async () => testFormatting(new YapfFormatter(ioc.serviceContainer), formattedYapf, yapfFileToFormat, 'yapf.output')); @@ -154,7 +162,7 @@ suite('Formatting', () => { const options = { insertSpaces: textEditor.options.insertSpaces! as boolean, tabSize: 1 }; const formatter = new YapfFormatter(ioc.serviceContainer); - const edits = await formatter.formatDocument(textDocument, options, new vscode.CancellationTokenSource().token); + const edits = await formatter.formatDocument(textDocument, options, new CancellationTokenSource().token); await textEditor.edit(editBuilder => { edits.forEach(edit => editBuilder.replace(edit.range, edit.newText)); }); diff --git a/src/test/linters/lint.test.ts b/src/test/linters/lint.test.ts index 254fb3ba6ba2..737a2686729c 100644 --- a/src/test/linters/lint.test.ts +++ b/src/test/linters/lint.test.ts @@ -95,7 +95,7 @@ const filteredPep88MessagesToBeReturned: ILintMessage[] = [ ]; // tslint:disable-next-line:max-func-body-length -suite('Linting', () => { +suite('Linting - General Tests', () => { let ioc: UnitTestIocContainer; let linterManager: ILinterManager; let configService: IConfigurationService; diff --git a/src/test/pythonFiles/hover/functionHover.py b/src/test/pythonFiles/hover/functionHover.py new file mode 100644 index 000000000000..a0f765a5a41f --- /dev/null +++ b/src/test/pythonFiles/hover/functionHover.py @@ -0,0 +1,9 @@ +def my_func(): + """ + This is a test. + + It also includes this text, too. + """ + pass + +my_func() diff --git a/src/test/refactor/extension.refactor.extract.var.test.ts b/src/test/refactor/extension.refactor.extract.var.test.ts index 126ef1a3fee9..320d277de524 100644 --- a/src/test/refactor/extension.refactor.extract.var.test.ts +++ b/src/test/refactor/extension.refactor.extract.var.test.ts @@ -3,12 +3,13 @@ import * as assert from 'assert'; import * as fs from 'fs-extra'; import * as path from 'path'; -import * as vscode from 'vscode'; -import { Position } from 'vscode'; +import { commands, Position, Range, Selection, TextEditorCursorStyle, TextEditorLineNumbersStyle, TextEditorOptions, Uri, window, workspace } from 'vscode'; import { PythonSettings } from '../../client/common/configSettings'; import { getTextEditsFromPatch } from '../../client/common/editor'; import { extractVariable } from '../../client/providers/simpleRefactorProvider'; import { RefactorProxy } from '../../client/refactor/proxy'; +import { PythonVersionInformation } from '../../client/unittests/common/types'; +import { rootWorkspaceUri } from '../common'; import { UnitTestIocContainer } from '../unittests/serviceRegistry'; import { closeActiveWindows, initialize, initializeTest, IS_CI_SERVER } from './../initialize'; import { MockOutputChannel } from './../mockClasses'; @@ -23,13 +24,13 @@ interface RenameResponse { suite('Variable Extraction', () => { // Hack hac hack - const oldExecuteCommand = vscode.commands.executeCommand; - const options: vscode.TextEditorOptions = { cursorStyle: vscode.TextEditorCursorStyle.Line, insertSpaces: true, lineNumbers: vscode.TextEditorLineNumbersStyle.Off, tabSize: 4 }; + const oldExecuteCommand = commands.executeCommand; + const options: TextEditorOptions = { cursorStyle: TextEditorCursorStyle.Line, insertSpaces: true, lineNumbers: TextEditorLineNumbersStyle.Off, tabSize: 4 }; let refactorTargetFile = ''; let ioc: UnitTestIocContainer; suiteSetup(initialize); suiteTeardown(() => { - vscode.commands.executeCommand = oldExecuteCommand; + commands.executeCommand = oldExecuteCommand; return closeActiveWindows(); }); setup(async () => { @@ -37,10 +38,10 @@ suite('Variable Extraction', () => { refactorTargetFile = path.join(refactorTargetFileDir, `refactor${new Date().getTime()}.py`); fs.copySync(refactorSourceFile, refactorTargetFile, { overwrite: true }); await initializeTest(); - (vscode).commands.executeCommand = (cmd) => Promise.resolve(); + (commands).executeCommand = (cmd) => Promise.resolve(); }); teardown(async () => { - vscode.commands.executeCommand = oldExecuteCommand; + commands.executeCommand = oldExecuteCommand; try { await fs.unlink(refactorTargetFile); } catch { } @@ -55,12 +56,12 @@ suite('Variable Extraction', () => { } async function testingVariableExtraction(shouldError: boolean, startPos: Position, endPos: Position): Promise { - const pythonSettings = PythonSettings.getInstance(vscode.Uri.file(refactorTargetFile)); - const rangeOfTextToExtract = new vscode.Range(startPos, endPos); + const pythonSettings = PythonSettings.getInstance(Uri.file(refactorTargetFile)); + const rangeOfTextToExtract = new Range(startPos, endPos); const proxy = new RefactorProxy(EXTENSION_DIR, pythonSettings, path.dirname(refactorTargetFile), ioc.serviceContainer); const DIFF = '--- a/refactor.py\n+++ b/refactor.py\n@@ -232,7 +232,8 @@\n sys.stdout.flush()\n \n def watch(self):\n- self._write_response("STARTED")\n+ myNewVariable = "STARTED"\n+ self._write_response(myNewVariable)\n while True:\n try:\n self._process_request(self._input.readline())\n'; - const mockTextDoc = await vscode.workspace.openTextDocument(refactorTargetFile); + const mockTextDoc = await workspace.openTextDocument(refactorTargetFile); const expectedTextEdits = getTextEditsFromPatch(mockTextDoc.getText(), DIFF); try { const response = await proxy.extractVariable(mockTextDoc, 'myNewVariable', refactorTargetFile, rangeOfTextToExtract, options); @@ -81,27 +82,35 @@ suite('Variable Extraction', () => { } } - test('Extract Variable', async () => { - const startPos = new vscode.Position(234, 29); - const endPos = new vscode.Position(234, 38); - await testingVariableExtraction(false, startPos, endPos); + // tslint:disable-next-line:no-function-expression + test('Extract Variable', async function () { + const pyVersion: PythonVersionInformation = await ioc.getPythonMajorMinorVersion(rootWorkspaceUri); + + if (pyVersion.major === 3 && pyVersion.minor === 7) { + // tslint:disable-next-line:no-invalid-this + return this.skip(); + } else { + const startPos = new Position(234, 29); + const endPos = new Position(234, 38); + await testingVariableExtraction(false, startPos, endPos); + } }); test('Extract Variable fails if whole string not selected', async () => { - const startPos = new vscode.Position(234, 20); - const endPos = new vscode.Position(234, 38); + const startPos = new Position(234, 20); + const endPos = new Position(234, 38); await testingVariableExtraction(true, startPos, endPos); }); async function testingVariableExtractionEndToEnd(shouldError: boolean, startPos: Position, endPos: Position): Promise { const ch = new MockOutputChannel('Python'); - const rangeOfTextToExtract = new vscode.Range(startPos, endPos); + const rangeOfTextToExtract = new Range(startPos, endPos); - const textDocument = await vscode.workspace.openTextDocument(refactorTargetFile); - const editor = await vscode.window.showTextDocument(textDocument); + const textDocument = await workspace.openTextDocument(refactorTargetFile); + const editor = await window.showTextDocument(textDocument); - editor.selections = [new vscode.Selection(rangeOfTextToExtract.start, rangeOfTextToExtract.end)]; - editor.selection = new vscode.Selection(rangeOfTextToExtract.start, rangeOfTextToExtract.end); + editor.selections = [new Selection(rangeOfTextToExtract.start, rangeOfTextToExtract.end)]; + editor.selection = new Selection(rangeOfTextToExtract.start, rangeOfTextToExtract.end); try { await extractVariable(EXTENSION_DIR, editor, rangeOfTextToExtract, ch, ioc.serviceContainer); if (shouldError) { @@ -125,15 +134,15 @@ suite('Variable Extraction', () => { // This test fails on linux (text document not getting updated in time) if (!IS_CI_SERVER) { test('Extract Variable (end to end)', async () => { - const startPos = new vscode.Position(234, 29); - const endPos = new vscode.Position(234, 38); + const startPos = new Position(234, 29); + const endPos = new Position(234, 38); await testingVariableExtractionEndToEnd(false, startPos, endPos); }); } test('Extract Variable fails if whole string not selected (end to end)', async () => { - const startPos = new vscode.Position(234, 20); - const endPos = new vscode.Position(234, 38); + const startPos = new Position(234, 20); + const endPos = new Position(234, 38); await testingVariableExtractionEndToEnd(true, startPos, endPos); }); }); diff --git a/src/test/unittests/serviceRegistry.ts b/src/test/unittests/serviceRegistry.ts index 88b73407a1d1..f73fbecad906 100644 --- a/src/test/unittests/serviceRegistry.ts +++ b/src/test/unittests/serviceRegistry.ts @@ -12,7 +12,9 @@ import { TestsHelper } from '../../client/unittests/common/testUtils'; import { TestFlatteningVisitor } from '../../client/unittests/common/testVisitors/flatteningVisitor'; import { TestFolderGenerationVisitor } from '../../client/unittests/common/testVisitors/folderGenerationVisitor'; import { TestResultResetVisitor } from '../../client/unittests/common/testVisitors/resultResetVisitor'; -import { ITestResultsService, ITestsHelper, ITestsParser, ITestVisitor, IUnitTestSocketServer, TestProvider } from '../../client/unittests/common/types'; +import { ITestResultsService, ITestsHelper, ITestsParser, + ITestVisitor, IUnitTestSocketServer, + PythonVersionInformation, TestProvider } from '../../client/unittests/common/types'; // tslint:disable-next-line:no-duplicate-imports import { ITestCollectionStorageService, ITestDiscoveryService, ITestManager, ITestManagerFactory, ITestManagerService, ITestManagerServiceFactory } from '../../client/unittests/common/types'; import { TestManager as NoseTestManager } from '../../client/unittests/nosetest/main'; @@ -36,6 +38,25 @@ export class UnitTestIocContainer extends IocContainer { .then(pythonProcess => pythonProcess.exec(['-c', 'import sys;print(sys.version_info[0])'], {})) .then(output => parseInt(output.stdout.trim(), 10)); } + + public getPythonMajorMinorVersionString(resource: Uri): Promise { + return this.serviceContainer.get(IPythonExecutionFactory).create({ resource }) + .then(pythonProcess => pythonProcess.exec(['-c', 'import sys;print("{0}.{1}".format(*sys.version_info[:2]))'], {})) + .then(output => output.stdout.trim()); + } + + public getPythonMajorMinorVersion(resource: Uri): Promise { + return this.serviceContainer.get(IPythonExecutionFactory).create({ resource }) + .then(pythonProcess => pythonProcess.exec(['-c', 'import sys;print("{0}|{1}".format(*sys.version_info[:2]))'], {})) + .then(output => { + const versionString: string = output.stdout.trim(); + const versionInfo: string[] = versionString.split('|'); + return { + major: parseInt(versionInfo[0].trim(), 10), + minor: parseInt(versionInfo[1].trim(), 10) + }; + }); + } public registerTestVisitors() { this.serviceManager.add(ITestVisitor, TestFlatteningVisitor, 'TestFlatteningVisitor'); this.serviceManager.add(ITestVisitor, TestFolderGenerationVisitor, 'TestFolderGenerationVisitor'); From d7b317b94b830482aa69c813e923a42ae5fe687c Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Thu, 12 Jul 2018 11:45:46 -0700 Subject: [PATCH 401/433] WIP: Rename LS to 'Microsoft Python Language Server' (#2122) * LS symbol providers * Different ready wait * Progress reporting * Options hookup * Add diagnostic trace logging setting (hidden) * Rename analysis engine -> language server * Fix language server binary name * Finish renames * Add news * Undo * Baseline update * Rename and baseline * Fix contributions * Baseline --- .gitignore | 2 +- .vscode/launch.json | 4 +- .vscodeignore | 2 +- ...IS.md => CONTRIBUTING - LANGUAGE SERVER.md | 10 +- news/1 Enhancements/2107.md | 2 + src/client/activation/activationService.ts | 10 +- src/client/activation/downloader.ts | 10 +- src/client/activation/{classic.ts => jedi.ts} | 148 ++-- .../{analysis.ts => languageServer.ts} | 648 +++++++++--------- ...ngineHashes.ts => languageServerHashes.ts} | 20 +- src/client/activation/platformData.ts | 24 +- src/client/activation/serviceRegistry.ts | 8 +- src/client/common/configSettings.ts | 4 +- src/client/common/constants.ts | 4 +- src/client/common/types.ts | 2 +- src/client/telemetry/constants.ts | 8 +- .../activation/activationService.unit.test.ts | 4 +- ...s.ptvs.test.ts => excludeFiles.ls.test.ts} | 6 +- src/test/activation/platformData.test.ts | 4 +- src/test/analysisEngineTest.ts | 4 +- src/test/autocomplete/base.test.ts | 10 +- src/test/autocomplete/pep484.test.ts | 4 +- src/test/autocomplete/pep526.test.ts | 4 +- src/test/constants.ts | 4 +- src/test/definitions/hover.jedi.test.ts | 4 +- .../{hover.ptvs.test.ts => hover.ls.test.ts} | 480 ++++++------- src/test/definitions/navigation.test.ts | 36 +- src/test/definitions/parallel.jedi.test.ts | 4 +- ...allel.ptvs.test.ts => parallel.ls.test.ts} | 114 +-- src/test/performance/load.perf.test.ts | 12 +- src/test/performanceTest.ts | 24 +- src/test/signature/signature.jedi.test.ts | 4 +- ...ture.ptvs.test.ts => signature.ls.test.ts} | 6 +- 33 files changed, 817 insertions(+), 813 deletions(-) rename CONTRIBUTING - PYTHON_ANALYSIS.md => CONTRIBUTING - LANGUAGE SERVER.md (88%) create mode 100644 news/1 Enhancements/2107.md rename src/client/activation/{classic.ts => jedi.ts} (96%) rename src/client/activation/{analysis.ts => languageServer.ts} (91%) rename src/client/activation/{analysisEngineHashes.ts => languageServerHashes.ts} (51%) rename src/test/activation/{excludeFiles.ptvs.test.ts => excludeFiles.ls.test.ts} (96%) rename src/test/definitions/{hover.ptvs.test.ts => hover.ls.test.ts} (92%) rename src/test/definitions/{parallel.ptvs.test.ts => parallel.ls.test.ts} (92%) rename src/test/signature/{signature.ptvs.test.ts => signature.ls.test.ts} (97%) diff --git a/.gitignore b/.gitignore index c3e5da568e27..c6cd087c91ce 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,7 @@ coverage/ .venv pythonFiles/experimental/ptvsd/** debug_coverage*/** -analysis/** +languageServer/** bin/** obj/** .pytest_cache diff --git a/.vscode/launch.json b/.vscode/launch.json index 415804510f82..4a1a1f9bd12a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -104,7 +104,7 @@ "preLaunchTask": "Compile" }, { - "name": "Launch Analysis Engine Tests", + "name": "Launch Language Server Tests", "type": "extensionHost", "request": "launch", "runtimeExecutable": "${execPath}", @@ -120,7 +120,7 @@ ], "preLaunchTask": "Compile", "env": { - "VSC_PYTHON_ANALYSIS": "1" + "VSC_PYTHON_LANGUAGE_SERVER": "1" } }, { diff --git a/.vscodeignore b/.vscodeignore index 2bae3e5be984..8df1ffe5bf9f 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -26,7 +26,7 @@ yarn.lock .nvm/** .vscode/** .vscode-test/** -analysis/publish*.* +languageServer/publish*.* bin/** BuildOutput/** coverage/** diff --git a/CONTRIBUTING - PYTHON_ANALYSIS.md b/CONTRIBUTING - LANGUAGE SERVER.md similarity index 88% rename from CONTRIBUTING - PYTHON_ANALYSIS.md rename to CONTRIBUTING - LANGUAGE SERVER.md index 1b67cd884e6a..116b668e1723 100644 --- a/CONTRIBUTING - PYTHON_ANALYSIS.md +++ b/CONTRIBUTING - LANGUAGE SERVER.md @@ -1,4 +1,4 @@ -# Contributing to Microsoft Python Analysis Engine +# Contributing to Microsoft Python Language Server [![Contributing to Python Tools for Visual Studio](https://github.com/Microsoft/PTVS/blob/master/CONTRIBUTING.md)] [![Build Status (Travis)](https://travis-ci.org/Microsoft/vscode-python.svg?branch=master)](https://travis-ci.org/Microsoft/vscode-python) [![Build status (AppVeyor)](https://ci.appveyor.com/api/projects/status/s0pt8d79gqw222j7?svg=true)](https://ci.appveyor.com/project/DonJayamanne/vscode-python-v3vd6) [![codecov](https://codecov.io/gh/Microsoft/vscode-python/branch/master/graph/badge.svg)](https://codecov.io/gh/Microsoft/vscode-python) @@ -30,9 +30,9 @@ Visual Studio 2017: 1. Open solution in Python/Product/VsCode 2. Build AnalysisVsc project 3. Binaries arrive in *Python/BuildOutput/VsCode/raw* -4. Delete contents of the *analysis* folder in the Python Extension folder -5. Copy *.dll, *.pdb, *.json fron *Python/BuildOutput/VsCode/raw* to *analysis* -6. In VS Code set setting *python.downloadCodeAnalysis* to *false* +4. Delete contents of the *languageServer* folder in the Python Extension folder +5. Copy *.dll, *.pdb, *.json fron *Python/BuildOutput/VsCode/raw* to *languageServer* +6. In VS Code set setting *python.downloadLanguageServer* to *false* 7. In VS Code set setting *python.jediEnabled* to *false* ### Debugging code in Python Extension to VS Code @@ -44,7 +44,7 @@ Folow regular TypeScript debugging steps 3. Python Analysis Engine code is in *Python/Product/VsCode/Analysis* 4. Run extension from VS Code 5. In the instance with C# code select Dotnet Attach launch task. -6. Attach to *dotnet* process running *Microsoft.PythonTools.VsCode.dll* +6. Attach to *dotnet* process running *Microsoft.Python.languageServer.dll* On Windows you can also attach from Visual Studio 2017. diff --git a/news/1 Enhancements/2107.md b/news/1 Enhancements/2107.md new file mode 100644 index 000000000000..22014105dda2 --- /dev/null +++ b/news/1 Enhancements/2107.md @@ -0,0 +1,2 @@ +Messages changes to reflect name of the language server: 'Microsoft Python Language Server'. +Folder name changed from 'analysis' to 'languageServer'. \ No newline at end of file diff --git a/src/client/activation/activationService.ts b/src/client/activation/activationService.ts index 1f8ff2d754ff..73af6ba7cd0b 100644 --- a/src/client/activation/activationService.ts +++ b/src/client/activation/activationService.ts @@ -6,7 +6,7 @@ import { inject, injectable } from 'inversify'; import { ConfigurationChangeEvent, Disposable, OutputChannel, Uri } from 'vscode'; import { IApplicationShell, ICommandManager, IWorkspaceService } from '../common/application/types'; -import { isPythonAnalysisEngineTest, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; +import { isLanguageServerTest, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; import '../common/extensions'; import { IConfigurationService, IDisposableRegistry, IOutputChannel, IPythonSettings } from '../common/types'; import { IServiceContainer } from '../ioc/types'; @@ -38,8 +38,8 @@ export class ExtensionActivationService implements IExtensionActivationService, const jedi = this.useJedi(); - const engineName = jedi ? 'classic analysis engine' : 'analysis engine'; - this.output.appendLine(`Starting the ${engineName}.`); + const engineName = jedi ? 'Jedi Python language engine' : 'Microsoft Python language server'; + this.output.appendLine(`Starting ${engineName}.`); const activatorName = jedi ? ExtensionActivators.Jedi : ExtensionActivators.DotNet; const activator = this.serviceContainer.get(IExtensionActivator, activatorName); this.currentActivator = { jedi, activator }; @@ -61,7 +61,7 @@ export class ExtensionActivationService implements IExtensionActivationService, return; } - const item = await this.appShell.showInformationMessage('Please reload the window switching between the analysis engines.', 'Reload'); + const item = await this.appShell.showInformationMessage('Please reload the window switching between language engines.', 'Reload'); if (item === 'Reload') { this.serviceContainer.get(ICommandManager).executeCommand('workbench.action.reloadWindow'); } @@ -70,6 +70,6 @@ export class ExtensionActivationService implements IExtensionActivationService, const workspacesUris: (Uri | undefined)[] = this.workspaceService.hasWorkspaceFolders ? this.workspaceService.workspaceFolders!.map(item => item.uri) : [undefined]; const configuraionService = this.serviceContainer.get(IConfigurationService); const jediEnabledForAnyWorkspace = workspacesUris.filter(uri => configuraionService.getSettings(uri).jediEnabled).length > 0; - return !isPythonAnalysisEngineTest() && jediEnabledForAnyWorkspace; + return !isLanguageServerTest() && jediEnabledForAnyWorkspace; } } diff --git a/src/client/activation/downloader.ts b/src/client/activation/downloader.ts index adf5b81afbf5..59f9fd91a8db 100644 --- a/src/client/activation/downloader.ts +++ b/src/client/activation/downloader.ts @@ -17,12 +17,12 @@ import { PlatformData } from './platformData'; // tslint:disable-next-line:no-require-imports no-var-requires const StreamZip = require('node-stream-zip'); -const downloadUriPrefix = 'https://pvsc.blob.core.windows.net/python-analysis'; -const downloadBaseFileName = 'Python-Analysis-VSCode'; +const downloadUriPrefix = 'https://pvsc.blob.core.windows.net/python-language-server'; +const downloadBaseFileName = 'Python-Language-Server'; const downloadVersion = '0.1.0'; const downloadFileExtension = '.nupkg'; -export class AnalysisEngineDownloader { +export class LanguageServerDownloader { private readonly output: OutputChannel; private readonly platform: IPlatformService; private readonly platformData: PlatformData; @@ -35,7 +35,7 @@ export class AnalysisEngineDownloader { this.platformData = new PlatformData(this.platform, this.fs); } - public async downloadAnalysisEngine(context: IExtensionContext): Promise { + public async downloadLanguageServer(context: IExtensionContext): Promise { const platformString = await this.platformData.getPlatformName(); const enginePackageFileName = `${downloadBaseFileName}-${platformString}.${downloadVersion}${downloadFileExtension}`; @@ -142,6 +142,8 @@ export class AnalysisEngineDownloader { }).on('extract', (entry, file) => { extractedFiles += 1; progress.report({ message: `${title}${Math.round(100 * extractedFiles / totalFiles)}%` }); + }).on('error', e => { + deferred.reject(e); }); return deferred.promise; }); diff --git a/src/client/activation/classic.ts b/src/client/activation/jedi.ts similarity index 96% rename from src/client/activation/classic.ts rename to src/client/activation/jedi.ts index 72745cab86be..86629009162a 100644 --- a/src/client/activation/classic.ts +++ b/src/client/activation/jedi.ts @@ -1,74 +1,74 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable } from 'inversify'; -import { DocumentFilter, languages } from 'vscode'; -import { PYTHON } from '../common/constants'; -import { IConfigurationService, IExtensionContext, ILogger } from '../common/types'; -import { IShebangCodeLensProvider } from '../interpreter/contracts'; -import { IServiceContainer, IServiceManager } from '../ioc/types'; -import { JediFactory } from '../languageServices/jediProxyFactory'; -import { PythonCompletionItemProvider } from '../providers/completionProvider'; -import { PythonDefinitionProvider } from '../providers/definitionProvider'; -import { PythonHoverProvider } from '../providers/hoverProvider'; -import { activateGoToObjectDefinitionProvider } from '../providers/objectDefinitionProvider'; -import { PythonReferenceProvider } from '../providers/referenceProvider'; -import { PythonRenameProvider } from '../providers/renameProvider'; -import { PythonSignatureProvider } from '../providers/signatureProvider'; -import { PythonSymbolProvider } from '../providers/symbolProvider'; -import { IUnitTestManagementService } from '../unittests/types'; -import { WorkspaceSymbols } from '../workspaceSymbols/main'; -import { IExtensionActivator } from './types'; - -@injectable() -export class ClassicExtensionActivator implements IExtensionActivator { - private readonly context: IExtensionContext; - private jediFactory?: JediFactory; - private readonly documentSelector: DocumentFilter[]; - constructor(@inject(IServiceManager) private serviceManager: IServiceManager) { - this.context = this.serviceManager.get(IExtensionContext); - this.documentSelector = PYTHON; - } - - public async activate(): Promise { - const context = this.context; - - const jediFactory = this.jediFactory = new JediFactory(context.asAbsolutePath('.'), this.serviceManager); - context.subscriptions.push(jediFactory); - context.subscriptions.push(...activateGoToObjectDefinitionProvider(jediFactory)); - - context.subscriptions.push(jediFactory); - context.subscriptions.push(languages.registerRenameProvider(this.documentSelector, new PythonRenameProvider(this.serviceManager))); - const definitionProvider = new PythonDefinitionProvider(jediFactory); - - context.subscriptions.push(languages.registerDefinitionProvider(this.documentSelector, definitionProvider)); - context.subscriptions.push(languages.registerHoverProvider(this.documentSelector, new PythonHoverProvider(jediFactory))); - context.subscriptions.push(languages.registerReferenceProvider(this.documentSelector, new PythonReferenceProvider(jediFactory))); - context.subscriptions.push(languages.registerCompletionItemProvider(this.documentSelector, new PythonCompletionItemProvider(jediFactory, this.serviceManager), '.')); - context.subscriptions.push(languages.registerCodeLensProvider(this.documentSelector, this.serviceManager.get(IShebangCodeLensProvider))); - - const serviceContainer = this.serviceManager.get(IServiceContainer); - context.subscriptions.push(new WorkspaceSymbols(serviceContainer)); - - const symbolProvider = new PythonSymbolProvider(serviceContainer, jediFactory); - context.subscriptions.push(languages.registerDocumentSymbolProvider(this.documentSelector, symbolProvider)); - - const pythonSettings = this.serviceManager.get(IConfigurationService).getSettings(); - if (pythonSettings.devOptions.indexOf('DISABLE_SIGNATURE') === -1) { - context.subscriptions.push(languages.registerSignatureHelpProvider(this.documentSelector, new PythonSignatureProvider(jediFactory), '(', ',')); - } - - const testManagementService = this.serviceManager.get(IUnitTestManagementService); - testManagementService.activate() - .then(() => testManagementService.activateCodeLenses(symbolProvider)) - .catch(ex => this.serviceManager.get(ILogger).logError('Failed to activate Unit Tests', ex)); - - return true; - } - - public async deactivate(): Promise { - if (this.jediFactory) { - this.jediFactory.dispose(); - } - } -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable } from 'inversify'; +import { DocumentFilter, languages } from 'vscode'; +import { PYTHON } from '../common/constants'; +import { IConfigurationService, IExtensionContext, ILogger } from '../common/types'; +import { IShebangCodeLensProvider } from '../interpreter/contracts'; +import { IServiceContainer, IServiceManager } from '../ioc/types'; +import { JediFactory } from '../languageServices/jediProxyFactory'; +import { PythonCompletionItemProvider } from '../providers/completionProvider'; +import { PythonDefinitionProvider } from '../providers/definitionProvider'; +import { PythonHoverProvider } from '../providers/hoverProvider'; +import { activateGoToObjectDefinitionProvider } from '../providers/objectDefinitionProvider'; +import { PythonReferenceProvider } from '../providers/referenceProvider'; +import { PythonRenameProvider } from '../providers/renameProvider'; +import { PythonSignatureProvider } from '../providers/signatureProvider'; +import { PythonSymbolProvider } from '../providers/symbolProvider'; +import { IUnitTestManagementService } from '../unittests/types'; +import { WorkspaceSymbols } from '../workspaceSymbols/main'; +import { IExtensionActivator } from './types'; + +@injectable() +export class JediExtensionActivator implements IExtensionActivator { + private readonly context: IExtensionContext; + private jediFactory?: JediFactory; + private readonly documentSelector: DocumentFilter[]; + constructor(@inject(IServiceManager) private serviceManager: IServiceManager) { + this.context = this.serviceManager.get(IExtensionContext); + this.documentSelector = PYTHON; + } + + public async activate(): Promise { + const context = this.context; + + const jediFactory = this.jediFactory = new JediFactory(context.asAbsolutePath('.'), this.serviceManager); + context.subscriptions.push(jediFactory); + context.subscriptions.push(...activateGoToObjectDefinitionProvider(jediFactory)); + + context.subscriptions.push(jediFactory); + context.subscriptions.push(languages.registerRenameProvider(this.documentSelector, new PythonRenameProvider(this.serviceManager))); + const definitionProvider = new PythonDefinitionProvider(jediFactory); + + context.subscriptions.push(languages.registerDefinitionProvider(this.documentSelector, definitionProvider)); + context.subscriptions.push(languages.registerHoverProvider(this.documentSelector, new PythonHoverProvider(jediFactory))); + context.subscriptions.push(languages.registerReferenceProvider(this.documentSelector, new PythonReferenceProvider(jediFactory))); + context.subscriptions.push(languages.registerCompletionItemProvider(this.documentSelector, new PythonCompletionItemProvider(jediFactory, this.serviceManager), '.')); + context.subscriptions.push(languages.registerCodeLensProvider(this.documentSelector, this.serviceManager.get(IShebangCodeLensProvider))); + + const serviceContainer = this.serviceManager.get(IServiceContainer); + context.subscriptions.push(new WorkspaceSymbols(serviceContainer)); + + const symbolProvider = new PythonSymbolProvider(serviceContainer, jediFactory); + context.subscriptions.push(languages.registerDocumentSymbolProvider(this.documentSelector, symbolProvider)); + + const pythonSettings = this.serviceManager.get(IConfigurationService).getSettings(); + if (pythonSettings.devOptions.indexOf('DISABLE_SIGNATURE') === -1) { + context.subscriptions.push(languages.registerSignatureHelpProvider(this.documentSelector, new PythonSignatureProvider(jediFactory), '(', ',')); + } + + const testManagementService = this.serviceManager.get(IUnitTestManagementService); + testManagementService.activate() + .then(() => testManagementService.activateCodeLenses(symbolProvider)) + .catch(ex => this.serviceManager.get(ILogger).logError('Failed to activate Unit Tests', ex)); + + return true; + } + + public async deactivate(): Promise { + if (this.jediFactory) { + this.jediFactory.dispose(); + } + } +} diff --git a/src/client/activation/analysis.ts b/src/client/activation/languageServer.ts similarity index 91% rename from src/client/activation/analysis.ts rename to src/client/activation/languageServer.ts index 62c040f47e93..0d4a6f20871b 100644 --- a/src/client/activation/analysis.ts +++ b/src/client/activation/languageServer.ts @@ -1,324 +1,324 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable } from 'inversify'; -import * as path from 'path'; -import { OutputChannel, Uri } from 'vscode'; -import { Disposable, LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient'; -import { IApplicationShell, ICommandManager, IWorkspaceService } from '../common/application/types'; -import { PythonSettings } from '../common/configSettings'; -import { isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; -import { createDeferred, Deferred } from '../common/helpers'; -import { IFileSystem, IPlatformService } from '../common/platform/types'; -import { StopWatch } from '../common/stopWatch'; -import { IConfigurationService, IExtensionContext, IOutputChannel, IPythonSettings } from '../common/types'; -import { IServiceContainer } from '../ioc/types'; -import { - PYTHON_ANALYSIS_ENGINE_DOWNLOADED, - PYTHON_ANALYSIS_ENGINE_ENABLED, - PYTHON_ANALYSIS_ENGINE_ERROR -} from '../telemetry/constants'; -import { getTelemetryReporter } from '../telemetry/telemetry'; -import { AnalysisEngineDownloader } from './downloader'; -import { InterpreterData, InterpreterDataService } from './interpreterDataService'; -import { PlatformData } from './platformData'; -import { ProgressReporting } from './progress'; -import { IExtensionActivator } from './types'; - -const PYTHON = 'python'; -const dotNetCommand = 'dotnet'; -const languageClientName = 'Python Tools'; -const analysisEngineFolder = 'analysis'; -const loadExtensionCommand = 'python._loadLanguageServerExtension'; - -@injectable() -export class AnalysisExtensionActivator implements IExtensionActivator { - private readonly configuration: IConfigurationService; - private readonly appShell: IApplicationShell; - private readonly output: OutputChannel; - private readonly fs: IFileSystem; - private readonly sw = new StopWatch(); - private readonly platformData: PlatformData; - private readonly startupCompleted: Deferred; - private readonly disposables: Disposable[] = []; - private readonly context: IExtensionContext; - private readonly workspace: IWorkspaceService; - private readonly root: Uri | undefined; - - private languageClient: LanguageClient | undefined; - private interpreterHash: string = ''; - private excludedFiles: string[] = []; - private typeshedPaths: string[] = []; - private loadExtensionArgs: {} | undefined; - // tslint:disable-next-line:no-unused-variable - private progressReporting: ProgressReporting | undefined; - - constructor(@inject(IServiceContainer) private readonly services: IServiceContainer) { - this.context = this.services.get(IExtensionContext); - this.configuration = this.services.get(IConfigurationService); - this.appShell = this.services.get(IApplicationShell); - this.output = this.services.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); - this.fs = this.services.get(IFileSystem); - this.platformData = new PlatformData(services.get(IPlatformService), this.fs); - this.workspace = this.services.get(IWorkspaceService); - - // Currently only a single root. Multi-root support is future. - this.root = this.workspace && this.workspace.hasWorkspaceFolders - ? this.workspace.workspaceFolders![0]!.uri : undefined; - - this.startupCompleted = createDeferred(); - const commandManager = this.services.get(ICommandManager); - - this.disposables.push(commandManager.registerCommand(loadExtensionCommand, - async (args) => { - if (this.languageClient) { - await this.startupCompleted.promise; - this.languageClient.sendRequest('python/loadExtension', args); - } else { - this.loadExtensionArgs = args; - } - } - )); - - (this.configuration.getSettings() as PythonSettings).addListener('change', this.onSettingsChanged); - } - - public async activate(): Promise { - this.sw.reset(); - const clientOptions = await this.getAnalysisOptions(); - if (!clientOptions) { - return false; - } - return this.startLanguageServer(clientOptions); - } - - public async deactivate(): Promise { - if (this.languageClient) { - // Do not await on this - this.languageClient.stop(); - } - for (const d of this.disposables) { - d.dispose(); - } - (this.configuration.getSettings() as PythonSettings).removeListener('change', this.onSettingsChanged); - } - - private async startLanguageServer(clientOptions: LanguageClientOptions): Promise { - // Determine if we are running MSIL/Universal via dotnet or self-contained app. - - const reporter = getTelemetryReporter(); - reporter.sendTelemetryEvent(PYTHON_ANALYSIS_ENGINE_ENABLED); - - const settings = this.configuration.getSettings(); - if (!settings.downloadCodeAnalysis) { - // Depends on .NET Runtime or SDK. Typically development-only case. - this.languageClient = this.createSimpleLanguageClient(clientOptions); - await this.startLanguageClient(); - return true; - } - - const mscorlib = path.join(this.context.extensionPath, analysisEngineFolder, 'mscorlib.dll'); - if (!await this.fs.fileExists(mscorlib)) { - const downloader = new AnalysisEngineDownloader(this.services, analysisEngineFolder); - await downloader.downloadAnalysisEngine(this.context); - reporter.sendTelemetryEvent(PYTHON_ANALYSIS_ENGINE_DOWNLOADED); - } - - const serverModule = path.join(this.context.extensionPath, analysisEngineFolder, this.platformData.getEngineExecutableName()); - this.languageClient = this.createSelfContainedLanguageClient(serverModule, clientOptions); - try { - await this.startLanguageClient(); - return true; - } catch (ex) { - this.appShell.showErrorMessage(`Language server failed to start. Error ${ex}`); - reporter.sendTelemetryEvent(PYTHON_ANALYSIS_ENGINE_ERROR, { error: 'Failed to start (platform)' }); - return false; - } - } - - private async startLanguageClient(): Promise { - this.context.subscriptions.push(this.languageClient!.start()); - await this.serverReady(); - this.progressReporting = new ProgressReporting(this.languageClient!); - } - - private async serverReady(): Promise { - while (!this.languageClient!.initializeResult) { - await new Promise(resolve => setTimeout(resolve, 100)); - } - if (this.loadExtensionArgs) { - this.languageClient!.sendRequest('python/loadExtension', this.loadExtensionArgs); - } - this.startupCompleted.resolve(); - } - - private createSimpleLanguageClient(clientOptions: LanguageClientOptions): LanguageClient { - const commandOptions = { stdio: 'pipe' }; - const serverModule = path.join(this.context.extensionPath, analysisEngineFolder, this.platformData.getEngineDllName()); - const serverOptions: ServerOptions = { - run: { command: dotNetCommand, args: [serverModule], options: commandOptions }, - debug: { command: dotNetCommand, args: [serverModule, '--debug'], options: commandOptions } - }; - return new LanguageClient(PYTHON, languageClientName, serverOptions, clientOptions); - } - - private createSelfContainedLanguageClient(serverModule: string, clientOptions: LanguageClientOptions): LanguageClient { - const options = { stdio: 'pipe' }; - const serverOptions: ServerOptions = { - run: { command: serverModule, rgs: [], options: options }, - debug: { command: serverModule, args: ['--debug'], options } - }; - return new LanguageClient(PYTHON, languageClientName, serverOptions, clientOptions); - } - - private async getAnalysisOptions(): Promise { - // tslint:disable-next-line:no-any - const properties = new Map(); - let interpreterData: InterpreterData | undefined; - let pythonPath = ''; - - try { - const interpreterDataService = new InterpreterDataService(this.context, this.services); - interpreterData = await interpreterDataService.getInterpreterData(); - } catch (ex) { - this.appShell.showWarningMessage('Unable to determine path to the Python interpreter. IntelliSense will be limited.'); - } - - this.interpreterHash = interpreterData ? interpreterData.hash : ''; - if (interpreterData) { - pythonPath = path.dirname(interpreterData.path); - // tslint:disable-next-line:no-string-literal - properties['InterpreterPath'] = interpreterData.path; - // tslint:disable-next-line:no-string-literal - properties['Version'] = interpreterData.version; - // tslint:disable-next-line:no-string-literal - properties['PrefixPath'] = interpreterData.prefix; - } - - // tslint:disable-next-line:no-string-literal - properties['DatabasePath'] = path.join(this.context.extensionPath, analysisEngineFolder); - - let searchPaths = interpreterData ? interpreterData.searchPaths.split(path.delimiter) : []; - const settings = this.configuration.getSettings(); - if (settings.autoComplete) { - const extraPaths = settings.autoComplete.extraPaths; - if (extraPaths && extraPaths.length > 0) { - searchPaths.push(...extraPaths); - } - } - - // Make sure paths do not contain multiple slashes so file URIs - // in VS Code (Node.js) and in the language server (.NET) match. - // Note: for the language server paths separator is always ; - searchPaths.push(pythonPath); - searchPaths = searchPaths.map(p => path.normalize(p)); - - const selector = [{ language: PYTHON, scheme: 'file' }]; - this.excludedFiles = this.getExcludedFiles(); - this.typeshedPaths = this.getTypeshedPaths(settings); - - const traceLogging = (settings.analysis && settings.analysis.traceLogging) ? settings.analysis.traceLogging : false; - - // Options to control the language client - return { - // Register the server for Python documents - documentSelector: selector, - synchronize: { - configurationSection: PYTHON - }, - outputChannel: this.output, - initializationOptions: { - interpreter: { - properties - }, - displayOptions: { - preferredFormat: 1, // Markdown - trimDocumentationLines: false, - maxDocumentationLineLength: 0, - trimDocumentationText: false, - maxDocumentationTextLength: 0 - }, - searchPaths, - typeStubSearchPaths: this.typeshedPaths, - excludeFiles: this.excludedFiles, - testEnvironment: isTestExecution(), - analysisUpdates: true, - traceLogging - } - }; - } - - private getExcludedFiles(): string[] { - const list: string[] = ['**/Lib/**', '**/site-packages/**']; - this.getVsCodeExcludeSection('search.exclude', list); - this.getVsCodeExcludeSection('files.exclude', list); - this.getVsCodeExcludeSection('files.watcherExclude', list); - this.getPythonExcludeSection('linting.ignorePatterns', list); - this.getPythonExcludeSection('workspaceSymbols.exclusionPattern', list); - return list; - } - - private getVsCodeExcludeSection(setting: string, list: string[]): void { - const states = this.workspace.getConfiguration(setting, this.root); - if (states) { - Object.keys(states) - .filter(k => (k.indexOf('*') >= 0 || k.indexOf('/') >= 0) && states[k]) - .forEach(p => list.push(p)); - } - } - - private getPythonExcludeSection(setting: string, list: string[]): void { - const pythonSettings = this.configuration.getSettings(this.root); - const paths = pythonSettings && pythonSettings.linting ? pythonSettings.linting.ignorePatterns : undefined; - if (paths && Array.isArray(paths)) { - paths - .filter(p => p && p.length > 0) - .forEach(p => list.push(p)); - } - } - - private getTypeshedPaths(settings: IPythonSettings): string[] { - return settings.analysis.typeshedPaths && settings.analysis.typeshedPaths.length > 0 - ? settings.analysis.typeshedPaths - : [path.join(this.context.extensionPath, 'typeshed')]; - } - - private async onSettingsChanged(): Promise { - const ids = new InterpreterDataService(this.context, this.services); - const idata = await ids.getInterpreterData(); - if (!idata || idata.hash !== this.interpreterHash) { - this.interpreterHash = idata ? idata.hash : ''; - await this.restartLanguageServer(); - return; - } - - const excludedFiles = this.getExcludedFiles(); - await this.restartLanguageServerIfArrayChanged(this.excludedFiles, excludedFiles); - - const settings = this.configuration.getSettings(); - const typeshedPaths = this.getTypeshedPaths(settings); - await this.restartLanguageServerIfArrayChanged(this.typeshedPaths, typeshedPaths); - } - - private async restartLanguageServerIfArrayChanged(oldArray: string[], newArray: string[]): Promise { - if (newArray.length !== oldArray.length) { - await this.restartLanguageServer(); - return; - } - - for (let i = 0; i < oldArray.length; i += 1) { - if (oldArray[i] !== newArray[i]) { - await this.restartLanguageServer(); - return; - } - } - } - - private async restartLanguageServer(): Promise { - if (!this.context) { - return; - } - await this.deactivate(); - await this.activate(); - } -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { inject, injectable } from 'inversify'; +import * as path from 'path'; +import { OutputChannel, Uri } from 'vscode'; +import { Disposable, LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient'; +import { IApplicationShell, ICommandManager, IWorkspaceService } from '../common/application/types'; +import { PythonSettings } from '../common/configSettings'; +import { isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; +import { createDeferred, Deferred } from '../common/helpers'; +import { IFileSystem, IPlatformService } from '../common/platform/types'; +import { StopWatch } from '../common/stopWatch'; +import { IConfigurationService, IExtensionContext, IOutputChannel, IPythonSettings } from '../common/types'; +import { IServiceContainer } from '../ioc/types'; +import { + PYTHON_LANGUAGE_SERVER_DOWNLOADED, + PYTHON_LANGUAGE_SERVER_ENABLED, + PYTHON_LANGUAGE_SERVER_ERROR +} from '../telemetry/constants'; +import { getTelemetryReporter } from '../telemetry/telemetry'; +import { LanguageServerDownloader } from './downloader'; +import { InterpreterData, InterpreterDataService } from './interpreterDataService'; +import { PlatformData } from './platformData'; +import { ProgressReporting } from './progress'; +import { IExtensionActivator } from './types'; + +const PYTHON = 'python'; +const dotNetCommand = 'dotnet'; +const languageClientName = 'Python Tools'; +const languageServerFolder = 'languageServer'; +const loadExtensionCommand = 'python._loadLanguageServerExtension'; + +@injectable() +export class LanguageServerExtensionActivator implements IExtensionActivator { + private readonly configuration: IConfigurationService; + private readonly appShell: IApplicationShell; + private readonly output: OutputChannel; + private readonly fs: IFileSystem; + private readonly sw = new StopWatch(); + private readonly platformData: PlatformData; + private readonly startupCompleted: Deferred; + private readonly disposables: Disposable[] = []; + private readonly context: IExtensionContext; + private readonly workspace: IWorkspaceService; + private readonly root: Uri | undefined; + + private languageClient: LanguageClient | undefined; + private interpreterHash: string = ''; + private excludedFiles: string[] = []; + private typeshedPaths: string[] = []; + private loadExtensionArgs: {} | undefined; + // tslint:disable-next-line:no-unused-variable + private progressReporting: ProgressReporting | undefined; + + constructor(@inject(IServiceContainer) private readonly services: IServiceContainer) { + this.context = this.services.get(IExtensionContext); + this.configuration = this.services.get(IConfigurationService); + this.appShell = this.services.get(IApplicationShell); + this.output = this.services.get(IOutputChannel, STANDARD_OUTPUT_CHANNEL); + this.fs = this.services.get(IFileSystem); + this.platformData = new PlatformData(services.get(IPlatformService), this.fs); + this.workspace = this.services.get(IWorkspaceService); + + // Currently only a single root. Multi-root support is future. + this.root = this.workspace && this.workspace.hasWorkspaceFolders + ? this.workspace.workspaceFolders![0]!.uri : undefined; + + this.startupCompleted = createDeferred(); + const commandManager = this.services.get(ICommandManager); + + this.disposables.push(commandManager.registerCommand(loadExtensionCommand, + async (args) => { + if (this.languageClient) { + await this.startupCompleted.promise; + this.languageClient.sendRequest('python/loadExtension', args); + } else { + this.loadExtensionArgs = args; + } + } + )); + + (this.configuration.getSettings() as PythonSettings).addListener('change', this.onSettingsChanged); + } + + public async activate(): Promise { + this.sw.reset(); + const clientOptions = await this.getAnalysisOptions(); + if (!clientOptions) { + return false; + } + return this.startLanguageServer(clientOptions); + } + + public async deactivate(): Promise { + if (this.languageClient) { + // Do not await on this + this.languageClient.stop(); + } + for (const d of this.disposables) { + d.dispose(); + } + (this.configuration.getSettings() as PythonSettings).removeListener('change', this.onSettingsChanged); + } + + private async startLanguageServer(clientOptions: LanguageClientOptions): Promise { + // Determine if we are running MSIL/Universal via dotnet or self-contained app. + + const reporter = getTelemetryReporter(); + reporter.sendTelemetryEvent(PYTHON_LANGUAGE_SERVER_ENABLED); + + const settings = this.configuration.getSettings(); + if (!settings.downloadLanguageServer) { + // Depends on .NET Runtime or SDK. Typically development-only case. + this.languageClient = this.createSimpleLanguageClient(clientOptions); + await this.startLanguageClient(); + return true; + } + + const mscorlib = path.join(this.context.extensionPath, languageServerFolder, 'mscorlib.dll'); + if (!await this.fs.fileExists(mscorlib)) { + const downloader = new LanguageServerDownloader(this.services, languageServerFolder); + await downloader.downloadLanguageServer(this.context); + reporter.sendTelemetryEvent(PYTHON_LANGUAGE_SERVER_DOWNLOADED); + } + + const serverModule = path.join(this.context.extensionPath, languageServerFolder, this.platformData.getEngineExecutableName()); + this.languageClient = this.createSelfContainedLanguageClient(serverModule, clientOptions); + try { + await this.startLanguageClient(); + return true; + } catch (ex) { + this.appShell.showErrorMessage(`Language server failed to start. Error ${ex}`); + reporter.sendTelemetryEvent(PYTHON_LANGUAGE_SERVER_ERROR, { error: 'Failed to start (platform)' }); + return false; + } + } + + private async startLanguageClient(): Promise { + this.context.subscriptions.push(this.languageClient!.start()); + await this.serverReady(); + this.progressReporting = new ProgressReporting(this.languageClient!); + } + + private async serverReady(): Promise { + while (!this.languageClient!.initializeResult) { + await new Promise(resolve => setTimeout(resolve, 100)); + } + if (this.loadExtensionArgs) { + this.languageClient!.sendRequest('python/loadExtension', this.loadExtensionArgs); + } + this.startupCompleted.resolve(); + } + + private createSimpleLanguageClient(clientOptions: LanguageClientOptions): LanguageClient { + const commandOptions = { stdio: 'pipe' }; + const serverModule = path.join(this.context.extensionPath, languageServerFolder, this.platformData.getEngineDllName()); + const serverOptions: ServerOptions = { + run: { command: dotNetCommand, args: [serverModule], options: commandOptions }, + debug: { command: dotNetCommand, args: [serverModule, '--debug'], options: commandOptions } + }; + return new LanguageClient(PYTHON, languageClientName, serverOptions, clientOptions); + } + + private createSelfContainedLanguageClient(serverModule: string, clientOptions: LanguageClientOptions): LanguageClient { + const options = { stdio: 'pipe' }; + const serverOptions: ServerOptions = { + run: { command: serverModule, rgs: [], options: options }, + debug: { command: serverModule, args: ['--debug'], options } + }; + return new LanguageClient(PYTHON, languageClientName, serverOptions, clientOptions); + } + + private async getAnalysisOptions(): Promise { + // tslint:disable-next-line:no-any + const properties = new Map(); + let interpreterData: InterpreterData | undefined; + let pythonPath = ''; + + try { + const interpreterDataService = new InterpreterDataService(this.context, this.services); + interpreterData = await interpreterDataService.getInterpreterData(); + } catch (ex) { + this.appShell.showWarningMessage('Unable to determine path to the Python interpreter. IntelliSense will be limited.'); + } + + this.interpreterHash = interpreterData ? interpreterData.hash : ''; + if (interpreterData) { + pythonPath = path.dirname(interpreterData.path); + // tslint:disable-next-line:no-string-literal + properties['InterpreterPath'] = interpreterData.path; + // tslint:disable-next-line:no-string-literal + properties['Version'] = interpreterData.version; + // tslint:disable-next-line:no-string-literal + properties['PrefixPath'] = interpreterData.prefix; + } + + // tslint:disable-next-line:no-string-literal + properties['DatabasePath'] = path.join(this.context.extensionPath, languageServerFolder); + + let searchPaths = interpreterData ? interpreterData.searchPaths.split(path.delimiter) : []; + const settings = this.configuration.getSettings(); + if (settings.autoComplete) { + const extraPaths = settings.autoComplete.extraPaths; + if (extraPaths && extraPaths.length > 0) { + searchPaths.push(...extraPaths); + } + } + + // Make sure paths do not contain multiple slashes so file URIs + // in VS Code (Node.js) and in the language server (.NET) match. + // Note: for the language server paths separator is always ; + searchPaths.push(pythonPath); + searchPaths = searchPaths.map(p => path.normalize(p)); + + const selector = [{ language: PYTHON, scheme: 'file' }]; + this.excludedFiles = this.getExcludedFiles(); + this.typeshedPaths = this.getTypeshedPaths(settings); + + const traceLogging = (settings.analysis && settings.analysis.traceLogging) ? settings.analysis.traceLogging : false; + + // Options to control the language client + return { + // Register the server for Python documents + documentSelector: selector, + synchronize: { + configurationSection: PYTHON + }, + outputChannel: this.output, + initializationOptions: { + interpreter: { + properties + }, + displayOptions: { + preferredFormat: 1, // Markdown + trimDocumentationLines: false, + maxDocumentationLineLength: 0, + trimDocumentationText: false, + maxDocumentationTextLength: 0 + }, + searchPaths, + typeStubSearchPaths: this.typeshedPaths, + excludeFiles: this.excludedFiles, + testEnvironment: isTestExecution(), + analysisUpdates: true, + traceLogging + } + }; + } + + private getExcludedFiles(): string[] { + const list: string[] = ['**/Lib/**', '**/site-packages/**']; + this.getVsCodeExcludeSection('search.exclude', list); + this.getVsCodeExcludeSection('files.exclude', list); + this.getVsCodeExcludeSection('files.watcherExclude', list); + this.getPythonExcludeSection('linting.ignorePatterns', list); + this.getPythonExcludeSection('workspaceSymbols.exclusionPattern', list); + return list; + } + + private getVsCodeExcludeSection(setting: string, list: string[]): void { + const states = this.workspace.getConfiguration(setting, this.root); + if (states) { + Object.keys(states) + .filter(k => (k.indexOf('*') >= 0 || k.indexOf('/') >= 0) && states[k]) + .forEach(p => list.push(p)); + } + } + + private getPythonExcludeSection(setting: string, list: string[]): void { + const pythonSettings = this.configuration.getSettings(this.root); + const paths = pythonSettings && pythonSettings.linting ? pythonSettings.linting.ignorePatterns : undefined; + if (paths && Array.isArray(paths)) { + paths + .filter(p => p && p.length > 0) + .forEach(p => list.push(p)); + } + } + + private getTypeshedPaths(settings: IPythonSettings): string[] { + return settings.analysis.typeshedPaths && settings.analysis.typeshedPaths.length > 0 + ? settings.analysis.typeshedPaths + : [path.join(this.context.extensionPath, 'typeshed')]; + } + + private async onSettingsChanged(): Promise { + const ids = new InterpreterDataService(this.context, this.services); + const idata = await ids.getInterpreterData(); + if (!idata || idata.hash !== this.interpreterHash) { + this.interpreterHash = idata ? idata.hash : ''; + await this.restartLanguageServer(); + return; + } + + const excludedFiles = this.getExcludedFiles(); + await this.restartLanguageServerIfArrayChanged(this.excludedFiles, excludedFiles); + + const settings = this.configuration.getSettings(); + const typeshedPaths = this.getTypeshedPaths(settings); + await this.restartLanguageServerIfArrayChanged(this.typeshedPaths, typeshedPaths); + } + + private async restartLanguageServerIfArrayChanged(oldArray: string[], newArray: string[]): Promise { + if (newArray.length !== oldArray.length) { + await this.restartLanguageServer(); + return; + } + + for (let i = 0; i < oldArray.length; i += 1) { + if (oldArray[i] !== newArray[i]) { + await this.restartLanguageServer(); + return; + } + } + } + + private async restartLanguageServer(): Promise { + if (!this.context) { + return; + } + await this.deactivate(); + await this.activate(); + } +} diff --git a/src/client/activation/analysisEngineHashes.ts b/src/client/activation/languageServerHashes.ts similarity index 51% rename from src/client/activation/analysisEngineHashes.ts rename to src/client/activation/languageServerHashes.ts index c4b1c30af6de..d8d856064363 100644 --- a/src/client/activation/analysisEngineHashes.ts +++ b/src/client/activation/languageServerHashes.ts @@ -1,10 +1,10 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -// This file will be replaced by a generated one during the release build -// with actual hashes of the uploaded packages. -// Values are for test purposes only -export const analysis_engine_win_x86_sha512 = 'win-x86'; -export const analysis_engine_win_x64_sha512 = 'win-x64'; -export const analysis_engine_osx_x64_sha512 = 'osx-x64'; -export const analysis_engine_linux_x64_sha512 = 'linux-x64'; +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// This file will be replaced by a generated one during the release build +// with actual hashes of the uploaded packages. +// Values are for test purposes only +export const language_server_win_x86_sha512 = 'win-x86'; +export const language_server_win_x64_sha512 = 'win-x64'; +export const language_server_osx_x64_sha512 = 'osx-x64'; +export const language_server_linux_x64_sha512 = 'linux-x64'; diff --git a/src/client/activation/platformData.ts b/src/client/activation/platformData.ts index 466955c496e5..60448dccc850 100644 --- a/src/client/activation/platformData.ts +++ b/src/client/activation/platformData.ts @@ -3,11 +3,11 @@ import { IFileSystem, IPlatformService } from '../common/platform/types'; import { - analysis_engine_linux_x64_sha512, - analysis_engine_osx_x64_sha512, - analysis_engine_win_x64_sha512, - analysis_engine_win_x86_sha512 -} from './analysisEngineHashes'; + language_server_linux_x64_sha512, + language_server_osx_x64_sha512, + language_server_win_x64_sha512, + language_server_win_x86_sha512 +} from './languageServerHashes'; export class PlatformData { constructor(private platform: IPlatformService, fs: IFileSystem) { } @@ -20,7 +20,7 @@ export class PlatformData { } if (this.platform.isLinux) { if (!this.platform.is64bit) { - throw new Error('Python Analysis Engine does not support 32-bit Linux.'); + throw new Error('Microsoft Python Language Server does not support 32-bit Linux.'); } return 'linux-x64'; } @@ -28,24 +28,24 @@ export class PlatformData { } public getEngineDllName(): string { - return 'Microsoft.PythonTools.VsCode.dll'; + return 'Microsoft.Python.LanguageServer.dll'; } public getEngineExecutableName(): string { return this.platform.isWindows - ? 'Microsoft.PythonTools.VsCode.exe' - : 'Microsoft.PythonTools.VsCode.VsCode'; + ? 'Microsoft.Python.LanguageServer.exe' + : 'Microsoft.Python.LanguageServer.LanguageServer'; } public async getExpectedHash(): Promise { if (this.platform.isWindows) { - return this.platform.is64bit ? analysis_engine_win_x64_sha512 : analysis_engine_win_x86_sha512; + return this.platform.is64bit ? language_server_win_x64_sha512 : language_server_win_x86_sha512; } if (this.platform.isMac) { - return analysis_engine_osx_x64_sha512; + return language_server_osx_x64_sha512; } if (this.platform.isLinux && this.platform.is64bit) { - return analysis_engine_linux_x64_sha512; + return language_server_linux_x64_sha512; } throw new Error('Unknown platform.'); } diff --git a/src/client/activation/serviceRegistry.ts b/src/client/activation/serviceRegistry.ts index b07ba82d6218..f9d8dfdf549d 100644 --- a/src/client/activation/serviceRegistry.ts +++ b/src/client/activation/serviceRegistry.ts @@ -5,12 +5,12 @@ import { IServiceManager } from '../ioc/types'; import { ExtensionActivationService } from './activationService'; -import { AnalysisExtensionActivator } from './analysis'; -import { ClassicExtensionActivator } from './classic'; +import { JediExtensionActivator } from './jedi'; +import { LanguageServerExtensionActivator } from './languageServer'; import { ExtensionActivators, IExtensionActivationService, IExtensionActivator } from './types'; export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IExtensionActivationService, ExtensionActivationService); - serviceManager.add(IExtensionActivator, ClassicExtensionActivator, ExtensionActivators.Jedi); - serviceManager.add(IExtensionActivator, AnalysisExtensionActivator, ExtensionActivators.DotNet); + serviceManager.add(IExtensionActivator, JediExtensionActivator, ExtensionActivators.Jedi); + serviceManager.add(IExtensionActivator, LanguageServerExtensionActivator, ExtensionActivators.DotNet); } diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index fe3b4d570b7b..26311e9657be 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -28,7 +28,7 @@ export const IS_WINDOWS = /^win/.test(process.platform); // tslint:disable-next-line:completed-docs export class PythonSettings extends EventEmitter implements IPythonSettings { private static pythonSettings: Map = new Map(); - public downloadCodeAnalysis = true; + public downloadLanguageServer = true; public jediEnabled = true; public jediPath = ''; public jediMemoryLimit = 1024; @@ -121,7 +121,7 @@ export class PythonSettings extends EventEmitter implements IPythonSettings { this.venvPath = systemVariables.resolveAny(pythonSettings.get('venvPath'))!; this.venvFolders = systemVariables.resolveAny(pythonSettings.get('venvFolders'))!; - this.downloadCodeAnalysis = systemVariables.resolveAny(pythonSettings.get('downloadCodeAnalysis', true))!; + this.downloadLanguageServer = systemVariables.resolveAny(pythonSettings.get('downloadLanguageServer', true))!; this.jediEnabled = systemVariables.resolveAny(pythonSettings.get('jediEnabled', true))!; if (this.jediEnabled) { // tslint:disable-next-line:no-backbone-get-set-outside-model no-non-null-assertion diff --git a/src/client/common/constants.ts b/src/client/common/constants.ts index 6affcbcf1617..bebb4777d065 100644 --- a/src/client/common/constants.ts +++ b/src/client/common/constants.ts @@ -73,8 +73,8 @@ export const STANDARD_OUTPUT_CHANNEL = 'STANDARD_OUTPUT_CHANNEL'; export function isTestExecution(): boolean { return process.env.VSC_PYTHON_CI_TEST === '1'; } -export function isPythonAnalysisEngineTest(): boolean { - return process.env.VSC_PYTHON_ANALYSIS === '1'; +export function isLanguageServerTest(): boolean { + return process.env.VSC_PYTHON_LANGUAGE_SERVER === '1'; } export const EXTENSION_ROOT_DIR = path.join(__dirname, '..', '..', '..'); diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 21939d784d23..4e5c35215e63 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -118,7 +118,7 @@ export interface IPythonSettings { readonly pythonPath: string; readonly venvPath: string; readonly venvFolders: string[]; - readonly downloadCodeAnalysis: boolean; + readonly downloadLanguageServer: boolean; readonly jediEnabled: boolean; readonly jediPath: string; readonly jediMemoryLimit: number; diff --git a/src/client/telemetry/constants.ts b/src/client/telemetry/constants.ts index b3c3dfe4f0c9..edd932725a08 100644 --- a/src/client/telemetry/constants.ts +++ b/src/client/telemetry/constants.ts @@ -32,8 +32,8 @@ export const UNITTEST_STOP = 'UNITTEST.STOP'; export const UNITTEST_RUN = 'UNITTEST.RUN'; export const UNITTEST_DISCOVER = 'UNITTEST.DISCOVER'; export const UNITTEST_VIEW_OUTPUT = 'UNITTEST.VIEW_OUTPUT'; -export const PYTHON_ANALYSIS_ENGINE_ENABLED = 'PYTHON_ANALYSIS_ENGINE.ENABLED'; -export const PYTHON_ANALYSIS_ENGINE_DOWNLOADED = 'PYTHON_ANALYSIS_ENGINE.DOWNLOADED'; -export const PYTHON_ANALYSIS_ENGINE_ERROR = 'PYTHON_ANALYSIS_ENGINE.ERROR'; -export const PYTHON_ANALYSIS_ENGINE_STARTUP = 'PYTHON_ANALYSIS_ENGINE.STARTUP'; +export const PYTHON_LANGUAGE_SERVER_ENABLED = 'PYTHON_LANGUAGE_SERVER.ENABLED'; +export const PYTHON_LANGUAGE_SERVER_DOWNLOADED = 'PYTHON_LANGUAGE_SERVER.DOWNLOADED'; +export const PYTHON_LANGUAGE_SERVER_ERROR = 'PYTHON_LANGUAGE_SERVER.ERROR'; +export const PYTHON_LANGUAGE_SERVER_STARTUP = 'PYTHON_LANGUAGE_SERVER.STARTUP'; export const TERMINAL_CREATE = 'TERMINAL.CREATE'; diff --git a/src/test/activation/activationService.unit.test.ts b/src/test/activation/activationService.unit.test.ts index e5390cc2ae23..e365e7642369 100644 --- a/src/test/activation/activationService.unit.test.ts +++ b/src/test/activation/activationService.unit.test.ts @@ -10,7 +10,7 @@ import { ConfigurationChangeEvent, Disposable } from 'vscode'; import { ExtensionActivationService } from '../../client/activation/activationService'; import { ExtensionActivators, IExtensionActivationService, IExtensionActivator } from '../../client/activation/types'; import { IApplicationShell, ICommandManager, IWorkspaceService } from '../../client/common/application/types'; -import { isPythonAnalysisEngineTest } from '../../client/common/constants'; +import { isLanguageServerTest } from '../../client/common/constants'; import { IConfigurationService, IDisposableRegistry, IOutputChannel, IPythonSettings } from '../../client/common/types'; import { IServiceContainer } from '../../client/ioc/types'; @@ -23,7 +23,7 @@ suite('Activation - ActivationService', () => { let cmdManager: TypeMoq.IMock; let workspaceService: TypeMoq.IMock; setup(function () { - if (isPythonAnalysisEngineTest()) { + if (isLanguageServerTest()) { // tslint:disable-next-line:no-invalid-this return this.skip(); } diff --git a/src/test/activation/excludeFiles.ptvs.test.ts b/src/test/activation/excludeFiles.ls.test.ts similarity index 96% rename from src/test/activation/excludeFiles.ptvs.test.ts rename to src/test/activation/excludeFiles.ls.test.ts index 88c844866aa6..f7e753493e3d 100644 --- a/src/test/activation/excludeFiles.ptvs.test.ts +++ b/src/test/activation/excludeFiles.ls.test.ts @@ -12,21 +12,21 @@ import { activated } from '../../client/extension'; import { ServiceContainer } from '../../client/ioc/container'; import { ServiceManager } from '../../client/ioc/serviceManager'; import { IServiceContainer, IServiceManager } from '../../client/ioc/types'; -import { IsAnalysisEngineTest } from '../constants'; +import { IsLanguageServerTest } from '../constants'; import { closeActiveWindows } from '../initialize'; const wksPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'exclusions'); const fileOne = path.join(wksPath, 'one.py'); // tslint:disable-next-line:max-func-body-length -suite('Exclude files (Analysis Engine)', () => { +suite('Exclude files (Language Server)', () => { let textDocument: TextDocument; let serviceManager: IServiceManager; let serviceContainer: IServiceContainer; let configService: IConfigurationService; suiteSetup(async function () { - if (!IsAnalysisEngineTest()) { + if (!IsLanguageServerTest()) { // tslint:disable-next-line:no-invalid-this this.skip(); } diff --git a/src/test/activation/platformData.test.ts b/src/test/activation/platformData.test.ts index fe999ec670fd..6d847253c05f 100644 --- a/src/test/activation/platformData.test.ts +++ b/src/test/activation/platformData.test.ts @@ -25,8 +25,8 @@ const testDataLinux = [ ]; const testDataModuleName = [ - { isWindows: true, expectedName: 'Microsoft.PythonTools.VsCode.exe' }, - { isWindows: false, expectedName: 'Microsoft.PythonTools.VsCode.VsCode' } + { isWindows: true, expectedName: 'Microsoft.Python.LanguageServer.exe' }, + { isWindows: false, expectedName: 'Microsoft.Python.LanguageServer.LanguageServer' } ]; // tslint:disable-next-line:max-func-body-length diff --git a/src/test/analysisEngineTest.ts b/src/test/analysisEngineTest.ts index 509cba41160c..359da2bc0cde 100644 --- a/src/test/analysisEngineTest.ts +++ b/src/test/analysisEngineTest.ts @@ -6,11 +6,11 @@ import * as path from 'path'; process.env.CODE_TESTS_WORKSPACE = path.join(__dirname, '..', '..', 'src', 'test'); process.env.IS_CI_SERVER_TEST_DEBUGGER = ''; -process.env.VSC_PYTHON_ANALYSIS = '1'; +process.env.VSC_PYTHON_LANGUAGE_SERVER = '1'; function start() { console.log('*'.repeat(100)); - console.log('Start Analysis Engine tests'); + console.log('Start Language Server tests'); require('../../node_modules/vscode/bin/test'); } start(); diff --git a/src/test/autocomplete/base.test.ts b/src/test/autocomplete/base.test.ts index 5e582b00bfb8..1e658e2821c5 100644 --- a/src/test/autocomplete/base.test.ts +++ b/src/test/autocomplete/base.test.ts @@ -8,7 +8,7 @@ import * as path from 'path'; import * as vscode from 'vscode'; import { IConfigurationService } from '../../client/common/types'; import { rootWorkspaceUri } from '../common'; -import { closeActiveWindows, initialize, initializeTest, IsAnalysisEngineTest } from '../initialize'; +import { closeActiveWindows, initialize, initializeTest, IsLanguageServerTest } from '../initialize'; import { UnitTestIocContainer } from '../unittests/serviceRegistry'; const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); @@ -103,8 +103,8 @@ suite('Autocomplete', function () { // https://github.com/DonJayamanne/pythonVSCode/issues/630 test('For "abc.decorators"', async () => { - // Disabled for MS Python Code Analysis, see https://github.com/Microsoft/PTVS/issues/3857 - if (IsAnalysisEngineTest()) { + // Disabled for the Language Server, see https://github.com/Microsoft/PTVS/issues/3857 + if (IsLanguageServerTest()) { return; } const textDocument = await vscode.workspace.openTextDocument(fileDecorator); @@ -203,9 +203,9 @@ suite('Autocomplete', function () { // https://github.com/Microsoft/vscode-python/issues/110 test('Suppress in strings/comments', async () => { - // Excluded from MS Python Code Analysis b/c skipping of strings and comments + // Excluded from the Language Server b/c skipping of strings and comments // is not yet there. See https://github.com/Microsoft/PTVS/issues/3798 - if (IsAnalysisEngineTest()) { + if (IsLanguageServerTest()) { return; } const positions = [ diff --git a/src/test/autocomplete/pep484.test.ts b/src/test/autocomplete/pep484.test.ts index 288300a101bf..a6d77af91a26 100644 --- a/src/test/autocomplete/pep484.test.ts +++ b/src/test/autocomplete/pep484.test.ts @@ -2,7 +2,7 @@ import * as assert from 'assert'; import * as path from 'path'; import * as vscode from 'vscode'; import { rootWorkspaceUri } from '../common'; -import { IsAnalysisEngineTest } from '../constants'; +import { IsLanguageServerTest } from '../constants'; import { closeActiveWindows, initialize, initializeTest } from '../initialize'; import { UnitTestIocContainer } from '../unittests/serviceRegistry'; @@ -14,7 +14,7 @@ suite('Autocomplete PEP 484', () => { let ioc: UnitTestIocContainer; suiteSetup(async function () { // https://github.com/Microsoft/PTVS/issues/3917 - if (IsAnalysisEngineTest()) { + if (IsLanguageServerTest()) { // tslint:disable-next-line:no-invalid-this this.skip(); } diff --git a/src/test/autocomplete/pep526.test.ts b/src/test/autocomplete/pep526.test.ts index 01cc82f932df..81de7ed79bd1 100644 --- a/src/test/autocomplete/pep526.test.ts +++ b/src/test/autocomplete/pep526.test.ts @@ -2,7 +2,7 @@ import * as assert from 'assert'; import * as path from 'path'; import * as vscode from 'vscode'; import { rootWorkspaceUri } from '../common'; -import { closeActiveWindows, initialize, initializeTest, IsAnalysisEngineTest } from '../initialize'; +import { closeActiveWindows, initialize, initializeTest, IsLanguageServerTest } from '../initialize'; import { UnitTestIocContainer } from '../unittests/serviceRegistry'; const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); @@ -14,7 +14,7 @@ suite('Autocomplete PEP 526', () => { let ioc: UnitTestIocContainer; suiteSetup(async function () { // https://github.com/Microsoft/PTVS/issues/3917 - if (IsAnalysisEngineTest()) { + if (IsLanguageServerTest()) { // tslint:disable-next-line:no-invalid-this this.skip(); } diff --git a/src/test/constants.ts b/src/test/constants.ts index 5ce47d49001d..268352e7f20a 100644 --- a/src/test/constants.ts +++ b/src/test/constants.ts @@ -22,5 +22,5 @@ function isMultitrootTest() { return Array.isArray(workspace.workspaceFolders) && workspace.workspaceFolders.length > 1; } -export const IsAnalysisEngineTest = () => - !IS_TRAVIS && (process.env.VSC_PYTHON_ANALYSIS === '1' || !PythonSettings.getInstance().jediEnabled); +export const IsLanguageServerTest = () => + !IS_TRAVIS && (process.env.VSC_PYTHON_LANGUAGE_SERVER === '1' || !PythonSettings.getInstance().jediEnabled); diff --git a/src/test/definitions/hover.jedi.test.ts b/src/test/definitions/hover.jedi.test.ts index 0b47c9425386..103e78f4a7cc 100644 --- a/src/test/definitions/hover.jedi.test.ts +++ b/src/test/definitions/hover.jedi.test.ts @@ -2,7 +2,7 @@ import * as assert from 'assert'; import { EOL } from 'os'; import * as path from 'path'; import * as vscode from 'vscode'; -import { IsAnalysisEngineTest } from '../constants'; +import { IsLanguageServerTest } from '../constants'; import { closeActiveWindows, initialize, initializeTest } from '../initialize'; import { normalizeMarkedString } from '../textUtils'; @@ -18,7 +18,7 @@ const fileStringFormat = path.join(hoverPath, 'functionHover.py'); // tslint:disable-next-line:max-func-body-length suite('Hover Definition (Jedi)', () => { suiteSetup(async function () { - if (IsAnalysisEngineTest()) { + if (IsLanguageServerTest()) { // tslint:disable-next-line:no-invalid-this this.skip(); } diff --git a/src/test/definitions/hover.ptvs.test.ts b/src/test/definitions/hover.ls.test.ts similarity index 92% rename from src/test/definitions/hover.ptvs.test.ts rename to src/test/definitions/hover.ls.test.ts index f00411fedfad..43dc6c0fdb2a 100644 --- a/src/test/definitions/hover.ptvs.test.ts +++ b/src/test/definitions/hover.ls.test.ts @@ -1,240 +1,240 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import * as assert from 'assert'; -import * as path from 'path'; -import * as vscode from 'vscode'; -import '../../client/common/extensions'; -import { IsAnalysisEngineTest } from '../constants'; -import { closeActiveWindows, initialize, initializeTest } from '../initialize'; -import { normalizeMarkedString } from '../textUtils'; - -const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); -const hoverPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'hover'); -const fileOne = path.join(autoCompPath, 'one.py'); -const fileThree = path.join(autoCompPath, 'three.py'); -const fileEncoding = path.join(autoCompPath, 'four.py'); -const fileEncodingUsed = path.join(autoCompPath, 'five.py'); -const fileHover = path.join(autoCompPath, 'hoverTest.py'); -const fileStringFormat = path.join(hoverPath, 'stringFormat.py'); - -let textDocument: vscode.TextDocument; - -// tslint:disable-next-line:max-func-body-length -suite('Hover Definition (Analysis Engine)', () => { - suiteSetup(async function () { - if (!IsAnalysisEngineTest()) { - // tslint:disable-next-line:no-invalid-this - this.skip(); - } - await initialize(); - }); - setup(initializeTest); - suiteTeardown(closeActiveWindows); - teardown(closeActiveWindows); - - async function openAndHover(file: string, line: number, character: number): Promise { - textDocument = await vscode.workspace.openTextDocument(file); - await vscode.window.showTextDocument(textDocument); - const position = new vscode.Position(line, character); - const result = await vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); - return result ? result : []; - } - - test('Method', async () => { - const def = await openAndHover(fileOne, 30, 5); - assert.equal(def.length, 1, 'Definition length is incorrect'); - - assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '30,0', 'Start position is incorrect'); - assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '30,11', 'End position is incorrect'); - assert.equal(def[0].contents.length, 1, 'Invalid content items'); - - const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); - const expected = [ - 'obj.method1:', - '```python', - 'method method1 of pythonFiles.autocomp.one.Class1 objects', - '```', - 'This is method1' - ]; - verifySignatureLines(actual, expected); - }); - - test('Across files', async () => { - const def = await openAndHover(fileThree, 1, 12); - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '1,0', 'Start position is incorrect'); - assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '1,12', 'End position is incorrect'); - - const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); - const expected = [ - 'two.ct().fun:', - '```python', - 'method fun of pythonFiles.autocomp.two.ct objects', - '```', - 'This is fun' - ]; - verifySignatureLines(actual, expected); - }); - - test('With Unicode Characters', async () => { - const def = await openAndHover(fileEncoding, 25, 6); - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '25,0', 'Start position is incorrect'); - assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '25,7', 'End position is incorrect'); - - const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); - const expected = [ - '```python', - 'pythonFiles.autocomp.four.Foo.bar() -> bool', - 'declared in Foo', - '```', - '说明 - keep this line, it works', - 'delete following line, it works', - '如果存在需要等待审批或正在执行的任务,将不刷新页面' - ]; - verifySignatureLines(actual, expected); - }); - - test('Across files with Unicode Characters', async () => { - const def = await openAndHover(fileEncodingUsed, 1, 11); - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '1,0', 'Start position is incorrect'); - assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '1,16', 'End position is incorrect'); - - const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); - const expected = [ - '```python', - 'pythonFiles.autocomp.four.showMessage()', - '```', - 'Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи.', - 'Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.' - ]; - verifySignatureLines(actual, expected); - }); - - test('Nothing for keywords (class)', async () => { - const def = await openAndHover(fileOne, 5, 1); - if (def.length > 0) { - const actual = normalizeMarkedString(def[0].contents[0]); - assert.equal(actual, '', 'Definition length is incorrect'); - } - }); - - test('Nothing for keywords (for)', async () => { - const def = await openAndHover(fileHover, 3, 1); - if (def.length > 0) { - const actual = normalizeMarkedString(def[0].contents[0]); - assert.equal(actual, '', 'Definition length is incorrect'); - } - }); - - test('Highlighting Class', async () => { - const def = await openAndHover(fileHover, 11, 15); - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '11,7', 'Start position is incorrect'); - assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '11,18', 'End position is incorrect'); - - const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); - const expected = [ - '```python', - 'class pythonFiles.autocomp.misc.Random(_random.Random)', - '```', - 'Random number generator base class used by bound module functions.', - 'Used to instantiate instances of Random to get generators that don\'t', - 'share state.', - 'Class Random can also be subclassed if you want to use a different basic', - 'generator of your own devising: in that case, override the following', - 'methods: random(), seed(), getstate(), and setstate().', - 'Optionally, implement a getrandbits() method so that randrange()', - 'can cover arbitrarily large ranges.' - ]; - verifySignatureLines(actual, expected); - }); - - test('Highlight Method', async () => { - const def = await openAndHover(fileHover, 12, 10); - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '12,0', 'Start position is incorrect'); - assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '12,12', 'End position is incorrect'); - - const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); - const expected = [ - 'rnd2.randint:', - '```python', - 'method randint of pythonFiles.autocomp.misc.Random objects -> int', - '```', - 'Return random integer in range [a, b], including both end points.' - ]; - verifySignatureLines(actual, expected); - }); - - test('Highlight Function', async () => { - const def = await openAndHover(fileHover, 8, 14); - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '8,6', 'Start position is incorrect'); - assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '8,15', 'End position is incorrect'); - - const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); - const expected = [ - '```python', - 'acos(x)', - '```', - 'acos(x)', - 'Return the arc cosine (measured in radians) of x.' - ]; - verifySignatureLines(actual, expected); - }); - - test('Highlight Multiline Method Signature', async () => { - const def = await openAndHover(fileHover, 14, 14); - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '14,4', 'Start position is incorrect'); - assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '14,15', 'End position is incorrect'); - - const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); - const expected = [ - '```python', - 'class pythonFiles.autocomp.misc.Thread(_Verbose)', - '```', - 'A class that represents a thread of control.', - 'This class can be safely subclassed in a limited fashion.' - ]; - verifySignatureLines(actual, expected); - }); - - test('Variable', async () => { - const def = await openAndHover(fileHover, 6, 2); - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(def[0].contents.length, 1, 'Only expected one result'); - const contents = normalizeMarkedString(def[0].contents[0]); - if (contents.indexOf('Random') === -1) { - assert.fail(contents, '', 'Variable type is missing', 'compare'); - } - }); - - test('format().capitalize()', async function () { - // https://github.com/Microsoft/PTVS/issues/3868 - // tslint:disable-next-line:no-invalid-this - this.skip(); - const def = await openAndHover(fileStringFormat, 5, 41); - assert.equal(def.length, 1, 'Definition length is incorrect'); - assert.equal(def[0].contents.length, 1, 'Only expected one result'); - const contents = normalizeMarkedString(def[0].contents[0]); - if (contents.indexOf('capitalize') === -1) { - assert.fail(contents, '', '\'capitalize\' is missing', 'compare'); - } - if (contents.indexOf('Return a capitalized version of S') === -1 && - contents.indexOf('Return a copy of the string S with only its first character') === -1) { - assert.fail(contents, '', '\'Return a capitalized version of S/Return a copy of the string S with only its first character\' message missing', 'compare'); - } - }); - - function verifySignatureLines(actual: string[], expected: string[]) { - assert.equal(actual.length, expected.length, 'incorrect number of lines'); - for (let i = 0; i < actual.length; i += 1) { - actual[i] = actual[i].replace(new RegExp(' ', 'g'), ' '); - assert.equal(actual[i].trim(), expected[i], `signature line ${i + 1} is incorrect`); - } - } -}); +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as assert from 'assert'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import '../../client/common/extensions'; +import { IsLanguageServerTest } from '../constants'; +import { closeActiveWindows, initialize, initializeTest } from '../initialize'; +import { normalizeMarkedString } from '../textUtils'; + +const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); +const hoverPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'hover'); +const fileOne = path.join(autoCompPath, 'one.py'); +const fileThree = path.join(autoCompPath, 'three.py'); +const fileEncoding = path.join(autoCompPath, 'four.py'); +const fileEncodingUsed = path.join(autoCompPath, 'five.py'); +const fileHover = path.join(autoCompPath, 'hoverTest.py'); +const fileStringFormat = path.join(hoverPath, 'stringFormat.py'); + +let textDocument: vscode.TextDocument; + +// tslint:disable-next-line:max-func-body-length +suite('Hover Definition (Language Server)', () => { + suiteSetup(async function () { + if (!IsLanguageServerTest()) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + } + await initialize(); + }); + setup(initializeTest); + suiteTeardown(closeActiveWindows); + teardown(closeActiveWindows); + + async function openAndHover(file: string, line: number, character: number): Promise { + textDocument = await vscode.workspace.openTextDocument(file); + await vscode.window.showTextDocument(textDocument); + const position = new vscode.Position(line, character); + const result = await vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + return result ? result : []; + } + + test('Method', async () => { + const def = await openAndHover(fileOne, 30, 5); + assert.equal(def.length, 1, 'Definition length is incorrect'); + + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '30,0', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '30,11', 'End position is incorrect'); + assert.equal(def[0].contents.length, 1, 'Invalid content items'); + + const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); + const expected = [ + 'obj.method1:', + '```python', + 'method method1 of one.Class1 objects', + '```', + 'This is method1' + ]; + verifySignatureLines(actual, expected); + }); + + test('Across files', async () => { + const def = await openAndHover(fileThree, 1, 12); + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '1,0', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '1,12', 'End position is incorrect'); + + const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); + const expected = [ + 'two.ct().fun:', + '```python', + 'method fun of two.ct objects', + '```', + 'This is fun' + ]; + verifySignatureLines(actual, expected); + }); + + test('With Unicode Characters', async () => { + const def = await openAndHover(fileEncoding, 25, 6); + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '25,0', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '25,7', 'End position is incorrect'); + + const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); + const expected = [ + '```python', + 'four.Foo.bar() -> bool', + 'declared in Foo', + '```', + '说明 - keep this line, it works', + 'delete following line, it works', + '如果存在需要等待审批或正在执行的任务,将不刷新页面' + ]; + verifySignatureLines(actual, expected); + }); + + test('Across files with Unicode Characters', async () => { + const def = await openAndHover(fileEncodingUsed, 1, 11); + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '1,0', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '1,16', 'End position is incorrect'); + + const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); + const expected = [ + '```python', + 'four.showMessage()', + '```', + 'Кюм ут жэмпэр пошжим льаборэж, коммюны янтэрэсщэт нам ед, декта игнота ныморэ жят эи.', + 'Шэа декам экшырки эи, эи зыд эррэм докэндё, векж факэтэ пэрчыквюэрёж ку.' + ]; + verifySignatureLines(actual, expected); + }); + + test('Nothing for keywords (class)', async () => { + const def = await openAndHover(fileOne, 5, 1); + if (def.length > 0) { + const actual = normalizeMarkedString(def[0].contents[0]); + assert.equal(actual, '', 'Definition length is incorrect'); + } + }); + + test('Nothing for keywords (for)', async () => { + const def = await openAndHover(fileHover, 3, 1); + if (def.length > 0) { + const actual = normalizeMarkedString(def[0].contents[0]); + assert.equal(actual, '', 'Definition length is incorrect'); + } + }); + + test('Highlighting Class', async () => { + const def = await openAndHover(fileHover, 11, 15); + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '11,7', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '11,18', 'End position is incorrect'); + + const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); + const expected = [ + '```python', + 'class misc.Random(_random.Random)', + '```', + 'Random number generator base class used by bound module functions.', + 'Used to instantiate instances of Random to get generators that don\'t', + 'share state.', + 'Class Random can also be subclassed if you want to use a different basic', + 'generator of your own devising: in that case, override the following', + 'methods: random(), seed(), getstate(), and setstate().', + 'Optionally, implement a getrandbits() method so that randrange()', + 'can cover arbitrarily large ranges.' + ]; + verifySignatureLines(actual, expected); + }); + + test('Highlight Method', async () => { + const def = await openAndHover(fileHover, 12, 10); + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '12,0', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '12,12', 'End position is incorrect'); + + const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); + const expected = [ + 'rnd2.randint:', + '```python', + 'method randint of misc.Random objects -> int', + '```', + 'Return random integer in range [a, b], including both end points.' + ]; + verifySignatureLines(actual, expected); + }); + + test('Highlight Function', async () => { + const def = await openAndHover(fileHover, 8, 14); + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '8,6', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '8,15', 'End position is incorrect'); + + const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); + const expected = [ + '```python', + 'acos(x)', + '```', + 'acos(x)', + 'Return the arc cosine (measured in radians) of x.' + ]; + verifySignatureLines(actual, expected); + }); + + test('Highlight Multiline Method Signature', async () => { + const def = await openAndHover(fileHover, 14, 14); + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(`${def[0].range!.start.line},${def[0].range!.start.character}`, '14,4', 'Start position is incorrect'); + assert.equal(`${def[0].range!.end.line},${def[0].range!.end.character}`, '14,15', 'End position is incorrect'); + + const actual = normalizeMarkedString(def[0].contents[0]).splitLines(); + const expected = [ + '```python', + 'class misc.Thread(_Verbose)', + '```', + 'A class that represents a thread of control.', + 'This class can be safely subclassed in a limited fashion.' + ]; + verifySignatureLines(actual, expected); + }); + + test('Variable', async () => { + const def = await openAndHover(fileHover, 6, 2); + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(def[0].contents.length, 1, 'Only expected one result'); + const contents = normalizeMarkedString(def[0].contents[0]); + if (contents.indexOf('Random') === -1) { + assert.fail(contents, '', 'Variable type is missing', 'compare'); + } + }); + + test('format().capitalize()', async function () { + // https://github.com/Microsoft/PTVS/issues/3868 + // tslint:disable-next-line:no-invalid-this + this.skip(); + const def = await openAndHover(fileStringFormat, 5, 41); + assert.equal(def.length, 1, 'Definition length is incorrect'); + assert.equal(def[0].contents.length, 1, 'Only expected one result'); + const contents = normalizeMarkedString(def[0].contents[0]); + if (contents.indexOf('capitalize') === -1) { + assert.fail(contents, '', '\'capitalize\' is missing', 'compare'); + } + if (contents.indexOf('Return a capitalized version of S') === -1 && + contents.indexOf('Return a copy of the string S with only its first character') === -1) { + assert.fail(contents, '', '\'Return a capitalized version of S/Return a copy of the string S with only its first character\' message missing', 'compare'); + } + }); + + function verifySignatureLines(actual: string[], expected: string[]) { + assert.equal(actual.length, expected.length, 'incorrect number of lines'); + for (let i = 0; i < actual.length; i += 1) { + actual[i] = actual[i].replace(new RegExp(' ', 'g'), ' '); + assert.equal(actual[i].trim(), expected[i], `signature line ${i + 1} is incorrect`); + } + } +}); diff --git a/src/test/definitions/navigation.test.ts b/src/test/definitions/navigation.test.ts index 9b15afd397aa..71fe9bc6a217 100644 --- a/src/test/definitions/navigation.test.ts +++ b/src/test/definitions/navigation.test.ts @@ -4,7 +4,7 @@ import * as assert from 'assert'; import * as path from 'path'; import * as vscode from 'vscode'; -import { isPythonAnalysisEngineTest } from '../../client/common/constants'; +import { isLanguageServerTest } from '../../client/common/constants'; import { closeActiveWindows, initialize, initializeTest } from '../initialize'; const decoratorsPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'definition', 'navigation'); @@ -53,82 +53,82 @@ suite('Definition Navigation', () => { fileDefinitions, new vscode.Position(2, 6), [fileDefinitions], - isPythonAnalysisEngineTest() ? [new vscode.Range(2, 4, 2, 16)] : [new vscode.Range(2, 0, 11, 17)] + isLanguageServerTest() ? [new vscode.Range(2, 4, 2, 16)] : [new vscode.Range(2, 0, 11, 17)] )); test('Nested function', buildTest( fileDefinitions, new vscode.Position(11, 16), [fileDefinitions], - isPythonAnalysisEngineTest() ? [new vscode.Range(6, 8, 6, 15)] : [new vscode.Range(6, 4, 10, 16)] + isLanguageServerTest() ? [new vscode.Range(6, 8, 6, 15)] : [new vscode.Range(6, 4, 10, 16)] )); test('Decorator usage', buildTest( fileDefinitions, new vscode.Position(13, 1), [fileDefinitions], - isPythonAnalysisEngineTest() ? [new vscode.Range(2, 4, 2, 16)] : [new vscode.Range(2, 0, 11, 17)] + isLanguageServerTest() ? [new vscode.Range(2, 4, 2, 16)] : [new vscode.Range(2, 0, 11, 17)] )); test('Function decorated by stdlib', buildTest( fileDefinitions, new vscode.Position(29, 6), [fileDefinitions], - isPythonAnalysisEngineTest() ? [new vscode.Range(21, 4, 21, 22)] : [new vscode.Range(21, 0, 27, 17)] + isLanguageServerTest() ? [new vscode.Range(21, 4, 21, 22)] : [new vscode.Range(21, 0, 27, 17)] )); test('Function decorated by local decorator', buildTest( fileDefinitions, new vscode.Position(30, 6), [fileDefinitions], - isPythonAnalysisEngineTest() ? [new vscode.Range(14, 4, 14, 9)] : [new vscode.Range(14, 0, 18, 7)] + isLanguageServerTest() ? [new vscode.Range(14, 4, 14, 9)] : [new vscode.Range(14, 0, 18, 7)] )); test('Module imported decorator usage', buildTest( fileUsages, new vscode.Position(3, 15), [fileDefinitions], - isPythonAnalysisEngineTest() ? [new vscode.Range(2, 4, 2, 16)] : [new vscode.Range(2, 0, 11, 17)] + isLanguageServerTest() ? [new vscode.Range(2, 4, 2, 16)] : [new vscode.Range(2, 0, 11, 17)] )); test('Module imported function decorated by stdlib', buildTest( fileUsages, new vscode.Position(11, 19), [fileDefinitions], - isPythonAnalysisEngineTest() ? [new vscode.Range(21, 4, 21, 22)] : [new vscode.Range(21, 0, 27, 17)] + isLanguageServerTest() ? [new vscode.Range(21, 4, 21, 22)] : [new vscode.Range(21, 0, 27, 17)] )); test('Module imported function decorated by local decorator', buildTest( fileUsages, new vscode.Position(12, 19), [fileDefinitions], - isPythonAnalysisEngineTest() ? [new vscode.Range(14, 4, 14, 9)] : [new vscode.Range(14, 0, 18, 7)] + isLanguageServerTest() ? [new vscode.Range(14, 4, 14, 9)] : [new vscode.Range(14, 0, 18, 7)] )); test('Specifically imported decorator usage', buildTest( fileUsages, new vscode.Position(7, 1), - isPythonAnalysisEngineTest() ? [fileUsages, fileDefinitions] : [fileDefinitions], - isPythonAnalysisEngineTest() - ? [new vscode.Range(1, 45, 1, 57), new vscode.Range(2, 4, 2, 16)] + isLanguageServerTest() ? [fileDefinitions] : [fileDefinitions], + isLanguageServerTest() + ? [new vscode.Range(2, 4, 2, 16)] : [new vscode.Range(2, 0, 11, 17)] )); test('Specifically imported function decorated by stdlib', buildTest( fileUsages, new vscode.Position(14, 6), - isPythonAnalysisEngineTest() ? [fileUsages, fileDefinitions] : [fileDefinitions], - isPythonAnalysisEngineTest() - ? [new vscode.Range(1, 25, 1, 43), new vscode.Range(21, 4, 21, 22)] + isLanguageServerTest() ? [fileDefinitions] : [fileDefinitions], + isLanguageServerTest() + ? [new vscode.Range(21, 4, 21, 22)] : [new vscode.Range(21, 0, 27, 17)] )); test('Specifically imported function decorated by local decorator', buildTest( fileUsages, new vscode.Position(15, 6), - isPythonAnalysisEngineTest() ? [fileUsages, fileDefinitions] : [fileDefinitions], - isPythonAnalysisEngineTest() - ? [new vscode.Range(1, 59, 1, 64), new vscode.Range(14, 4, 14, 9)] + isLanguageServerTest() ? [fileDefinitions] : [fileDefinitions], + isLanguageServerTest() + ? [new vscode.Range(14, 4, 14, 9)] : [new vscode.Range(14, 0, 18, 7)] )); }); diff --git a/src/test/definitions/parallel.jedi.test.ts b/src/test/definitions/parallel.jedi.test.ts index 09fb921ecbb3..e2ed7c814d61 100644 --- a/src/test/definitions/parallel.jedi.test.ts +++ b/src/test/definitions/parallel.jedi.test.ts @@ -3,7 +3,7 @@ import { EOL } from 'os'; import * as path from 'path'; import * as vscode from 'vscode'; import { IS_WINDOWS } from '../../client/common/platform/constants'; -import { IsAnalysisEngineTest } from '../constants'; +import { IsLanguageServerTest } from '../constants'; import { closeActiveWindows, initialize } from '../initialize'; import { normalizeMarkedString } from '../textUtils'; @@ -12,7 +12,7 @@ const fileOne = path.join(autoCompPath, 'one.py'); suite('Code, Hover Definition and Intellisense (Jedi)', () => { suiteSetup(async function () { - if (IsAnalysisEngineTest()) { + if (IsLanguageServerTest()) { // tslint:disable-next-line:no-invalid-this this.skip(); } diff --git a/src/test/definitions/parallel.ptvs.test.ts b/src/test/definitions/parallel.ls.test.ts similarity index 92% rename from src/test/definitions/parallel.ptvs.test.ts rename to src/test/definitions/parallel.ls.test.ts index 339585130cea..f54d563c3b06 100644 --- a/src/test/definitions/parallel.ptvs.test.ts +++ b/src/test/definitions/parallel.ls.test.ts @@ -1,57 +1,57 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import * as assert from 'assert'; -import { EOL } from 'os'; -import * as path from 'path'; -import * as vscode from 'vscode'; -import { IS_WINDOWS } from '../../client/common/platform/constants'; -import { IsAnalysisEngineTest } from '../constants'; -import { closeActiveWindows, initialize } from '../initialize'; -import { normalizeMarkedString } from '../textUtils'; - -const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); -const fileOne = path.join(autoCompPath, 'one.py'); - -suite('Code, Hover Definition and Intellisense (MS Python Code Analysis)', () => { - suiteSetup(async function () { - // https://github.com/Microsoft/vscode-python/issues/1061 - // tslint:disable-next-line:no-invalid-this - this.skip(); - - if (!IsAnalysisEngineTest()) { - // tslint:disable-next-line:no-invalid-this - this.skip(); - } - await initialize(); - }); - suiteTeardown(closeActiveWindows); - teardown(closeActiveWindows); - - test('All three together', async () => { - const textDocument = await vscode.workspace.openTextDocument(fileOne); - - let position = new vscode.Position(30, 5); - const hoverDef = await vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); - const codeDef = await vscode.commands.executeCommand('vscode.executeDefinitionProvider', textDocument.uri, position); - position = new vscode.Position(3, 10); - const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); - - assert.equal(list!.items.filter(item => item.label === 'api_version').length, 1, 'api_version not found'); - - assert.equal(codeDef!.length, 1, 'Definition length is incorrect'); - const expectedPath = IS_WINDOWS ? fileOne.toUpperCase() : fileOne; - const actualPath = IS_WINDOWS ? codeDef![0].uri.fsPath.toUpperCase() : codeDef![0].uri.fsPath; - assert.equal(actualPath, expectedPath, 'Incorrect file'); - assert.equal(`${codeDef![0].range!.start.line},${codeDef![0].range!.start.character}`, '17,4', 'Start position is incorrect'); - assert.equal(`${codeDef![0].range!.end.line},${codeDef![0].range!.end.character}`, '21,11', 'End position is incorrect'); - - assert.equal(hoverDef!.length, 1, 'Definition length is incorrect'); - assert.equal(`${hoverDef![0].range!.start.line},${hoverDef![0].range!.start.character}`, '30,4', 'Start position is incorrect'); - assert.equal(`${hoverDef![0].range!.end.line},${hoverDef![0].range!.end.character}`, '30,11', 'End position is incorrect'); - assert.equal(hoverDef![0].contents.length, 1, 'Invalid content items'); - // tslint:disable-next-line:prefer-template - const expectedContent = '```python' + EOL + 'def method1()' + EOL + '```' + EOL + 'This is method1'; - assert.equal(normalizeMarkedString(hoverDef![0].contents[0]), expectedContent, 'function signature incorrect'); - }); -}); +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as assert from 'assert'; +import { EOL } from 'os'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { IS_WINDOWS } from '../../client/common/platform/constants'; +import { IsLanguageServerTest } from '../constants'; +import { closeActiveWindows, initialize } from '../initialize'; +import { normalizeMarkedString } from '../textUtils'; + +const autoCompPath = path.join(__dirname, '..', '..', '..', 'src', 'test', 'pythonFiles', 'autocomp'); +const fileOne = path.join(autoCompPath, 'one.py'); + +suite('Code, Hover Definition and Intellisense (Language Server)', () => { + suiteSetup(async function () { + // https://github.com/Microsoft/vscode-python/issues/1061 + // tslint:disable-next-line:no-invalid-this + this.skip(); + + if (!IsLanguageServerTest()) { + // tslint:disable-next-line:no-invalid-this + this.skip(); + } + await initialize(); + }); + suiteTeardown(closeActiveWindows); + teardown(closeActiveWindows); + + test('All three together', async () => { + const textDocument = await vscode.workspace.openTextDocument(fileOne); + + let position = new vscode.Position(30, 5); + const hoverDef = await vscode.commands.executeCommand('vscode.executeHoverProvider', textDocument.uri, position); + const codeDef = await vscode.commands.executeCommand('vscode.executeDefinitionProvider', textDocument.uri, position); + position = new vscode.Position(3, 10); + const list = await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', textDocument.uri, position); + + assert.equal(list!.items.filter(item => item.label === 'api_version').length, 1, 'api_version not found'); + + assert.equal(codeDef!.length, 1, 'Definition length is incorrect'); + const expectedPath = IS_WINDOWS ? fileOne.toUpperCase() : fileOne; + const actualPath = IS_WINDOWS ? codeDef![0].uri.fsPath.toUpperCase() : codeDef![0].uri.fsPath; + assert.equal(actualPath, expectedPath, 'Incorrect file'); + assert.equal(`${codeDef![0].range!.start.line},${codeDef![0].range!.start.character}`, '17,4', 'Start position is incorrect'); + assert.equal(`${codeDef![0].range!.end.line},${codeDef![0].range!.end.character}`, '21,11', 'End position is incorrect'); + + assert.equal(hoverDef!.length, 1, 'Definition length is incorrect'); + assert.equal(`${hoverDef![0].range!.start.line},${hoverDef![0].range!.start.character}`, '30,4', 'Start position is incorrect'); + assert.equal(`${hoverDef![0].range!.end.line},${hoverDef![0].range!.end.character}`, '30,11', 'End position is incorrect'); + assert.equal(hoverDef![0].contents.length, 1, 'Invalid content items'); + // tslint:disable-next-line:prefer-template + const expectedContent = '```python' + EOL + 'def method1()' + EOL + '```' + EOL + 'This is method1'; + assert.equal(normalizeMarkedString(hoverDef![0].contents[0]), expectedContent, 'function signature incorrect'); + }); +}); diff --git a/src/test/performance/load.perf.test.ts b/src/test/performance/load.perf.test.ts index 3c6ef65c11cb..f3b4f87ffa1c 100644 --- a/src/test/performance/load.perf.test.ts +++ b/src/test/performance/load.perf.test.ts @@ -40,7 +40,7 @@ suite('Activation Times', () => { if (process.env.ACTIVATION_TIMES_DEV_LOG_FILE_PATHS && process.env.ACTIVATION_TIMES_RELEASE_LOG_FILE_PATHS && - process.env.ACTIVATION_TIMES_DEV_ANALYSIS_LOG_FILE_PATHS) { + process.env.ACTIVATION_TIMES_DEV_LANGUAGE_SERVER_LOG_FILE_PATHS) { test('Test activation times of Dev vs Release Extension', async () => { function getActivationTimes(files: string[]) { @@ -57,14 +57,14 @@ suite('Activation Times', () => { } const devActivationTimes = getActivationTimes(JSON.parse(process.env.ACTIVATION_TIMES_DEV_LOG_FILE_PATHS!)); const releaseActivationTimes = getActivationTimes(JSON.parse(process.env.ACTIVATION_TIMES_RELEASE_LOG_FILE_PATHS!)); - const analysisEngineActivationTimes = getActivationTimes(JSON.parse(process.env.ACTIVATION_TIMES_DEV_ANALYSIS_LOG_FILE_PATHS!)); + const languageServerActivationTimes = getActivationTimes(JSON.parse(process.env.ACTIVATION_TIMES_DEV_LANGUAGE_SERVER_LOG_FILE_PATHS!)); const devActivationAvgTime = devActivationTimes.reduce((sum, item) => sum + item, 0) / devActivationTimes.length; const releaseActivationAvgTime = releaseActivationTimes.reduce((sum, item) => sum + item, 0) / releaseActivationTimes.length; - const analysisEngineActivationAvgTime = analysisEngineActivationTimes.reduce((sum, item) => sum + item, 0) / analysisEngineActivationTimes.length; + const languageServerActivationAvgTime = languageServerActivationTimes.reduce((sum, item) => sum + item, 0) / languageServerActivationTimes.length; - console.log(`Dev version Loaded in ${devActivationAvgTime}ms`); - console.log(`Release version Loaded in ${releaseActivationAvgTime}ms`); - console.log(`Analysis Engine Loaded in ${analysisEngineActivationAvgTime}ms`); + console.log(`Dev version loaded in ${devActivationAvgTime}ms`); + console.log(`Release version loaded in ${releaseActivationAvgTime}ms`); + console.log(`Language Server loaded in ${languageServerActivationAvgTime}ms`); expect(devActivationAvgTime - releaseActivationAvgTime).to.be.lessThan(AllowedIncreaseInActivationDelayInMS, 'Activation times have increased above allowed threshold.'); }); diff --git a/src/test/performanceTest.ts b/src/test/performanceTest.ts index 781841960a7d..c9e5a1d13538 100644 --- a/src/test/performanceTest.ts +++ b/src/test/performanceTest.ts @@ -41,10 +41,10 @@ class TestRunner { const timesToLoadEachVersion = 2; const devLogFiles: string[] = []; const releaseLogFiles: string[] = []; - const newAnalysisEngineLogFiles: string[] = []; + const languageServerLogFiles: string[] = []; for (let i = 0; i < timesToLoadEachVersion; i += 1) { - await this.enableNewAnalysisEngine(false); + await this.enableLanguageServer(false); const devLogFile = path.join(logFilesPath, `dev_loadtimes${i}.txt`); console.log(`Start Performance Tests: Counter ${i}, for Dev version with Jedi`); @@ -56,18 +56,18 @@ class TestRunner { await this.capturePerfTimes(Version.Release, releaseLogFile); releaseLogFiles.push(releaseLogFile); - // New Analysis engine. - await this.enableNewAnalysisEngine(true); - const newAnalysisEngineLogFile = path.join(logFilesPath, `newAnalysisEngine_loadtimes${i}.txt`); - console.log(`Start Performance Tests: Counter ${i}, for Release version with Analysis Engine`); - await this.capturePerfTimes(Version.Release, newAnalysisEngineLogFile); - newAnalysisEngineLogFiles.push(newAnalysisEngineLogFile); + // Language server. + await this.enableLanguageServer(true); + const languageServerLogFile = path.join(logFilesPath, `languageServer_loadtimes${i}.txt`); + console.log(`Start Performance Tests: Counter ${i}, for Release version with Language Server`); + await this.capturePerfTimes(Version.Release, languageServerLogFile); + languageServerLogFiles.push(languageServerLogFile); } console.log('Compare Performance Results'); - await this.runPerfTest(devLogFiles, releaseLogFiles, newAnalysisEngineLogFiles); + await this.runPerfTest(devLogFiles, releaseLogFiles, languageServerLogFiles); } - private async enableNewAnalysisEngine(enable: boolean) { + private async enableLanguageServer(enable: boolean) { const settings = `{ "python.jediEnabled": ${!enable} }`; await fs.writeFile(path.join(EXTENSION_ROOT_DIR, 'src', 'test', 'performance', 'settings.json'), settings); } @@ -84,11 +84,11 @@ class TestRunner { await this.launchTest(env); } - private async runPerfTest(devLogFiles: string[], releaseLogFiles: string[], newAnalysisEngineLogFiles: string[]) { + private async runPerfTest(devLogFiles: string[], releaseLogFiles: string[], languageServerLogFiles: string[]) { const env: { [key: string]: {} } = { ACTIVATION_TIMES_DEV_LOG_FILE_PATHS: JSON.stringify(devLogFiles), ACTIVATION_TIMES_RELEASE_LOG_FILE_PATHS: JSON.stringify(releaseLogFiles), - ACTIVATION_TIMES_DEV_ANALYSIS_LOG_FILE_PATHS: JSON.stringify(newAnalysisEngineLogFiles) + ACTIVATION_TIMES_DEV_LANGUAGE_SERVER_LOG_FILE_PATHS: JSON.stringify(languageServerLogFiles) }; await this.launchTest(env); diff --git a/src/test/signature/signature.jedi.test.ts b/src/test/signature/signature.jedi.test.ts index 5805f7b56b04..a81dbd8d1813 100644 --- a/src/test/signature/signature.jedi.test.ts +++ b/src/test/signature/signature.jedi.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import * as path from 'path'; import * as vscode from 'vscode'; import { rootWorkspaceUri } from '../common'; -import { IsAnalysisEngineTest } from '../constants'; +import { IsLanguageServerTest } from '../constants'; import { closeActiveWindows, initialize, initializeTest } from '../initialize'; import { UnitTestIocContainer } from '../unittests/serviceRegistry'; @@ -25,7 +25,7 @@ suite('Signatures (Jedi)', () => { let isPython2: boolean; let ioc: UnitTestIocContainer; suiteSetup(async function () { - if (IsAnalysisEngineTest()) { + if (IsLanguageServerTest()) { // tslint:disable-next-line:no-invalid-this this.skip(); } diff --git a/src/test/signature/signature.ptvs.test.ts b/src/test/signature/signature.ls.test.ts similarity index 97% rename from src/test/signature/signature.ptvs.test.ts rename to src/test/signature/signature.ls.test.ts index 8cc9e97ed87e..49aa4fa9c83f 100644 --- a/src/test/signature/signature.ptvs.test.ts +++ b/src/test/signature/signature.ls.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import * as path from 'path'; import * as vscode from 'vscode'; import { rootWorkspaceUri } from '../common'; -import { IsAnalysisEngineTest } from '../constants'; +import { IsLanguageServerTest } from '../constants'; import { closeActiveWindows, initialize, initializeTest } from '../initialize'; import { UnitTestIocContainer } from '../unittests/serviceRegistry'; @@ -21,11 +21,11 @@ class SignatureHelpResult { } // tslint:disable-next-line:max-func-body-length -suite('Signatures (Analysis Engine)', () => { +suite('Signatures (Language Server)', () => { let isPython2: boolean; let ioc: UnitTestIocContainer; suiteSetup(async function () { - if (!IsAnalysisEngineTest()) { + if (!IsLanguageServerTest()) { // tslint:disable-next-line:no-invalid-this this.skip(); } From 43175ae644b65e691e2e02f9ff96d81fd86cafab Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 13 Jul 2018 13:10:52 -0700 Subject: [PATCH 402/433] Ignore languageServer files from vsix (#2152) Fixes #2150 This pull request: - [x] Has a title summarizes what is changing - [x] Includes a [news entry](https://github.com/Microsoft/vscode-python/tree/master/news) file (remember to thank yourself!) - [ ] Has unit tests & [code coverage](https://codecov.io/gh/Microsoft/vscode-python) is not adversely affected (within reason) - [ ] Works on all [actively maintained versions of Python](https://devguide.python.org/#status-of-python-branches) (e.g. Python 2.7 & the latest Python 3 release) - [ ] Works on Windows 10, macOS, and Linux (e.g. considered file system case-sensitivity) --- .vscodeignore | 4 ++-- news/3 Code Health/2150.md | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 news/3 Code Health/2150.md diff --git a/.vscodeignore b/.vscodeignore index 8df1ffe5bf9f..4037e6fc7e54 100644 --- a/.vscodeignore +++ b/.vscodeignore @@ -10,7 +10,7 @@ CODE_OF_CONDUCT.md CODING_STANDARDS.md CONTRIBUTING.md -CONTRIBUTING - PYTHON_ANALYSIS.md +CONTRIBUTING - LANGUAGE SERVER.md coverconfig.json gulpfile.js packageExtension.cmd @@ -26,7 +26,7 @@ yarn.lock .nvm/** .vscode/** .vscode-test/** -languageServer/publish*.* +languageServer/** bin/** BuildOutput/** coverage/** diff --git a/news/3 Code Health/2150.md b/news/3 Code Health/2150.md new file mode 100644 index 000000000000..77408e558502 --- /dev/null +++ b/news/3 Code Health/2150.md @@ -0,0 +1 @@ +Ensure 'languageServer' directory is excluded from the build output. From 31b63e382b3447b74b6c8afa7ccd5e47b2da923e Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 13 Jul 2018 15:01:49 -0700 Subject: [PATCH 403/433] Add steps for dependencies --- .github/PULL_REQUEST_TEMPLATE.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 8552f75f33e9..d2c60c58c8bc 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -6,3 +6,5 @@ This pull request: - [ ] Has unit tests & [code coverage](https://codecov.io/gh/Microsoft/vscode-python) is not adversely affected (within reason) - [ ] Works on all [actively maintained versions of Python](https://devguide.python.org/#status-of-python-branches) (e.g. Python 2.7 & the latest Python 3 release) - [ ] Works on Windows 10, macOS, and Linux (e.g. considered file system case-sensitivity) +- [ ] Dependencies are pinned (e.g. `"1.2.3"`, not `"^1.2.3"`) +- [ ] `package-lock.json` has been regenerated if dependencies have changed From 4db747d98d551ee4e8b0aff684fbf30fbc141d6f Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Fri, 13 Jul 2018 15:02:32 -0700 Subject: [PATCH 404/433] Tweak grammar --- .github/PULL_REQUEST_TEMPLATE.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d2c60c58c8bc..ecc2a4026baf 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,9 +1,8 @@ Fixes # -This pull request: -- [ ] Has a title summarizes what is changing +- [ ] Title summarizes what is changing - [ ] Includes a [news entry](https://github.com/Microsoft/vscode-python/tree/master/news) file (remember to thank yourself!) -- [ ] Has unit tests & [code coverage](https://codecov.io/gh/Microsoft/vscode-python) is not adversely affected (within reason) +- [ ] Unit tests & [code coverage](https://codecov.io/gh/Microsoft/vscode-python) are not adversely affected (within reason) - [ ] Works on all [actively maintained versions of Python](https://devguide.python.org/#status-of-python-branches) (e.g. Python 2.7 & the latest Python 3 release) - [ ] Works on Windows 10, macOS, and Linux (e.g. considered file system case-sensitivity) - [ ] Dependencies are pinned (e.g. `"1.2.3"`, not `"^1.2.3"`) From 033029a118699b7de044cde25cbed55a307525fd Mon Sep 17 00:00:00 2001 From: Derek Keeler Date: Mon, 16 Jul 2018 17:05:25 -0600 Subject: [PATCH 405/433] Upgrade to latest LS protocol (#2164) * LS symbol providers * Different ready wait * Upgrade dependencies to latest LS * Make open files only default * Turn off hash checks * Fix double progress display * Update packages * Anchor dependencies * Add missing mock from vscode-mock * Downgrade pylint to < 2.0.0 to mirror prospector requirements --- news/1 Enhancements/2000.md | 1 + news/1 Enhancements/2113.md | 1 + package-lock.json | 47 ++++++++++++++----------- package.json | 11 +++--- requirements.txt | 2 +- src/client/activation/downloader.ts | 15 +------- src/client/activation/languageServer.ts | 2 +- src/test/vscode-mock.ts | 1 + 8 files changed, 39 insertions(+), 41 deletions(-) create mode 100644 news/1 Enhancements/2000.md create mode 100644 news/1 Enhancements/2113.md diff --git a/news/1 Enhancements/2000.md b/news/1 Enhancements/2000.md new file mode 100644 index 000000000000..319691492f78 --- /dev/null +++ b/news/1 Enhancements/2000.md @@ -0,0 +1 @@ +Only report Language Server download progress once. (Thanks @MikhailArkhipov) diff --git a/news/1 Enhancements/2113.md b/news/1 Enhancements/2113.md new file mode 100644 index 000000000000..9455e9f6077d --- /dev/null +++ b/news/1 Enhancements/2113.md @@ -0,0 +1 @@ +Set default analysis for language server to open files only. (Thanks @MikhailArkhipov) diff --git a/package-lock.json b/package-lock.json index e42cc4b1a58e..ee9849eb35e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9652,40 +9652,47 @@ } }, "vscode-jsonrpc": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz", - "integrity": "sha1-hyOdnhZrLXNSJFuKgTWXgEwdY6o=" + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-3.6.2.tgz", + "integrity": "sha512-T24Jb5V48e4VgYliUXMnZ379ItbrXgOimweKaJshD84z+8q7ZOZjJan0MeDe+Ugb+uqERDVV8SBmemaGMSMugA==" }, "vscode-languageclient": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-3.5.1.tgz", - "integrity": "sha512-GTQ+hSq/o4c/y6GYmyP9XNrVoIu0NFZ67KltSkqN+tO0eUNDIlrVNX+3DJzzyLhSsrctuGzuYWm3t87mNAcBmQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-4.3.0.tgz", + "integrity": "sha512-vDpsmYfYpfuyDKZ46pCJEFBOCNHepRBSmlBGA0fczEbYghYm059BiFo3SmT4MK1r8NvYrFEem4k5TYNW3wommg==", "requires": { - "vscode-languageserver-protocol": "3.5.1" + "vscode-languageserver-protocol": "^3.9.0" } }, "vscode-languageserver": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-3.5.1.tgz", - "integrity": "sha512-RYUKn0DgHTFcS8kS4VaNCjNMaQXYqiXdN9bKrFjXzu5RPKfjIYcoh47oVWwZj4L3R/DPB0Se7HPaDatvYY2XgQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-4.3.0.tgz", + "integrity": "sha512-4dTpnyTB6Q0HmMhxaG60rrpQthbTBlMtFX5cwJpPxcPzLZIFDWB3msR6TxGCzWpdYF11REIJihWByobpGkljdQ==", "requires": { - "vscode-languageserver-protocol": "3.5.1", - "vscode-uri": "^1.0.1" + "vscode-languageserver-protocol": "^3.9.0", + "vscode-uri": "^1.0.3" + }, + "dependencies": { + "vscode-uri": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-1.0.5.tgz", + "integrity": "sha1-O4majvccN/MFTXm9vdoxx7828g0=" + } } }, "vscode-languageserver-protocol": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.1.tgz", - "integrity": "sha512-1fPDIwsAv1difCV+8daOrJEGunClNJWqnUHq/ncWrjhitKWXgGmRCjlwZ3gDUTt54yRcvXz1PXJDaRNvNH6pYA==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.9.0.tgz", + "integrity": "sha512-i1sG5iU88Mocc7egTeh6dAow/yRWpPK5PLJaxsWsKiA+dspq1Yzr/R1bNLPc+6P/ab010lXhzdUHQY0CuIUyDw==", "requires": { - "vscode-jsonrpc": "3.5.0", - "vscode-languageserver-types": "3.5.0" + "vscode-jsonrpc": "^3.6.2", + "vscode-languageserver-types": "^3.9.0" } }, "vscode-languageserver-types": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz", - "integrity": "sha1-5I15li8LjgLelV4/UkkI4rGcA3Q=" + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.9.0.tgz", + "integrity": "sha512-Qzh3VsU3t0zhKtYl1revyax+4gGHl2ejNzYXeiZYQMF3i0vX4dtPohxGDFoZYfGFQI738aXYbSUQmhLeBckDlQ==" }, "vscode-uri": { "version": "1.0.1", diff --git a/package.json b/package.json index 4bc0621b780b..2795d022a5c4 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "theme": "dark" }, "engines": { - "vscode": "^1.23.0" + "vscode": "^1.25.0" }, "recommendations": [ "donjayamanne.jupyter" @@ -1277,7 +1277,7 @@ }, "python.analysis.openFilesOnly": { "type": "boolean", - "default": false, + "default": true, "description": "Only show errors and warnings for open files rather than for the entire workspace.", "scope": "resource" }, @@ -1964,7 +1964,7 @@ "fs-extra": "4.0.3", "fuzzy": "0.1.3", "get-port": "3.2.0", - "glob": "^7.1.2", + "glob": "7.1.2", "iconv-lite": "0.4.21", "inversify": "4.11.1", "line-by-line": "0.1.6", @@ -1990,8 +1990,9 @@ "vscode-debugadapter": "1.28.0", "vscode-debugprotocol": "1.28.0", "vscode-extension-telemetry": "0.0.15", - "vscode-languageclient": "3.5.1", - "vscode-languageserver": "3.5.1", + "vscode-languageclient": "4.3.0", + "vscode-languageserver": "4.3.0", + "vscode-languageserver-protocol": "3.9.0", "winreg": "1.2.4", "xml2js": "0.4.19" }, diff --git a/requirements.txt b/requirements.txt index 01c6059a10cb..cb71d94eb4e0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ flake8 autopep8 black ; python_version>='3.6' yapf -pylint +pylint<2.0.0 pep8 prospector pydocstyle diff --git a/src/client/activation/downloader.ts b/src/client/activation/downloader.ts index 59f9fd91a8db..c80b7dd40856 100644 --- a/src/client/activation/downloader.ts +++ b/src/client/activation/downloader.ts @@ -11,7 +11,6 @@ import { createDeferred } from '../common/helpers'; import { IFileSystem, IPlatformService } from '../common/platform/types'; import { IExtensionContext, IOutputChannel } from '../common/types'; import { IServiceContainer } from '../ioc/types'; -import { HashVerifier } from './hashVerifier'; import { PlatformData } from './platformData'; // tslint:disable-next-line:no-require-imports no-var-requires @@ -42,7 +41,6 @@ export class LanguageServerDownloader { let localTempFilePath = ''; try { localTempFilePath = await this.downloadFile(downloadUriPrefix, enginePackageFileName, 'Downloading Microsoft Python Language Server... '); - await this.verifyDownload(localTempFilePath, platformString); await this.unpackArchive(context.extensionPath, localTempFilePath); } catch (err) { this.output.appendLine('failed.'); @@ -70,8 +68,7 @@ export class LanguageServerDownloader { }); await window.withProgress({ - location: ProgressLocation.Window, - title + location: ProgressLocation.Window }, (progress) => { requestProgress(request(uri)) @@ -98,16 +95,6 @@ export class LanguageServerDownloader { return tempFile.filePath; } - private async verifyDownload(filePath: string, platformString: string): Promise { - this.output.appendLine(''); - this.output.append('Verifying download... '); - const verifier = new HashVerifier(); - if (!await verifier.verifyHash(filePath, platformString, await this.platformData.getExpectedHash())) { - throw new Error('Hash of the downloaded file does not match.'); - } - this.output.appendLine('valid.'); - } - private async unpackArchive(extensionPath: string, tempFilePath: string): Promise { this.output.append('Unpacking archive... '); diff --git a/src/client/activation/languageServer.ts b/src/client/activation/languageServer.ts index 0d4a6f20871b..5154b92a41af 100644 --- a/src/client/activation/languageServer.ts +++ b/src/client/activation/languageServer.ts @@ -232,7 +232,7 @@ export class LanguageServerExtensionActivator implements IExtensionActivator { properties }, displayOptions: { - preferredFormat: 1, // Markdown + preferredFormat: 'markdown', trimDocumentationLines: false, maxDocumentationLineLength: 0, trimDocumentationText: false, diff --git a/src/test/vscode-mock.ts b/src/test/vscode-mock.ts index 1500ca249eae..a1a35ed406c2 100644 --- a/src/test/vscode-mock.ts +++ b/src/test/vscode-mock.ts @@ -64,6 +64,7 @@ mockedVSCode.EventEmitter = vscodeMocks.vscMock.EventEmitter; mockedVSCode.ConfigurationTarget = vscodeMocks.vscMockExtHostedTypes.ConfigurationTarget; mockedVSCode.StatusBarAlignment = vscodeMocks.vscMockExtHostedTypes.StatusBarAlignment; mockedVSCode.SignatureHelp = vscodeMocks.vscMockExtHostedTypes.SignatureHelp; +mockedVSCode.DocumentLink = vscodeMocks.vscMockExtHostedTypes.DocumentLink; // This API is used in src/client/telemetry/telemetry.ts const extensions = TypeMoq.Mock.ofType(); From 914a73414c9c820d9cf7f4d2f87b223a388ba682 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 17 Jul 2018 10:52:54 -0700 Subject: [PATCH 406/433] Explicitly test against installing the language server --- .github/test_plan.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/test_plan.md b/.github/test_plan.md index c3d6ef79a7cf..8dc68cca76ce 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -15,6 +15,7 @@ - Check the `Output` window under `Python` for logged errors - Have `Developer Tools` open to detect any errors - Consider running the tests in a multi-folder workspace +- Focus on in-development features (i.e. experimental debugger and language server)
Scenarios @@ -48,6 +49,7 @@ - [ ] [`Create Terminal`](https://code.visualstudio.com/docs/python/environments#_activating-an-environment-in-the-terminal) works - [ ] Steals focus - [ ] `"python.terminal.activateEnvironment": false` deactivates automatically running the activation script in the terminal + - [ ] After the language server downloads it is able to complete its analysis of the environment w/o requiring a restart - [ ] Detect multiple virtual environments contained in the directory specified in `"python.venvPath"` - [ ] Detected all [conda environments created with an interpreter](https://code.visualstudio.com/docs/python/environments#_conda-environments) - [ ] Appropriate suffix label specified in status bar (e.g. `(condaenv)`) @@ -56,11 +58,13 @@ - [ ] Installs into environment - [ ] [`Create Terminal`](https://code.visualstudio.com/docs/python/environments#_activating-an-environment-in-the-terminal) works - [ ] `"python.terminal.activateEnvironment": false` deactivates automatically running the activation script in the terminal + - [ ] After the language server downloads it is able to complete its analysis of the environment w/o requiring a restart - [ ] (Linux/macOS until [`-m` is supported](https://github.com/Microsoft/vscode-python/issues/978)) Detected the virtual environment created by [pipenv](https://docs.pipenv.org/) - [ ] Appropriate suffix label specified in status bar (e.g. `(pipenv)`) - [ ] Prompt to install Pylint uses `pipenv install --dev` - [ ] [`Create Terminal`](https://code.visualstudio.com/docs/python/environments#_activating-an-environment-in-the-terminal) works - [ ] `"python.terminal.activateEnvironment": false` deactivates automatically running the activation script in the terminal + - [ ] After the language server downloads it is able to complete its analysis of the environment w/o requiring a restart - [ ] (Linux/macOS) Virtual environments created under `{workspaceFolder}/.direnv/python-{python_version}` are detected (for [direnv](https://direnv.net/) and its [`layout python3`](https://github.com/direnv/direnv/blob/master/stdlib.sh) support) - [ ] Appropriate suffix label specified in status bar (e.g. `(venv)`) From 9d34cb36e168fa2733ecbc0bafae5d3025903632 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Tue, 17 Jul 2018 14:05:55 -0700 Subject: [PATCH 407/433] Specify what versions of Python we support --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aac7bfd8d044..ae7464ae31c1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Python extension for Visual Studio Code -A [Visual Studio Code](https://code.visualstudio.com/) [extension](https://marketplace.visualstudio.com/VSCode) with rich support for the [Python language](https://www.python.org/) (_including Python 3.6_), including features such as linting, debugging, IntelliSense, code navigation, code formatting, refactoring, unit tests, snippets, and more! +A [Visual Studio Code](https://code.visualstudio.com/) [extension](https://marketplace.visualstudio.com/VSCode) with rich support for the [Python language](https://www.python.org/) (2.7, >=3.4), including features such as linting, debugging, IntelliSense, code navigation, code formatting, refactoring, unit tests, snippets, and more! ## Quick start From 25006935341df7866fb41e0d8c2921e4ce71c901 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 17 Jul 2018 15:45:09 -0700 Subject: [PATCH 408/433] Relax validation of environment path variable (#2172) --- news/2 Fixes/2076.md | 1 + .../diagnostics/checks/envPathVariable.ts | 6 +- .../checks/envPathVariable.unit.test.ts | 58 +++++++++---------- 3 files changed, 32 insertions(+), 33 deletions(-) create mode 100644 news/2 Fixes/2076.md diff --git a/news/2 Fixes/2076.md b/news/2 Fixes/2076.md new file mode 100644 index 000000000000..70ce03782a26 --- /dev/null +++ b/news/2 Fixes/2076.md @@ -0,0 +1 @@ +Relax validation of the environment `Path` variable. diff --git a/src/client/application/diagnostics/checks/envPathVariable.ts b/src/client/application/diagnostics/checks/envPathVariable.ts index 9b00ed1303f1..bccb3a29c042 100644 --- a/src/client/application/diagnostics/checks/envPathVariable.ts +++ b/src/client/application/diagnostics/checks/envPathVariable.ts @@ -16,8 +16,8 @@ import { DiagnosticCodes } from '../constants'; import { DiagnosticCommandPromptHandlerServiceId, MessageCommandPrompt } from '../promptHandler'; import { DiagnosticScope, IDiagnostic, IDiagnosticHandlerService } from '../types'; -const InvalidEnvPathVariableMessage = 'The environment variable \'{0}\' seems to have some paths containing characters (\';\', \'"\' or \';;\').' + - ' The existence of such characters are known to have caused the {1} extension to not load. If the extension fails to load please modify your paths to remove these characters.'; +const InvalidEnvPathVariableMessage = 'The environment variable \'{0}\' seems to have some paths containing the \'"\' character.' + + ' The existence of such a character is known to have caused the {1} extension to not load. If the extension fails to load please modify your paths to remove this \'"\' character.'; export class InvalidEnvironmentPathVariableDiagnostic extends BaseDiagnostic { constructor(message) { @@ -79,6 +79,6 @@ export class EnvironmentPathVariableDiagnosticsService extends BaseDiagnosticsSe const pathValue = currentProc.env[this.platform.pathVariableName]; const pathSeparator = this.serviceContainer.get(IPathUtils).delimiter; const paths = pathValue.split(pathSeparator); - return paths.filter((item, index) => item.indexOf('"') >= 0 || item.indexOf(';') >= 0 || (item.length === 0 && index !== paths.length - 1)).length > 0; + return paths.filter(item => item.indexOf('"') >= 0).length > 0; } } diff --git a/src/test/application/diagnostics/checks/envPathVariable.unit.test.ts b/src/test/application/diagnostics/checks/envPathVariable.unit.test.ts index e71efc0406cd..a2f678f1f142 100644 --- a/src/test/application/diagnostics/checks/envPathVariable.unit.test.ts +++ b/src/test/application/diagnostics/checks/envPathVariable.unit.test.ts @@ -115,36 +115,34 @@ suite('Application Diagnostics - Checks Env Path Variable', () => { expect(diagnostics).to.be.deep.equal([]); }); // Note: On windows, when a path contains a `;` then Windows encloses the path within `"`. - [';;', '"'].forEach(invalidCharacter => { - test(`Should return single diagnostics for Windows if path contains ${invalidCharacter}`, async () => { - platformService.setup(p => p.isWindows).returns(() => true); - const paths = [ - path.join('one', 'two', `three${invalidCharacter}`), - path.join('one', 'two', 'four') - ].join(pathDelimiter); - procEnv.setup(env => env[pathVariableName]).returns(() => paths); - - const diagnostics = await diagnosticService.diagnose(); - - expect(diagnostics).to.be.lengthOf(1); - expect(diagnostics[0].code).to.be.equal(DiagnosticCodes.InvalidEnvironmentPathVariableDiagnostic); - expect(diagnostics[0].message).to.contain(extensionName); - expect(diagnostics[0].message).to.contain(pathVariableName); - expect(diagnostics[0].severity).to.be.equal(DiagnosticSeverity.Warning); - expect(diagnostics[0].scope).to.be.equal(DiagnosticScope.Global); - }); - test('Should not return diagnostics for Windows if path ends with delimiter', async () => { - const paths = [ - path.join('one', 'two', 'three'), - path.join('one', 'two', 'four') - ].join(pathDelimiter) + pathDelimiter; - platformService.setup(p => p.isWindows).returns(() => true); - procEnv.setup(env => env[pathVariableName]).returns(() => paths); - - const diagnostics = await diagnosticService.diagnose(); - - expect(diagnostics).to.be.lengthOf(0); - }); + test('Should return single diagnostics for Windows if path contains \'"\'', async () => { + platformService.setup(p => p.isWindows).returns(() => true); + const paths = [ + path.join('one', 'two', 'three"'), + path.join('one', 'two', 'four') + ].join(pathDelimiter); + procEnv.setup(env => env[pathVariableName]).returns(() => paths); + + const diagnostics = await diagnosticService.diagnose(); + + expect(diagnostics).to.be.lengthOf(1); + expect(diagnostics[0].code).to.be.equal(DiagnosticCodes.InvalidEnvironmentPathVariableDiagnostic); + expect(diagnostics[0].message).to.contain(extensionName); + expect(diagnostics[0].message).to.contain(pathVariableName); + expect(diagnostics[0].severity).to.be.equal(DiagnosticSeverity.Warning); + expect(diagnostics[0].scope).to.be.equal(DiagnosticScope.Global); + }); + test('Should not return diagnostics for Windows if path ends with delimiter', async () => { + const paths = [ + path.join('one', 'two', 'three'), + path.join('one', 'two', 'four') + ].join(pathDelimiter) + pathDelimiter; + platformService.setup(p => p.isWindows).returns(() => true); + procEnv.setup(env => env[pathVariableName]).returns(() => paths); + + const diagnostics = await diagnosticService.diagnose(); + + expect(diagnostics).to.be.lengthOf(0); }); test('Should display three options in message displayed with 2 commands', async () => { platformService.setup(p => p.isWindows).returns(() => true); From 4ad74bb4300fa9a91d6a6ee5d7c27b0f7c079b1c Mon Sep 17 00:00:00 2001 From: Derek Keeler Date: Tue, 17 Jul 2018 18:21:38 -0600 Subject: [PATCH 409/433] Update CONTRIBUTING - LANGUAGE SERVER.md (#2161) --- CONTRIBUTING - LANGUAGE SERVER.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING - LANGUAGE SERVER.md b/CONTRIBUTING - LANGUAGE SERVER.md index 116b668e1723..7c363ce23b81 100644 --- a/CONTRIBUTING - LANGUAGE SERVER.md +++ b/CONTRIBUTING - LANGUAGE SERVER.md @@ -22,7 +22,7 @@ ```shell git clone https://github.com/microsoft/ptvs -cd Python/Product/VsCode/AnalysisVsc +cd ptvs/Python/Product/VSCode/AnalysisVsc dotnet build ``` From 2c6a2bdbfb561874283cd3cccc6b03ced335055f Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 18 Jul 2018 10:29:50 -0700 Subject: [PATCH 410/433] Ensure Pylint<2.0.0 on Python 2.x (#2178) * Ensure Pylint<=2.0.0 on Python 2.x * Python 2.7 requires older than 2.0.0 * tests * Add Tests * Fix broken tests * Redue nesting of suites --- src/client/common/installer/condaInstaller.ts | 3 +- .../common/installer/moduleInstaller.ts | 28 +- .../common/installer/pipEnvInstaller.ts | 22 +- src/client/common/installer/pipInstaller.ts | 7 +- .../common/installer/moduleInstaller.test.ts | 78 ----- .../installer/moduleInstaller.unit.test.ts | 269 ++++++++++++++++++ src/test/common/moduleInstaller.test.ts | 9 +- src/test/unittests.ts | 2 +- 8 files changed, 318 insertions(+), 100 deletions(-) delete mode 100644 src/test/common/installer/moduleInstaller.test.ts create mode 100644 src/test/common/installer/moduleInstaller.unit.test.ts diff --git a/src/client/common/installer/condaInstaller.ts b/src/client/common/installer/condaInstaller.ts index 10acd9f3bb64..83d519be9ac0 100644 --- a/src/client/common/installer/condaInstaller.ts +++ b/src/client/common/installer/condaInstaller.ts @@ -61,8 +61,7 @@ export class CondaInstaller extends ModuleInstaller implements IModuleInstaller args.push(moduleName); return { args, - execPath: condaFile, - moduleName: '' + execPath: condaFile }; } private async isCurrentEnvironmentACondaEnvironment(resource?: Uri): Promise { diff --git a/src/client/common/installer/moduleInstaller.ts b/src/client/common/installer/moduleInstaller.ts index 5fe19952bba9..5acfab18c9a1 100644 --- a/src/client/common/installer/moduleInstaller.ts +++ b/src/client/common/installer/moduleInstaller.ts @@ -10,11 +10,10 @@ import * as path from 'path'; import * as vscode from 'vscode'; import { IInterpreterService, InterpreterType } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; -import { PythonSettings } from '../configSettings'; import { STANDARD_OUTPUT_CHANNEL } from '../constants'; import { noop } from '../core.utils'; import { ITerminalServiceFactory } from '../terminal/types'; -import { ExecutionInfo, IOutputChannel } from '../types'; +import { ExecutionInfo, IConfigurationService, IOutputChannel } from '../types'; @injectable() export abstract class ModuleInstaller { @@ -23,9 +22,12 @@ export abstract class ModuleInstaller { const executionInfo = await this.getExecutionInfo(name, resource); const terminalService = this.serviceContainer.get(ITerminalServiceFactory).getTerminalService(resource); + const executionInfoArgs = await this.processInstallArgs(executionInfo.args, resource); if (executionInfo.moduleName) { - const settings = PythonSettings.getInstance(resource); - const args = ['-m', 'pip'].concat(executionInfo.args); + const configService = this.serviceContainer.get(IConfigurationService); + const settings = configService.getSettings(resource); + const args = ['-m', executionInfo.moduleName].concat(executionInfoArgs); + const pythonPath = settings.pythonPath; const interpreterService = this.serviceContainer.get(IInterpreterService); @@ -43,12 +45,28 @@ export abstract class ModuleInstaller { await terminalService.sendCommand(pythonPath, args.concat(['--user'])); } } else { - await terminalService.sendCommand(executionInfo.execPath!, executionInfo.args); + await terminalService.sendCommand(executionInfo.execPath!, executionInfoArgs); } } public abstract isSupported(resource?: vscode.Uri): Promise; protected abstract getExecutionInfo(moduleName: string, resource?: vscode.Uri): Promise; + private async processInstallArgs(args: string[], resource?: vscode.Uri): Promise { + const indexOfPylint = args.findIndex(arg => arg.toUpperCase() === 'PYLINT'); + if (indexOfPylint === -1) { + return args; + } + // If installing pylint on python 2.x, then use pylint~=1.9.0 + const interpreterService = this.serviceContainer.get(IInterpreterService); + const currentInterpreter = await interpreterService.getActiveInterpreter(resource); + if (currentInterpreter && currentInterpreter.version_info && currentInterpreter.version_info[0] === 2) { + const newArgs = [...args]; + // This command could be sent to the terminal, hence '<' needs to be escaped for UNIX. + newArgs[indexOfPylint] = '"pylint<2.0.0"'; + return newArgs; + } + return args; + } private async isPathWritableAsync(directoryPath: string): Promise { const filePath = `${directoryPath}${path.sep}___vscpTest___`; return new Promise(resolve => { diff --git a/src/client/common/installer/pipEnvInstaller.ts b/src/client/common/installer/pipEnvInstaller.ts index 4b01df9fd3e2..2833aa218324 100644 --- a/src/client/common/installer/pipEnvInstaller.ts +++ b/src/client/common/installer/pipEnvInstaller.ts @@ -5,13 +5,14 @@ import { inject, injectable } from 'inversify'; import { Uri } from 'vscode'; import { IInterpreterLocatorService, PIPENV_SERVICE } from '../../interpreter/contracts'; import { IServiceContainer } from '../../ioc/types'; -import { ITerminalServiceFactory } from '../terminal/types'; +import { ExecutionInfo } from '../types'; +import { ModuleInstaller } from './moduleInstaller'; import { IModuleInstaller } from './types'; -const pipenvName = 'pipenv'; +export const pipenvName = 'pipenv'; @injectable() -export class PipEnvInstaller implements IModuleInstaller { +export class PipEnvInstaller extends ModuleInstaller implements IModuleInstaller { private readonly pipenv: IInterpreterLocatorService; public get displayName() { @@ -21,17 +22,18 @@ export class PipEnvInstaller implements IModuleInstaller { return 10; } - constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { + constructor(@inject(IServiceContainer) serviceContainer: IServiceContainer) { + super(serviceContainer); this.pipenv = this.serviceContainer.get(IInterpreterLocatorService, PIPENV_SERVICE); } - - public installModule(name: string, resource?: Uri): Promise { - const terminalService = this.serviceContainer.get(ITerminalServiceFactory).getTerminalService(resource); - return terminalService.sendCommand(pipenvName, ['install', name, '--dev']); - } - public async isSupported(resource?: Uri): Promise { const interpreters = await this.pipenv.getInterpreters(resource); return interpreters && interpreters.length > 0; } + protected async getExecutionInfo(moduleName: string, resource?: Uri): Promise { + return { + args: ['install', moduleName, '--dev'], + execPath: pipenvName + }; + } } diff --git a/src/client/common/installer/pipInstaller.ts b/src/client/common/installer/pipInstaller.ts index 90a471b382b1..16f886f29e93 100644 --- a/src/client/common/installer/pipInstaller.ts +++ b/src/client/common/installer/pipInstaller.ts @@ -2,8 +2,9 @@ // Licensed under the MIT License. import { inject, injectable } from 'inversify'; -import { Uri, workspace } from 'vscode'; +import { Uri } from 'vscode'; import { IServiceContainer } from '../../ioc/types'; +import { IWorkspaceService } from '../application/types'; import { IPythonExecutionFactory } from '../process/types'; import { ExecutionInfo } from '../types'; import { ModuleInstaller } from './moduleInstaller'; @@ -25,14 +26,14 @@ export class PipInstaller extends ModuleInstaller implements IModuleInstaller { } protected async getExecutionInfo(moduleName: string, resource?: Uri): Promise { const proxyArgs: string[] = []; - const proxy = workspace.getConfiguration('http').get('proxy', ''); + const workspaceService = this.serviceContainer.get(IWorkspaceService); + const proxy = workspaceService.getConfiguration('http').get('proxy', ''); if (proxy.length > 0) { proxyArgs.push('--proxy'); proxyArgs.push(proxy); } return { args: [...proxyArgs, 'install', '-U', moduleName], - execPath: '', moduleName: 'pip' }; } diff --git a/src/test/common/installer/moduleInstaller.test.ts b/src/test/common/installer/moduleInstaller.test.ts deleted file mode 100644 index a7590f952896..000000000000 --- a/src/test/common/installer/moduleInstaller.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import * as path from 'path'; -import * as TypeMoq from 'typemoq'; -import { Disposable} from 'vscode'; -import { CondaInstaller } from '../../../client/common/installer/condaInstaller'; -import { PipInstaller } from '../../../client/common/installer/pipInstaller'; -import { IInstallationChannelManager, IModuleInstaller } from '../../../client/common/installer/types'; -import { ITerminalService, ITerminalServiceFactory } from '../../../client/common/terminal/types'; -import { IConfigurationService, IDisposableRegistry, IPythonSettings } from '../../../client/common/types'; -import { ICondaService, IInterpreterService } from '../../../client/interpreter/contracts'; -import { IServiceContainer } from '../../../client/ioc/types'; -import { initialize } from '../../initialize'; - -// tslint:disable-next-line:max-func-body-length -suite('Module Installerx', () => { - const pythonPath = path.join(__dirname, 'python'); - suiteSetup(initialize); - [CondaInstaller, PipInstaller].forEach(installerClass => { - let disposables: Disposable[] = []; - let installer: IModuleInstaller; - let installationChannel: TypeMoq.IMock; - let serviceContainer: TypeMoq.IMock; - let terminalService: TypeMoq.IMock; - let pythonSettings: TypeMoq.IMock; - let interpreterService: TypeMoq.IMock; - setup(() => { - serviceContainer = TypeMoq.Mock.ofType(); - - disposables = []; - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IDisposableRegistry), TypeMoq.It.isAny())).returns(() => disposables); - - installationChannel = TypeMoq.Mock.ofType(); - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInstallationChannelManager), TypeMoq.It.isAny())).returns(() => installationChannel.object); - - const condaService = TypeMoq.Mock.ofType(); - condaService.setup(c => c.getCondaFile()).returns(() => Promise.resolve('conda')); - condaService.setup(c => c.getCondaEnvironment(TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)); - - const configService = TypeMoq.Mock.ofType(); - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationService), TypeMoq.It.isAny())).returns(() => configService); - pythonSettings = TypeMoq.Mock.ofType(); - pythonSettings.setup(p => p.pythonPath).returns(() => pythonPath); - configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); - - terminalService = TypeMoq.Mock.ofType(); - const terminalServiceFactory = TypeMoq.Mock.ofType(); - terminalServiceFactory.setup(f => f.getTerminalService(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => terminalService.object); - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ITerminalServiceFactory), TypeMoq.It.isAny())).returns(() => terminalServiceFactory.object); - - interpreterService = TypeMoq.Mock.ofType(); - serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInterpreterService), TypeMoq.It.isAny())).returns(() => interpreterService.object); - - installer = new installerClass(serviceContainer.object); - }); - teardown(() => { - disposables.forEach(disposable => { - if (disposable) { - disposable.dispose(); - } - }); - }); - test(`Ensure getActiveInterperter is used (${installerClass.name})`, async () => { - if (installer.displayName !== 'Pip') { - return; - } - interpreterService.setup(i => i.getActiveInterpreter(TypeMoq.It.isAny())).returns(() => Promise.resolve(undefined)).verifiable(); - try { - await installer.installModule('xyz'); - // tslint:disable-next-line:no-empty - } catch { } - interpreterService.verifyAll(); - }); - }); -}); diff --git a/src/test/common/installer/moduleInstaller.unit.test.ts b/src/test/common/installer/moduleInstaller.unit.test.ts new file mode 100644 index 000000000000..c57c9befe149 --- /dev/null +++ b/src/test/common/installer/moduleInstaller.unit.test.ts @@ -0,0 +1,269 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:no-any max-func-body-length no-invalid-this + +import * as path from 'path'; +import * as TypeMoq from 'typemoq'; +import { Disposable, OutputChannel, Uri, WorkspaceConfiguration } from 'vscode'; +import { IWorkspaceService } from '../../../client/common/application/types'; +import { noop } from '../../../client/common/core.utils'; +import { EnumEx } from '../../../client/common/enumUtils'; +import { CondaInstaller } from '../../../client/common/installer/condaInstaller'; +import { PipEnvInstaller, pipenvName } from '../../../client/common/installer/pipEnvInstaller'; +import { PipInstaller } from '../../../client/common/installer/pipInstaller'; +import { ProductInstaller } from '../../../client/common/installer/productInstaller'; +import { IInstallationChannelManager, IModuleInstaller } from '../../../client/common/installer/types'; +import { PythonVersionInfo } from '../../../client/common/process/types'; +import { ITerminalService, ITerminalServiceFactory } from '../../../client/common/terminal/types'; +import { IConfigurationService, IDisposableRegistry, IPythonSettings, ModuleNamePurpose, Product } from '../../../client/common/types'; +import { ICondaService, IInterpreterService, InterpreterType, PythonInterpreter } from '../../../client/interpreter/contracts'; +import { IServiceContainer } from '../../../client/ioc/types'; + +/* Complex test to ensure we cover all combinations: +We could have written separate tests for each installer, but we'd be replicate code. +Both approachs have their benefits. + +Comnbinations of: +1. With and without a workspace. +2. Http Proxy configuration. +3. All products. +4. Different versions of Python. +5. With and without conda. +6. Conda environments with names and without names. +7. All installers. +*/ +suite('Module Installer', () => { + const pythonPath = path.join(__dirname, 'python'); + [CondaInstaller, PipInstaller, PipEnvInstaller].forEach(installerClass => { + // Proxy info is relevant only for PipInstaller. + const proxyServers = installerClass === PipInstaller ? ['', 'proxy:1234'] : ['']; + proxyServers.forEach(proxyServer => { + [undefined, Uri.file('/users/dev/xyz')].forEach(resource => { + // Conda info is relevant only for CondaInstaller. + const condaEnvs = installerClass === CondaInstaller ? [{ name: 'My-Env01', path: '' }, { name: '', path: '/conda/path' }] : []; + [undefined, ...condaEnvs].forEach(condaEnvInfo => { + const testProxySuffix = proxyServer.length === 0 ? 'without proxy info' : 'with proxy info'; + const testCondaEnv = condaEnvInfo ? (condaEnvInfo.name ? 'without conda name' : 'with conda path') : 'without conda'; + const testSuite = [testProxySuffix, testCondaEnv].filter(item => item.length > 0).join(', '); + suite(`${installerClass.name} (${testSuite})`, () => { + let disposables: Disposable[] = []; + let installer: IModuleInstaller; + let installationChannel: TypeMoq.IMock; + let serviceContainer: TypeMoq.IMock; + let terminalService: TypeMoq.IMock; + let pythonSettings: TypeMoq.IMock; + let interpreterService: TypeMoq.IMock; + const condaExecutable = 'my.exe'; + setup(() => { + serviceContainer = TypeMoq.Mock.ofType(); + + disposables = []; + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IDisposableRegistry), TypeMoq.It.isAny())).returns(() => disposables); + + installationChannel = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInstallationChannelManager), TypeMoq.It.isAny())).returns(() => installationChannel.object); + + const condaService = TypeMoq.Mock.ofType(); + condaService.setup(c => c.getCondaFile()).returns(() => Promise.resolve(condaExecutable)); + condaService.setup(c => c.getCondaEnvironment(TypeMoq.It.isAny())).returns(() => Promise.resolve(condaEnvInfo)); + + const configService = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IConfigurationService), TypeMoq.It.isAny())).returns(() => configService.object); + pythonSettings = TypeMoq.Mock.ofType(); + pythonSettings.setup(p => p.pythonPath).returns(() => pythonPath); + configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings.object); + + terminalService = TypeMoq.Mock.ofType(); + const terminalServiceFactory = TypeMoq.Mock.ofType(); + terminalServiceFactory.setup(f => f.getTerminalService(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => terminalService.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ITerminalServiceFactory), TypeMoq.It.isAny())).returns(() => terminalServiceFactory.object); + + interpreterService = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IInterpreterService), TypeMoq.It.isAny())).returns(() => interpreterService.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(ICondaService), TypeMoq.It.isAny())).returns(() => condaService.object); + + const workspaceService = TypeMoq.Mock.ofType(); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IWorkspaceService), TypeMoq.It.isAny())).returns(() => workspaceService.object); + const http = TypeMoq.Mock.ofType(); + http.setup(h => h.get(TypeMoq.It.isValue('proxy'), TypeMoq.It.isAny())).returns(() => proxyServer); + workspaceService.setup(w => w.getConfiguration(TypeMoq.It.isValue('http'))).returns(() => http.object); + + installer = new installerClass(serviceContainer.object); + }); + teardown(() => { + disposables.forEach(disposable => { + if (disposable) { + disposable.dispose(); + } + }); + }); + function setActiveInterpreter(activeInterpreter?: PythonInterpreter) { + interpreterService + .setup(i => i.getActiveInterpreter(TypeMoq.It.isValue(resource))) + .returns(() => Promise.resolve(activeInterpreter)) + .verifiable(TypeMoq.Times.atLeastOnce()); + } + getModuleNamesForTesting().forEach(product => { + const moduleName = product.moduleName; + async function installModuleAndVerifyCommand(command: string, expectedArgs: string[]) { + terminalService.setup(t => t.sendCommand(TypeMoq.It.isValue(command), TypeMoq.It.isValue(expectedArgs))) + .returns(() => Promise.resolve()) + .verifiable(TypeMoq.Times.once()); + + await installer.installModule(moduleName, resource); + terminalService.verifyAll(); + } + + if (product.value === Product.pylint) { + // tslint:disable-next-line:no-shadowed-variable + generatePythonInterpreterVersions().forEach(interpreterInfo => { + const majorVersion = interpreterInfo.version_info[0]; + if (majorVersion === 2) { + const testTitle = `Ensure install arg is \'pylint<2.0.0\' in ${interpreterInfo.version_info.join('.')}`; + if (installerClass === PipInstaller) { + test(testTitle, async () => { + setActiveInterpreter(interpreterInfo); + const proxyArgs = proxyServer.length === 0 ? [] : ['--proxy', proxyServer]; + const expectedArgs = ['-m', 'pip', ...proxyArgs, 'install', '-U', '"pylint<2.0.0"']; + await installModuleAndVerifyCommand(pythonPath, expectedArgs); + }); + } + if (installerClass === PipEnvInstaller) { + test(testTitle, async () => { + setActiveInterpreter(interpreterInfo); + const expectedArgs = ['install', '"pylint<2.0.0"', '--dev']; + await installModuleAndVerifyCommand(pipenvName, expectedArgs); + }); + } + if (installerClass === CondaInstaller) { + test(testTitle, async () => { + setActiveInterpreter(interpreterInfo); + const expectedArgs = ['install']; + if (condaEnvInfo && condaEnvInfo.name) { + expectedArgs.push('--name'); + expectedArgs.push(condaEnvInfo.name); + } else if (condaEnvInfo && condaEnvInfo.path) { + expectedArgs.push('--prefix'); + expectedArgs.push(condaEnvInfo.path); + } + expectedArgs.push('"pylint<2.0.0"'); + await installModuleAndVerifyCommand(condaExecutable, expectedArgs); + }); + } + } else { + const testTitle = `Ensure install arg is \'pylint\' in ${interpreterInfo.version_info.join('.')}`; + if (installerClass === PipInstaller) { + test(testTitle, async () => { + setActiveInterpreter(interpreterInfo); + const proxyArgs = proxyServer.length === 0 ? [] : ['--proxy', proxyServer]; + const expectedArgs = ['-m', 'pip', ...proxyArgs, 'install', '-U', 'pylint']; + await installModuleAndVerifyCommand(pythonPath, expectedArgs); + }); + } + if (installerClass === PipEnvInstaller) { + test(testTitle, async () => { + setActiveInterpreter(interpreterInfo); + const expectedArgs = ['install', 'pylint', '--dev']; + await installModuleAndVerifyCommand(pipenvName, expectedArgs); + }); + } + if (installerClass === CondaInstaller) { + test(testTitle, async () => { + setActiveInterpreter(interpreterInfo); + const expectedArgs = ['install']; + if (condaEnvInfo && condaEnvInfo.name) { + expectedArgs.push('--name'); + expectedArgs.push(condaEnvInfo.name); + } else if (condaEnvInfo && condaEnvInfo.path) { + expectedArgs.push('--prefix'); + expectedArgs.push(condaEnvInfo.path); + } + expectedArgs.push('pylint'); + await installModuleAndVerifyCommand(condaExecutable, expectedArgs); + }); + } + } + }); + return; + } + + if (installerClass === PipInstaller) { + test(`Ensure getActiveInterperter is used in PipInstaller (${product.name})`, async () => { + setActiveInterpreter(); + try { + await installer.installModule(product.name, resource); + } catch { + noop(); + } + interpreterService.verifyAll(); + }); + } + if (installerClass === PipInstaller) { + test(`Test Args (${product.name})`, async () => { + setActiveInterpreter(); + const proxyArgs = proxyServer.length === 0 ? [] : ['--proxy', proxyServer]; + const expectedArgs = ['-m', 'pip', ...proxyArgs, 'install', '-U', moduleName]; + await installModuleAndVerifyCommand(pythonPath, expectedArgs); + interpreterService.verifyAll(); + }); + } + if (installerClass === PipEnvInstaller) { + test(`Test args (${product.name})`, async () => { + setActiveInterpreter(); + const expectedArgs = ['install', moduleName, '--dev']; + await installModuleAndVerifyCommand(pipenvName, expectedArgs); + }); + } + if (installerClass === CondaInstaller) { + test(`Test args (${product.name})`, async () => { + setActiveInterpreter(); + const expectedArgs = ['install']; + if (condaEnvInfo && condaEnvInfo.name) { + expectedArgs.push('--name'); + expectedArgs.push(condaEnvInfo.name); + } else if (condaEnvInfo && condaEnvInfo.path) { + expectedArgs.push('--prefix'); + expectedArgs.push(condaEnvInfo.path); + } + expectedArgs.push(moduleName); + await installModuleAndVerifyCommand(condaExecutable, expectedArgs); + }); + } + }); + }); + }); + }); + }); + }); +}); + +function generatePythonInterpreterVersions() { + const versions: PythonVersionInfo[] = [[2, 7, 0, 'final'], [3, 4, 0, 'final'], [3, 5, 0, 'final'], [3, 6, 0, 'final'], [3, 7, 0, 'final']]; + return versions.map(version => { + const info = TypeMoq.Mock.ofType(); + info.setup((t: any) => t.then).returns(() => undefined); + info.setup(t => t.type).returns(() => InterpreterType.VirtualEnv); + info.setup(t => t.version_info).returns(() => version); + return info.object; + }); +} + +function getModuleNamesForTesting(): { name: string; value: Product; moduleName: string }[] { + return EnumEx.getNamesAndValues(Product) + .map(product => { + let moduleName = ''; + const mockSvc = TypeMoq.Mock.ofType().object; + const mockOutChnl = TypeMoq.Mock.ofType().object; + try { + const prodInstaller = new ProductInstaller(mockSvc, mockOutChnl); + moduleName = prodInstaller.translateProductToModuleName(product.value, ModuleNamePurpose.install); + return { name: product.name, value: product.value, moduleName }; + } catch { + return; + } + }) + .filter(item => item !== undefined) as { name: string; value: Product; moduleName: string }[]; +} diff --git a/src/test/common/moduleInstaller.test.ts b/src/test/common/moduleInstaller.test.ts index 291cc6d3f534..0cf8698fc644 100644 --- a/src/test/common/moduleInstaller.test.ts +++ b/src/test/common/moduleInstaller.test.ts @@ -3,7 +3,8 @@ import { expect } from 'chai'; import * as path from 'path'; import * as TypeMoq from 'typemoq'; -import { ConfigurationTarget, Uri } from 'vscode'; +import { ConfigurationTarget, Uri, WorkspaceConfiguration } from 'vscode'; +import { IWorkspaceService } from '../../client/common/application/types'; import { PythonSettings } from '../../client/common/configSettings'; import { ConfigurationService } from '../../client/common/configuration/service'; import { CondaInstaller } from '../../client/common/installer/condaInstaller'; @@ -103,6 +104,12 @@ suite('Module Installer', () => { ioc.serviceManager.addSingleton(IPlatformService, PlatformService); ioc.serviceManager.addSingleton(IConfigurationService, ConfigurationService); + const workspaceService = TypeMoq.Mock.ofType(); + ioc.serviceManager.addSingletonInstance(IWorkspaceService, workspaceService.object); + const http = TypeMoq.Mock.ofType(); + http.setup(h => h.get(TypeMoq.It.isValue('proxy'), TypeMoq.It.isAny())).returns(() => ''); + workspaceService.setup(w => w.getConfiguration(TypeMoq.It.isValue('http'))).returns(() => http.object); + ioc.registerMockProcessTypes(); ioc.serviceManager.addSingletonInstance(IsWindows, false); } diff --git a/src/test/unittests.ts b/src/test/unittests.ts index 8a8c41bfcb69..f2c965417650 100644 --- a/src/test/unittests.ts +++ b/src/test/unittests.ts @@ -91,7 +91,7 @@ if (require.main === module) { const timeoutArgIndex = args.findIndex(arg => arg.startsWith('timeout=')); const grepArgIndex = args.findIndex(arg => arg.startsWith('grep=')); const timeout: number | undefined = timeoutArgIndex >= 0 ? parseInt(args[timeoutArgIndex].split('=')[1].trim(), 10) : undefined; - let grep: string | undefined = timeoutArgIndex >= 0 ? args[grepArgIndex].split('=')[1].trim() : undefined; + let grep: string | undefined = grepArgIndex >= 0 ? args[grepArgIndex].split('=')[1].trim() : undefined; grep = grep && grep.length > 0 ? grep : undefined; runTests({ grep, timeout }); From 9aaed897e2d94f707805b31126592b6df96f3755 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 18 Jul 2018 11:12:26 -0700 Subject: [PATCH 411/433] Change default port in experimental debugger to 5678 (#2174) --- news/2 Fixes/2146.md | 1 + package.json | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 news/2 Fixes/2146.md diff --git a/news/2 Fixes/2146.md b/news/2 Fixes/2146.md new file mode 100644 index 000000000000..93e660fa18d3 --- /dev/null +++ b/news/2 Fixes/2146.md @@ -0,0 +1 @@ +Change the default port used in remote debugging using `Experimental` debugger to `5678`. diff --git a/package.json b/package.json index 2795d022a5c4..167c1f3b3a93 100644 --- a/package.json +++ b/package.json @@ -901,7 +901,7 @@ "name": "Attach (Remote Debug)", "type": "pythonExperimental", "request": "attach", - "port": 3000, + "port": 5678, "host": "localhost" } } @@ -1101,7 +1101,7 @@ "name": "Python Experimental: Attach", "type": "pythonExperimental", "request": "attach", - "port": 3000, + "port": 5678, "host": "localhost" }, { From 8b77cfef24a43df0e15cd138ae71bb092d5fab65 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 18 Jul 2018 13:38:13 -0700 Subject: [PATCH 412/433] Change the download links of the language server files (#2181) --- news/3 Code Health/2180.md | 1 + src/client/activation/downloader.ts | 24 +++++-- src/client/activation/platformData.ts | 15 ++-- src/test/activation/downloader.unit.test.ts | 68 +++++++++++++++++++ ...Data.test.ts => platformData.unit.test.ts} | 13 ++-- 5 files changed, 103 insertions(+), 18 deletions(-) create mode 100644 news/3 Code Health/2180.md create mode 100644 src/test/activation/downloader.unit.test.ts rename src/test/activation/{platformData.test.ts => platformData.unit.test.ts} (90%) diff --git a/news/3 Code Health/2180.md b/news/3 Code Health/2180.md new file mode 100644 index 000000000000..d49e681aa8c7 --- /dev/null +++ b/news/3 Code Health/2180.md @@ -0,0 +1 @@ +Change the download links of the language server files. diff --git a/src/client/activation/downloader.ts b/src/client/activation/downloader.ts index c80b7dd40856..8b7ed686babe 100644 --- a/src/client/activation/downloader.ts +++ b/src/client/activation/downloader.ts @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. +'use strict'; + import * as fileSystem from 'fs'; import * as path from 'path'; import * as request from 'request'; @@ -11,7 +13,7 @@ import { createDeferred } from '../common/helpers'; import { IFileSystem, IPlatformService } from '../common/platform/types'; import { IExtensionContext, IOutputChannel } from '../common/types'; import { IServiceContainer } from '../ioc/types'; -import { PlatformData } from './platformData'; +import { PlatformData, PlatformName } from './platformData'; // tslint:disable-next-line:no-require-imports no-var-requires const StreamZip = require('node-stream-zip'); @@ -21,6 +23,13 @@ const downloadBaseFileName = 'Python-Language-Server'; const downloadVersion = '0.1.0'; const downloadFileExtension = '.nupkg'; +const DownloadLinks = { + [PlatformName.Windows32Bit]: `${downloadUriPrefix}/${downloadBaseFileName}-${PlatformName.Windows32Bit}.${downloadVersion}${downloadFileExtension}`, + [PlatformName.Windows64Bit]: `${downloadUriPrefix}/${downloadBaseFileName}-${PlatformName.Windows64Bit}.${downloadVersion}${downloadFileExtension}`, + [PlatformName.Linux64Bit]: `${downloadUriPrefix}/${downloadBaseFileName}-${PlatformName.Linux64Bit}.${downloadVersion}${downloadFileExtension}`, + [PlatformName.Mac64Bit]: `${downloadUriPrefix}/${downloadBaseFileName}-${PlatformName.Mac64Bit}.${downloadVersion}${downloadFileExtension}` +}; + export class LanguageServerDownloader { private readonly output: OutputChannel; private readonly platform: IPlatformService; @@ -34,13 +43,17 @@ export class LanguageServerDownloader { this.platformData = new PlatformData(this.platform, this.fs); } - public async downloadLanguageServer(context: IExtensionContext): Promise { + public async getDownloadUri() { const platformString = await this.platformData.getPlatformName(); - const enginePackageFileName = `${downloadBaseFileName}-${platformString}.${downloadVersion}${downloadFileExtension}`; + return DownloadLinks[platformString]; + } + + public async downloadLanguageServer(context: IExtensionContext): Promise { + const downloadUri = await this.getDownloadUri(); let localTempFilePath = ''; try { - localTempFilePath = await this.downloadFile(downloadUriPrefix, enginePackageFileName, 'Downloading Microsoft Python Language Server... '); + localTempFilePath = await this.downloadFile(downloadUri, 'Downloading Microsoft Python Language Server... '); await this.unpackArchive(context.extensionPath, localTempFilePath); } catch (err) { this.output.appendLine('failed.'); @@ -53,8 +66,7 @@ export class LanguageServerDownloader { } } - private async downloadFile(location: string, fileName: string, title: string): Promise { - const uri = `${location}/${fileName}`; + private async downloadFile(uri: string, title: string): Promise { this.output.append(`Downloading ${uri}... `); const tempFile = await this.fs.createTemporaryFile(downloadFileExtension); diff --git a/src/client/activation/platformData.ts b/src/client/activation/platformData.ts index 60448dccc850..8564b8625b44 100644 --- a/src/client/activation/platformData.ts +++ b/src/client/activation/platformData.ts @@ -9,20 +9,27 @@ import { language_server_win_x86_sha512 } from './languageServerHashes'; +export enum PlatformName { + Windows32Bit = 'win-x86', + Windows64Bit = 'win-x64', + Mac64Bit = 'osx-x64', + Linux64Bit = 'linux-x64' +} + export class PlatformData { constructor(private platform: IPlatformService, fs: IFileSystem) { } - public async getPlatformName(): Promise { + public async getPlatformName(): Promise { if (this.platform.isWindows) { - return this.platform.is64bit ? 'win-x64' : 'win-x86'; + return this.platform.is64bit ? PlatformName.Windows64Bit : PlatformName.Windows32Bit; } if (this.platform.isMac) { - return 'osx-x64'; + return PlatformName.Mac64Bit; } if (this.platform.isLinux) { if (!this.platform.is64bit) { throw new Error('Microsoft Python Language Server does not support 32-bit Linux.'); } - return 'linux-x64'; + return PlatformName.Linux64Bit; } throw new Error('Unknown OS platform.'); } diff --git a/src/test/activation/downloader.unit.test.ts b/src/test/activation/downloader.unit.test.ts new file mode 100644 index 000000000000..c75b5e3b3d49 --- /dev/null +++ b/src/test/activation/downloader.unit.test.ts @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:no-unused-variable + +import * as assert from 'assert'; +import * as TypeMoq from 'typemoq'; +import { LanguageServerDownloader } from '../../client/activation/downloader'; +import { IFileSystem, IPlatformService } from '../../client/common/platform/types'; +import { IOutputChannel } from '../../client/common/types'; +import { IServiceContainer } from '../../client/ioc/types'; + +const downloadUriPrefix = 'https://pvsc.blob.core.windows.net/python-language-server'; +const downloadBaseFileName = 'Python-Language-Server'; +const downloadVersion = '0.1.0'; +const downloadFileExtension = '.nupkg'; + +suite('Activation - Downloader', () => { + let languageServerDownloader: LanguageServerDownloader; + let serviceContainer: TypeMoq.IMock; + let platformService: TypeMoq.IMock; + setup(() => { + serviceContainer = TypeMoq.Mock.ofType(); + platformService = TypeMoq.Mock.ofType(); + const fs = TypeMoq.Mock.ofType(); + const output = TypeMoq.Mock.ofType(); + + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IOutputChannel), TypeMoq.It.isAny())).returns(() => output.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IPlatformService))).returns(() => platformService.object); + serviceContainer.setup(c => c.get(TypeMoq.It.isValue(IFileSystem))).returns(() => fs.object); + + languageServerDownloader = new LanguageServerDownloader(serviceContainer.object, ''); + }); + type PlatformIdentifier = { + windows?: boolean; + mac?: boolean; + linux?: boolean; + is64Bit?: boolean; + }; + function setupPlatform(platform: PlatformIdentifier) { + platformService.setup(x => x.isWindows).returns(() => platform.windows === true); + platformService.setup(x => x.isMac).returns(() => platform.mac === true); + platformService.setup(x => x.isLinux).returns(() => platform.linux === true); + platformService.setup(x => x.is64bit).returns(() => platform.is64Bit === true); + } + test('Windows 32Bit', async () => { + setupPlatform({ windows: true }); + const link = await languageServerDownloader.getDownloadUri(); + assert.equal(link, `${downloadUriPrefix}/${downloadBaseFileName}-win-x86.${downloadVersion}${downloadFileExtension}`); + }); + test('Windows 64Bit', async () => { + setupPlatform({ windows: true, is64Bit: true }); + const link = await languageServerDownloader.getDownloadUri(); + assert.equal(link, `${downloadUriPrefix}/${downloadBaseFileName}-win-x64.${downloadVersion}${downloadFileExtension}`); + }); + test('Mac 64Bit', async () => { + setupPlatform({ mac: true, is64Bit: true }); + const link = await languageServerDownloader.getDownloadUri(); + assert.equal(link, `${downloadUriPrefix}/${downloadBaseFileName}-osx-x64.${downloadVersion}${downloadFileExtension}`); + }); + test('Linux 64Bit', async () => { + setupPlatform({ linux: true, is64Bit: true }); + const link = await languageServerDownloader.getDownloadUri(); + assert.equal(link, `${downloadUriPrefix}/${downloadBaseFileName}-linux-x64.${downloadVersion}${downloadFileExtension}`); + }); +}); diff --git a/src/test/activation/platformData.test.ts b/src/test/activation/platformData.unit.test.ts similarity index 90% rename from src/test/activation/platformData.test.ts rename to src/test/activation/platformData.unit.test.ts index 6d847253c05f..80ce2ce494be 100644 --- a/src/test/activation/platformData.test.ts +++ b/src/test/activation/platformData.unit.test.ts @@ -6,7 +6,6 @@ import * as assert from 'assert'; import * as TypeMoq from 'typemoq'; import { PlatformData } from '../../client/activation/platformData'; import { IFileSystem, IPlatformService } from '../../client/common/platform/types'; -import { initialize } from '../initialize'; const testDataWinMac = [ { isWindows: true, is64Bit: true, expectedName: 'win-x64' }, @@ -31,8 +30,6 @@ const testDataModuleName = [ // tslint:disable-next-line:max-func-body-length suite('Activation - platform data', () => { - suiteSetup(initialize); - test('Name and hash (Windows/Mac)', async () => { for (const t of testDataWinMac) { const platformService = TypeMoq.Mock.ofType(); @@ -43,11 +40,11 @@ suite('Activation - platform data', () => { const fs = TypeMoq.Mock.ofType(); const pd = new PlatformData(platformService.object, fs.object); - let actual = await pd.getPlatformName(); + const actual = await pd.getPlatformName(); assert.equal(actual, t.expectedName, `${actual} does not match ${t.expectedName}`); - actual = await pd.getExpectedHash(); - assert.equal(actual, t.expectedName, `${actual} hash not match ${t.expectedName}`); + const actualHash = await pd.getExpectedHash(); + assert.equal(actualHash, t.expectedName, `${actual} hash not match ${t.expectedName}`); } }); test('Name and hash (Linux)', async () => { @@ -62,10 +59,10 @@ suite('Activation - platform data', () => { fs.setup(x => x.readFile(TypeMoq.It.isAnyString())).returns(() => Promise.resolve(`NAME="name"\nID=${t.name}\nID_LIKE=debian`)); const pd = new PlatformData(platformService.object, fs.object); - let actual = await pd.getPlatformName(); + const actual = await pd.getPlatformName(); assert.equal(actual, t.expectedName, `${actual} does not match ${t.expectedName}`); - actual = await pd.getExpectedHash(); + const actualHash = await pd.getExpectedHash(); assert.equal(actual, t.expectedName, `${actual} hash not match ${t.expectedName}`); } }); From 7c8caf1d614d51ef7164d4e8b38064d628657ce7 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 18 Jul 2018 14:45:18 -0700 Subject: [PATCH 413/433] Change shortcut from ctrl+enter to shift+enter (#2189) --- .github/test_plan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/test_plan.md b/.github/test_plan.md index 8dc68cca76ce..a98b21fea0b6 100644 --- a/.github/test_plan.md +++ b/.github/test_plan.md @@ -33,7 +33,7 @@ - [ ] `Run Selection/Line in Python Terminal` - [ ] Right-click - [ ] Command - - [ ] `Ctrl-Enter` + - [ ] `Shift+Enter` #### Virtual environments From e41e08d4dc114fef6d94bfee5a006b0d16f189de Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 18 Jul 2018 14:57:08 -0700 Subject: [PATCH 414/433] Register test manager when using the new language server (#2187) --- news/2 Fixes/2186.md | 1 + src/client/activation/languageServer.ts | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 news/2 Fixes/2186.md diff --git a/news/2 Fixes/2186.md b/news/2 Fixes/2186.md new file mode 100644 index 000000000000..a2f6df4f564a --- /dev/null +++ b/news/2 Fixes/2186.md @@ -0,0 +1 @@ +Register test manager when using the new language server. diff --git a/src/client/activation/languageServer.ts b/src/client/activation/languageServer.ts index 5154b92a41af..14e0d53ff0f1 100644 --- a/src/client/activation/languageServer.ts +++ b/src/client/activation/languageServer.ts @@ -11,7 +11,7 @@ import { isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; import { createDeferred, Deferred } from '../common/helpers'; import { IFileSystem, IPlatformService } from '../common/platform/types'; import { StopWatch } from '../common/stopWatch'; -import { IConfigurationService, IExtensionContext, IOutputChannel, IPythonSettings } from '../common/types'; +import { IConfigurationService, IExtensionContext, ILogger, IOutputChannel, IPythonSettings } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { PYTHON_LANGUAGE_SERVER_DOWNLOADED, @@ -19,6 +19,7 @@ import { PYTHON_LANGUAGE_SERVER_ERROR } from '../telemetry/constants'; import { getTelemetryReporter } from '../telemetry/telemetry'; +import { IUnitTestManagementService } from '../unittests/types'; import { LanguageServerDownloader } from './downloader'; import { InterpreterData, InterpreterDataService } from './interpreterDataService'; import { PlatformData } from './platformData'; @@ -89,6 +90,11 @@ export class LanguageServerExtensionActivator implements IExtensionActivator { if (!clientOptions) { return false; } + + const testManagementService = this.services.get(IUnitTestManagementService); + testManagementService.activate() + .catch(ex => this.services.get(ILogger).logError('Failed to activate Unit Tests', ex)); + return this.startLanguageServer(clientOptions); } From aade701d35dcdb009f601b36ad6ed2c69597ed71 Mon Sep 17 00:00:00 2001 From: Derek Keeler Date: Wed, 18 Jul 2018 16:27:47 -0600 Subject: [PATCH 415/433] Add two new popups: One to switch to new LS, another to ask for feedback. (#2173) --- news/1 Enhancements/2127.md | 1 + src/client/activation/languageServer.ts | 21 ++- src/client/common/types.ts | 20 +++ src/client/common/utils.ts | 16 ++ src/client/debugger/banner.ts | 4 +- src/client/debugger/serviceRegistry.ts | 9 +- src/client/debugger/types.ts | 10 -- src/client/extension.ts | 6 +- .../languageServerSurveyBanner.ts | 143 ++++++++++++++++++ .../proposeLanguageServerBanner.ts | 124 +++++++++++++++ src/client/providers/jediProxy.ts | 12 +- .../unittests/pytest/services/argsService.ts | 7 +- src/test/debugger/banner.unit.test.ts | 4 +- .../banners/languageServerSurvey.unit.test.ts | 105 +++++++++++++ ...roposeNewLanguageServerBanner.unit.test.ts | 85 +++++++++++ 15 files changed, 542 insertions(+), 25 deletions(-) create mode 100644 news/1 Enhancements/2127.md create mode 100644 src/client/languageServices/languageServerSurveyBanner.ts create mode 100644 src/client/languageServices/proposeLanguageServerBanner.ts create mode 100644 src/test/unittests/banners/languageServerSurvey.unit.test.ts create mode 100644 src/test/unittests/banners/proposeNewLanguageServerBanner.unit.test.ts diff --git a/news/1 Enhancements/2127.md b/news/1 Enhancements/2127.md new file mode 100644 index 000000000000..04135259c073 --- /dev/null +++ b/news/1 Enhancements/2127.md @@ -0,0 +1 @@ +Add two popups to the extension: one to ask users to move to the new language server, the other to request feedback from users of that language server. diff --git a/src/client/activation/languageServer.ts b/src/client/activation/languageServer.ts index 14e0d53ff0f1..8c416206a7b1 100644 --- a/src/client/activation/languageServer.ts +++ b/src/client/activation/languageServer.ts @@ -3,15 +3,18 @@ import { inject, injectable } from 'inversify'; import * as path from 'path'; -import { OutputChannel, Uri } from 'vscode'; -import { Disposable, LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient'; +import { CancellationToken, CompletionContext, OutputChannel, Position, + TextDocument, Uri } from 'vscode'; +import { Disposable, LanguageClient, LanguageClientOptions, + ProvideCompletionItemsSignature, ServerOptions } from 'vscode-languageclient'; import { IApplicationShell, ICommandManager, IWorkspaceService } from '../common/application/types'; import { PythonSettings } from '../common/configSettings'; import { isTestExecution, STANDARD_OUTPUT_CHANNEL } from '../common/constants'; import { createDeferred, Deferred } from '../common/helpers'; import { IFileSystem, IPlatformService } from '../common/platform/types'; import { StopWatch } from '../common/stopWatch'; -import { IConfigurationService, IExtensionContext, ILogger, IOutputChannel, IPythonSettings } from '../common/types'; +import { BANNER_NAME_LS_SURVEY, IConfigurationService, IExtensionContext, ILogger, + IOutputChannel, IPythonExtensionBanner, IPythonSettings } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { PYTHON_LANGUAGE_SERVER_DOWNLOADED, @@ -51,6 +54,7 @@ export class LanguageServerExtensionActivator implements IExtensionActivator { private excludedFiles: string[] = []; private typeshedPaths: string[] = []; private loadExtensionArgs: {} | undefined; + private surveyBanner: IPythonExtensionBanner; // tslint:disable-next-line:no-unused-variable private progressReporting: ProgressReporting | undefined; @@ -81,6 +85,8 @@ export class LanguageServerExtensionActivator implements IExtensionActivator { } )); + this.surveyBanner = services.get(IPythonExtensionBanner, BANNER_NAME_LS_SURVEY); + (this.configuration.getSettings() as PythonSettings).addListener('change', this.onSettingsChanged); } @@ -155,6 +161,7 @@ export class LanguageServerExtensionActivator implements IExtensionActivator { if (this.loadExtensionArgs) { this.languageClient!.sendRequest('python/loadExtension', this.loadExtensionArgs); } + this.startupCompleted.resolve(); } @@ -250,6 +257,14 @@ export class LanguageServerExtensionActivator implements IExtensionActivator { testEnvironment: isTestExecution(), analysisUpdates: true, traceLogging + }, + middleware: { + provideCompletionItem: (document: TextDocument, position: Position, context: CompletionContext, token: CancellationToken, next: ProvideCompletionItemsSignature) => { + if (this.surveyBanner) { + this.surveyBanner.showBanner().ignoreErrors(); + } + return next(document, position, context, token); + } } }; } diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 4e5c35215e63..83dee6718c57 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -267,3 +267,23 @@ export const IBrowserService = Symbol('IBrowserService'); export interface IBrowserService { launch(url: string): void; } + +export const IExperimentalDebuggerBanner = Symbol('IExperimentalDebuggerBanner'); +export interface IExperimentalDebuggerBanner { + enabled: boolean; + initialize(): void; + showBanner(): Promise; + shouldShowBanner(): Promise; + disable(): Promise; + launchSurvey(): Promise; +} + +export const IPythonExtensionBanner = Symbol('IPythonExtensionBanner'); +export interface IPythonExtensionBanner { + enabled: boolean; + shownCount: Promise; + optionLabels: string[]; + showBanner(): Promise; +} +export const BANNER_NAME_LS_SURVEY: string = 'LSSurveyBanner'; +export const BANNER_NAME_PROPOSE_LS: string = 'ProposeLS'; diff --git a/src/client/common/utils.ts b/src/client/common/utils.ts index 3e803b388466..4cd5f2c027e0 100644 --- a/src/client/common/utils.ts +++ b/src/client/common/utils.ts @@ -1,6 +1,7 @@ 'use strict'; // tslint:disable: no-any one-line no-suspicious-comment prefer-template prefer-const no-unnecessary-callback-wrapper no-function-expression no-string-literal no-control-regex no-shadowed-variable +import * as crypto from 'crypto'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; @@ -111,3 +112,18 @@ export function arePathsSame(path1: string, path2: string) { return path1 === path2; } } + +function getRandom(): number { + let num: number = 0; + + const buf: Buffer = crypto.randomBytes(2); + num = (buf.readUInt8(0) << 8) + buf.readUInt8(1); + + const maxValue: number = Math.pow(16, 4) - 1; + return (num / maxValue); +} + +export function getRandomBetween(min: number = 0, max: number = 10): number { + const randomVal: number = getRandom(); + return min + (randomVal * (max - min)); +} diff --git a/src/client/debugger/banner.ts b/src/client/debugger/banner.ts index a5971b7cfe36..480dc604b5e0 100644 --- a/src/client/debugger/banner.ts +++ b/src/client/debugger/banner.ts @@ -8,10 +8,10 @@ import { inject, injectable } from 'inversify'; import { Disposable } from 'vscode'; import { IApplicationEnvironment, IApplicationShell, IDebugService } from '../common/application/types'; import '../common/extensions'; -import { IBrowserService, IDisposableRegistry, ILogger, IPersistentStateFactory } from '../common/types'; +import { IBrowserService, IDisposableRegistry, IExperimentalDebuggerBanner, + ILogger, IPersistentStateFactory } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { ExperimentalDebuggerType } from './Common/constants'; -import { IExperimentalDebuggerBanner } from './types'; export enum PersistentStateKeys { ShowBanner = 'ShowBanner', diff --git a/src/client/debugger/serviceRegistry.ts b/src/client/debugger/serviceRegistry.ts index e634d828eed7..efb44040b4cf 100644 --- a/src/client/debugger/serviceRegistry.ts +++ b/src/client/debugger/serviceRegistry.ts @@ -9,16 +9,19 @@ import { FileSystem } from '../common/platform/fileSystem'; import { PlatformService } from '../common/platform/platformService'; import { IFileSystem, IPlatformService } from '../common/platform/types'; import { CurrentProcess } from '../common/process/currentProcess'; -import { ICurrentProcess, ISocketServer } from '../common/types'; +import { BANNER_NAME_LS_SURVEY, BANNER_NAME_PROPOSE_LS, ICurrentProcess, + IExperimentalDebuggerBanner, IPythonExtensionBanner, ISocketServer } from '../common/types'; import { ServiceContainer } from '../ioc/container'; import { ServiceManager } from '../ioc/serviceManager'; import { IServiceContainer, IServiceManager } from '../ioc/types'; +import { LanguageServerSurveyBanner } from '../languageServices/languageServerSurveyBanner'; +import { ProposeLanguageServerBanner } from '../languageServices/proposeLanguageServerBanner'; import { ExperimentalDebuggerBanner } from './banner'; import { DebugStreamProvider } from './Common/debugStreamProvider'; import { ProtocolLogger } from './Common/protocolLogger'; import { ProtocolParser } from './Common/protocolParser'; import { ProtocolMessageWriter } from './Common/protocolWriter'; -import { IDebugStreamProvider, IExperimentalDebuggerBanner, IProtocolLogger, IProtocolMessageWriter, IProtocolParser } from './types'; +import { IDebugStreamProvider, IProtocolLogger, IProtocolMessageWriter, IProtocolParser } from './types'; export function initializeIoc(): IServiceContainer { const cont = new Container(); @@ -42,4 +45,6 @@ function registerDebuggerTypes(serviceManager: IServiceManager) { export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IExperimentalDebuggerBanner, ExperimentalDebuggerBanner); + serviceManager.addSingleton(IPythonExtensionBanner, LanguageServerSurveyBanner, BANNER_NAME_LS_SURVEY); + serviceManager.addSingleton(IPythonExtensionBanner, ProposeLanguageServerBanner, BANNER_NAME_PROPOSE_LS); } diff --git a/src/client/debugger/types.ts b/src/client/debugger/types.ts index 6a641a08e8ad..c34031b082af 100644 --- a/src/client/debugger/types.ts +++ b/src/client/debugger/types.ts @@ -38,13 +38,3 @@ export interface IProtocolMessageWriter { } export const IDebugConfigurationProvider = Symbol('DebugConfigurationProvider'); - -export const IExperimentalDebuggerBanner = Symbol('IExperimentalDebuggerBanner'); -export interface IExperimentalDebuggerBanner { - enabled: boolean; - initialize(): void; - showBanner(): Promise; - shouldShowBanner(): Promise; - disable(): Promise; - launchSurvey(): Promise; -} diff --git a/src/client/extension.ts b/src/client/extension.ts index d3528183e022..2761d4b6b863 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -26,13 +26,15 @@ import { registerTypes as platformRegisterTypes } from './common/platform/servic import { registerTypes as processRegisterTypes } from './common/process/serviceRegistry'; import { registerTypes as commonRegisterTypes } from './common/serviceRegistry'; import { ITerminalHelper } from './common/terminal/types'; -import { GLOBAL_MEMENTO, IConfigurationService, IDisposableRegistry, IExtensionContext, ILogger, IMemento, IOutputChannel, IPersistentStateFactory, WORKSPACE_MEMENTO } from './common/types'; +import { GLOBAL_MEMENTO, IConfigurationService, IDisposableRegistry, + IExperimentalDebuggerBanner, IExtensionContext, ILogger, IMemento, IOutputChannel, + IPersistentStateFactory, WORKSPACE_MEMENTO } from './common/types'; import { registerTypes as variableRegisterTypes } from './common/variables/serviceRegistry'; import { AttachRequestArguments, LaunchRequestArguments } from './debugger/Common/Contracts'; import { BaseConfigurationProvider } from './debugger/configProviders/baseProvider'; import { registerTypes as debugConfigurationRegisterTypes } from './debugger/configProviders/serviceRegistry'; import { registerTypes as debuggerRegisterTypes } from './debugger/serviceRegistry'; -import { IDebugConfigurationProvider, IExperimentalDebuggerBanner } from './debugger/types'; +import { IDebugConfigurationProvider } from './debugger/types'; import { registerTypes as formattersRegisterTypes } from './formatters/serviceRegistry'; import { IInterpreterSelector } from './interpreter/configuration/types'; import { ICondaService, IInterpreterService, PythonInterpreter } from './interpreter/contracts'; diff --git a/src/client/languageServices/languageServerSurveyBanner.ts b/src/client/languageServices/languageServerSurveyBanner.ts new file mode 100644 index 000000000000..7a375df9bb49 --- /dev/null +++ b/src/client/languageServices/languageServerSurveyBanner.ts @@ -0,0 +1,143 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { IApplicationShell } from '../common/application/types'; +import '../common/extensions'; +import { IBrowserService, IPersistentStateFactory, + IPythonExtensionBanner } from '../common/types'; +import { getRandomBetween } from '../common/utils'; + +// persistent state names, exported to make use of in testing +export enum LSSurveyStateKeys { + ShowBanner = 'ShowLSSurveyBanner', + ShowAttemptCounter = 'LSSurveyShowAttempt', + ShowAfterCompletionCount = 'LSSurveyShowCount' +} + +enum LSSurveyLabelIndex { + Yes, + No +} + +/* +This class represents a popup that will ask our users for some feedback after +a specific event occurs N times. +*/ +@injectable() +export class LanguageServerSurveyBanner implements IPythonExtensionBanner { + private disabledInCurrentSession: boolean = false; + private minCompletionsBeforeShow: number; + private maxCompletionsBeforeShow: number; + private isInitialized: boolean = false; + private bannerMessage: string = 'Can you please take 2 minutes to tell us how the Experimental Debugger is working for you?'; + private bannerLabels: string [] = [ 'Yes, take survey now', 'No, thanks']; + + constructor( + @inject(IApplicationShell) private appShell: IApplicationShell, + @inject(IPersistentStateFactory) private persistentState: IPersistentStateFactory, + @inject(IBrowserService) private browserService: IBrowserService, + showAfterMinimumEventsCount: number = 100, + showBeforeMaximumEventsCount: number = 500) + { + this.minCompletionsBeforeShow = showAfterMinimumEventsCount; + this.maxCompletionsBeforeShow = showBeforeMaximumEventsCount; + this.initialize(); + } + + public initialize(): void { + if (this.isInitialized) { + return; + } + this.isInitialized = true; + + if (this.minCompletionsBeforeShow >= this.maxCompletionsBeforeShow) { + this.disable().ignoreErrors(); + } + } + + public get optionLabels(): string[] { + return this.bannerLabels; + } + + public get shownCount(): Promise { + return this.getPythonLSLaunchCounter(); + } + + public get enabled(): boolean { + return this.persistentState.createGlobalPersistentState(LSSurveyStateKeys.ShowBanner, true).value; + } + + public async showBanner(): Promise { + if (!this.enabled || this.disabledInCurrentSession) { + return; + } + + const launchCounter: number = await this.incrementPythonLanguageServiceLaunchCounter(); + const show = await this.shouldShowBanner(launchCounter); + if (!show) { + return; + } + + const response = await this.appShell.showInformationMessage(this.bannerMessage, ...this.bannerLabels); + switch (response) { + case this.bannerLabels[LSSurveyLabelIndex.Yes]: + { + await this.launchSurvey(); + await this.disable(); + break; + } + case this.bannerLabels[LSSurveyLabelIndex.No]: { + await this.disable(); + break; + } + default: { + // Disable for the current session. + this.disabledInCurrentSession = true; + } + } + } + + public async shouldShowBanner(launchCounter?: number): Promise { + if (!this.enabled || this.disabledInCurrentSession) { + return false; + } + + if (! launchCounter) { + launchCounter = await this.getPythonLSLaunchCounter(); + } + const threshold: number = await this.getPythonLSLaunchThresholdCounter(); + + return launchCounter >= threshold; + } + + public async disable(): Promise { + await this.persistentState.createGlobalPersistentState(LSSurveyStateKeys.ShowBanner, false).updateValue(false); + } + + public async launchSurvey(): Promise { + const launchCounter = await this.getPythonLSLaunchCounter(); + this.browserService.launch(`https://www.research.net/r/LJZV9BZ?n=${launchCounter}`); + } + + private async incrementPythonLanguageServiceLaunchCounter(): Promise { + const state = this.persistentState.createGlobalPersistentState(LSSurveyStateKeys.ShowAttemptCounter, 0); + await state.updateValue(state.value + 1); + return state.value; + } + + private async getPythonLSLaunchCounter(): Promise { + const state = this.persistentState.createGlobalPersistentState(LSSurveyStateKeys.ShowAttemptCounter, 0); + return state.value; + } + + private async getPythonLSLaunchThresholdCounter(): Promise { + const state = this.persistentState.createGlobalPersistentState(LSSurveyStateKeys.ShowAfterCompletionCount, undefined); + if (state.value === undefined) { + await state.updateValue(getRandomBetween(this.minCompletionsBeforeShow, this.maxCompletionsBeforeShow)); + } + return state.value!; + } +} diff --git a/src/client/languageServices/proposeLanguageServerBanner.ts b/src/client/languageServices/proposeLanguageServerBanner.ts new file mode 100644 index 000000000000..7ae5dc6b5b98 --- /dev/null +++ b/src/client/languageServices/proposeLanguageServerBanner.ts @@ -0,0 +1,124 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { ConfigurationTarget } from 'vscode'; +import { IApplicationShell } from '../common/application/types'; +import '../common/extensions'; +import { IConfigurationService, IPersistentStateFactory, + IPythonExtensionBanner } from '../common/types'; +import { getRandomBetween } from '../common/utils'; + +// persistent state names, exported to make use of in testing +export enum ProposeLSStateKeys { + ShowBanner = 'ProposeLSBanner' +} + +enum ProposeLSLabelIndex { + Yes, + No, + Later +} + +/* +This class represents a popup that propose that the user try out a new +feature of the extension, and optionally enable that new feature if they +choose to do so. It is meant to be shown only to a subset of our users, +and will show as soon as it is instructed to do so, if a random sample +function enables the popup for this user. +*/ +@injectable() +export class ProposeLanguageServerBanner implements IPythonExtensionBanner { + private initialized?: boolean; + private disabledInCurrentSession: boolean = false; + private sampleSizePerHundred: number; + private bannerMessage: string = 'Try out Preview of our new Python Language Server to get richer and faster IntelliSense completions, and syntax errors as you type.'; + private bannerLabels: string[] = [ 'Try it now', 'No thanks', 'Remind me Later' ]; + + constructor( + @inject(IApplicationShell) private appShell: IApplicationShell, + @inject(IPersistentStateFactory) private persistentState: IPersistentStateFactory, + @inject(IConfigurationService) private configuration: IConfigurationService, + sampleSizePerOneHundredUsers: number = 10) + { + this.sampleSizePerHundred = sampleSizePerOneHundredUsers; + this.initialize(); + } + + public initialize() { + if (this.initialized) { + return; + } + this.initialized = true; + + // Don't even bother adding handlers if banner has been turned off. + if (!this.enabled) { + return; + } + + // we only want 10% of folks that use Jedi to see this survey. + const randomSample: number = getRandomBetween(0, 100); + if (randomSample >= this.sampleSizePerHundred) { + this.disable().ignoreErrors(); + return; + } + } + + public get shownCount(): Promise { + return Promise.resolve(-1); // we don't count this popup banner! + } + + public get optionLabels(): string[] { + return this.bannerLabels; + } + + public get enabled(): boolean { + return this.persistentState.createGlobalPersistentState(ProposeLSStateKeys.ShowBanner, true).value; + } + + public async showBanner(): Promise { + if (!this.enabled) { + return; + } + + const show = await this.shouldShowBanner(); + if (!show) { + return; + } + + const response = await this.appShell.showInformationMessage(this.bannerMessage, ...this.bannerLabels); + switch (response) { + case this.bannerLabels[ProposeLSLabelIndex.Yes]: { + await this.enableNewLanguageServer(); + await this.disable(); + break; + } + case this.bannerLabels[ProposeLSLabelIndex.No]: { + await this.disable(); + break; + } + case this.bannerLabels[ProposeLSLabelIndex.Later]: { + this.disabledInCurrentSession = true; + break; + } + default: { + // Disable for the current session. + this.disabledInCurrentSession = true; + } + } + } + + public async shouldShowBanner(): Promise { + return Promise.resolve(this.enabled && !this.disabledInCurrentSession); + } + + public async disable(): Promise { + await this.persistentState.createGlobalPersistentState(ProposeLSStateKeys.ShowBanner, false).updateValue(false); + } + + public async enableNewLanguageServer(): Promise { + await this.configuration.updateSettingAsync('jediEnabled', false, undefined, ConfigurationTarget.Global); + } +} diff --git a/src/client/providers/jediProxy.ts b/src/client/providers/jediProxy.ts index 73e1dbe44992..962b6dc8c0b0 100644 --- a/src/client/providers/jediProxy.ts +++ b/src/client/providers/jediProxy.ts @@ -6,14 +6,16 @@ import { ChildProcess } from 'child_process'; import * as fs from 'fs-extra'; import * as path from 'path'; import * as pidusage from 'pidusage'; -import { CancellationToken, CancellationTokenSource, CompletionItemKind, Disposable, SymbolKind, Uri } from 'vscode'; +import { CancellationToken, CancellationTokenSource, CompletionItemKind, + Disposable, SymbolKind, Uri } from 'vscode'; import { PythonSettings } from '../common/configSettings'; +import { isTestExecution } from '../common/constants'; import { debounce, swallowExceptions } from '../common/decorators'; import '../common/extensions'; import { createDeferred, Deferred } from '../common/helpers'; import { IPythonExecutionFactory } from '../common/process/types'; import { StopWatch } from '../common/stopWatch'; -import { ILogger } from '../common/types'; +import { BANNER_NAME_PROPOSE_LS, ILogger, IPythonExtensionBanner } from '../common/types'; import { IEnvironmentVariablesProvider } from '../common/variables/types'; import { IServiceContainer } from '../ioc/types'; import * as logger from './../common/logger'; @@ -148,6 +150,7 @@ export class JediProxy implements Disposable { private pidUsageFailures = { timer: new StopWatch(), counter: 0 }; private lastCmdIdProcessed?: number; private lastCmdIdProcessedForPidUsage?: number; + private proposeNewLanguageServerPopup: IPythonExtensionBanner; public constructor(private extensionRootDir: string, workspacePath: string, private serviceContainer: IServiceContainer) { this.workspacePath = workspacePath; @@ -158,6 +161,8 @@ export class JediProxy implements Disposable { this.initialized = createDeferred(); this.startLanguageServer().then(() => this.initialized.resolve()).ignoreErrors(); + this.proposeNewLanguageServerPopup = serviceContainer.get(IPythonExtensionBanner, BANNER_NAME_PROPOSE_LS); + this.checkJediMemoryFootprint().ignoreErrors(); } @@ -292,6 +297,9 @@ export class JediProxy implements Disposable { private async startLanguageServer(): Promise { const newAutoComletePaths = await this.buildAutoCompletePaths(); this.additionalAutoCompletePaths = newAutoComletePaths; + if (!isTestExecution()) { + await this.proposeNewLanguageServerPopup.showBanner(); + } return this.restartLanguageServer(); } private restartLanguageServer(): Promise { diff --git a/src/client/unittests/pytest/services/argsService.ts b/src/client/unittests/pytest/services/argsService.ts index c539fdbae265..e8942a840b9f 100644 --- a/src/client/unittests/pytest/services/argsService.ts +++ b/src/client/unittests/pytest/services/argsService.ts @@ -9,6 +9,7 @@ import { IArgumentsHelper, IArgumentsService, TestFilter } from '../../types'; const OptionsWithArguments = ['-c', '-k', '-m', '-o', '-p', '-r', '-W', '--assert', '--basetemp', '--capture', '--color', '--confcutdir', + '--cov', '--cov-config', '--cov-fail-under', '--cov-report', '--deselect', '--dist', '--doctest-glob', '--doctest-report', '--durations', '--ignore', '--import-mode', '--junit-prefix', '--junit-xml', '--last-failed-no-failures', @@ -21,12 +22,14 @@ const OptionsWithArguments = ['-c', '-k', '-m', '-o', '-p', '-r', '-W', '--numprocesses', '--rsyncdir', '--rsyncignore', '--tx']; const OptionsWithoutArguments = ['--cache-clear', '--cache-show', '--collect-in-virtualenv', - '--collect-only', '--continue-on-collection-errors', '--debug', '--disable-pytest-warnings', + '--collect-only', '--continue-on-collection-errors', + '--cov-append', '--cov-branch', '--debug', '--disable-pytest-warnings', '--disable-warnings', '--doctest-continue-on-failure', '--doctest-ignore-import-errors', '--doctest-modules', '--exitfirst', '--failed-first', '--ff', '--fixtures', '--fixtures-per-test', '--force-sugar', '--full-trace', '--funcargs', '--help', '--keep-duplicates', '--last-failed', '--lf', '--markers', '--new-first', '--nf', - '--no-print-logs', '--noconftest', '--old-summary', '--pdb', '--pyargs', + '--no-cov', '--no-cov-on-fail', + '--no-print-logs', '--noconftest', '--old-summary', '--pdb', '--pyargs', '-PyTest, Unittest-pyargs', '--quiet', '--runxfail', '--setup-only', '--setup-plan', '--setup-show', '--showlocals', '--strict', '--trace-config', '--verbose', '--version', '-h', '-l', '-q', '-s', '-v', '-x', '--boxed', '--forked', '--looponfail', '--tx', '-d']; diff --git a/src/test/debugger/banner.unit.test.ts b/src/test/debugger/banner.unit.test.ts index 22706fea11ae..59ac609c5c02 100644 --- a/src/test/debugger/banner.unit.test.ts +++ b/src/test/debugger/banner.unit.test.ts @@ -9,10 +9,10 @@ import { expect } from 'chai'; import * as typemoq from 'typemoq'; import { DebugSession } from 'vscode'; import { IApplicationShell, IDebugService } from '../../client/common/application/types'; -import { IBrowserService, IDisposableRegistry, ILogger, IPersistentState, IPersistentStateFactory } from '../../client/common/types'; +import { IBrowserService, IDisposableRegistry, IExperimentalDebuggerBanner, + ILogger, IPersistentState, IPersistentStateFactory } from '../../client/common/types'; import { ExperimentalDebuggerBanner, PersistentStateKeys } from '../../client/debugger/banner'; import { ExperimentalDebuggerType } from '../../client/debugger/Common/constants'; -import { IExperimentalDebuggerBanner } from '../../client/debugger/types'; import { IServiceContainer } from '../../client/ioc/types'; suite('Debugging - Banner', () => { diff --git a/src/test/unittests/banners/languageServerSurvey.unit.test.ts b/src/test/unittests/banners/languageServerSurvey.unit.test.ts new file mode 100644 index 000000000000..bf3b1660cab8 --- /dev/null +++ b/src/test/unittests/banners/languageServerSurvey.unit.test.ts @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:no-any max-func-body-length + +import { expect } from 'chai'; +import * as typemoq from 'typemoq'; +import { IApplicationShell } from '../../../client/common/application/types'; +import { IBrowserService, IConfigurationService, IPersistentState, IPersistentStateFactory } from '../../../client/common/types'; +import { LanguageServerSurveyBanner, LSSurveyStateKeys } from '../../../client/languageServices/languageServerSurveyBanner'; + +suite('Language Server Survey Banner', () => { + let config: typemoq.IMock; + let appShell: typemoq.IMock; + let browser: typemoq.IMock; + const message = 'Can you please take 2 minutes to tell us how the Experimental Debugger is working for you?'; + const yes = 'Yes, take survey now'; + const no = 'No, thanks'; + + setup(() => { + config = typemoq.Mock.ofType(); + appShell = typemoq.Mock.ofType(); + browser = typemoq.Mock.ofType(); + }); + test('Is debugger enabled upon creation?', () => { + const enabledValue: boolean = true; + const attemptCounter: number = 0; + const completionsCount: number = 0; + const testBanner: LanguageServerSurveyBanner = preparePopup(attemptCounter, completionsCount, enabledValue, 0, 100, appShell.object, browser.object); + expect(testBanner.enabled).to.be.equal(true, 'Sampling 100/100 should always enable the banner.'); + }); + test('Do not show banner when it is disabled', () => { + appShell.setup(a => a.showInformationMessage(typemoq.It.isValue(message), + typemoq.It.isValue(yes), + typemoq.It.isValue(no))) + .verifiable(typemoq.Times.never()); + const enabledValue: boolean = true; + const attemptCounter: number = 0; + const completionsCount: number = 0; + const testBanner: LanguageServerSurveyBanner = preparePopup(attemptCounter, completionsCount, enabledValue, 0, 0, appShell.object, browser.object); + testBanner.showBanner().ignoreErrors(); + }); + test('shouldShowBanner must return false when Banner is implicitly disabled by sampling', () => { + const enabledValue: boolean = true; + const attemptCounter: number = 0; + const completionsCount: number = 0; + const testBanner: LanguageServerSurveyBanner = preparePopup(attemptCounter, completionsCount, enabledValue, 0, 0, appShell.object, browser.object); + expect(testBanner.enabled).to.be.equal(false, 'We implicitly disabled the banner, it should never show.'); + }); +}); + +function preparePopup(attemptCounter: number, completionsCount: number, enabledValue: boolean, minCompletionCount: number, maxCompletionCount: number, appShell: IApplicationShell, browser: IBrowserService): LanguageServerSurveyBanner { + const myfactory: typemoq.IMock = typemoq.Mock.ofType(); + const enabledValState: typemoq.IMock> = typemoq.Mock.ofType>(); + const attemptCountState: typemoq.IMock> = typemoq.Mock.ofType>(); + const completionCountState: typemoq.IMock> = typemoq.Mock.ofType>(); + + enabledValState.setup(a => a.updateValue(typemoq.It.isValue(true))).returns(() => { + enabledValue = true; + return Promise.resolve(); + }); + enabledValState.setup(a => a.updateValue(typemoq.It.isValue(false))).returns(() => { + enabledValue = false; + return Promise.resolve(); + }); + + attemptCountState.setup(a => a.updateValue(typemoq.It.isAnyNumber())).returns(() => { + attemptCounter += 1; + return Promise.resolve(); + }); + + completionCountState.setup(a => a.updateValue(typemoq.It.isAnyNumber())).returns(() => { + completionsCount += 1; + return Promise.resolve(); + }); + + enabledValState.setup(a => a.value).returns(() => enabledValue); + attemptCountState.setup(a => a.value).returns(() => attemptCounter); + completionCountState.setup(a => a.value).returns(() => completionsCount); + + myfactory.setup(a => a.createGlobalPersistentState(typemoq.It.isValue(LSSurveyStateKeys.ShowBanner), + typemoq.It.isValue(true))).returns(() => { + return enabledValState.object; + }); + myfactory.setup(a => a.createGlobalPersistentState(typemoq.It.isValue(LSSurveyStateKeys.ShowBanner), + typemoq.It.isValue(false))).returns(() => { + return enabledValState.object; + }); + myfactory.setup(a => a.createGlobalPersistentState(typemoq.It.isValue(LSSurveyStateKeys.ShowAttemptCounter), + typemoq.It.isAnyNumber())).returns(() => { + return attemptCountState.object; + }); + myfactory.setup(a => a.createGlobalPersistentState(typemoq.It.isValue(LSSurveyStateKeys.ShowAfterCompletionCount), + typemoq.It.isAnyNumber())).returns(() => { + return completionCountState.object; + }); + return new LanguageServerSurveyBanner( + appShell, + myfactory.object, + browser, + minCompletionCount, + maxCompletionCount); +} diff --git a/src/test/unittests/banners/proposeNewLanguageServerBanner.unit.test.ts b/src/test/unittests/banners/proposeNewLanguageServerBanner.unit.test.ts new file mode 100644 index 000000000000..e11c7e75d637 --- /dev/null +++ b/src/test/unittests/banners/proposeNewLanguageServerBanner.unit.test.ts @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable:no-any max-func-body-length + +import { expect } from 'chai'; +import * as typemoq from 'typemoq'; +import { IApplicationShell } from '../../../client/common/application/types'; +import { IConfigurationService, IPersistentState, IPersistentStateFactory } from '../../../client/common/types'; +import { ProposeLanguageServerBanner, ProposeLSStateKeys } from '../../../client/languageServices/proposeLanguageServerBanner'; + +suite('Propose New Language Server Banner', () => { + let config: typemoq.IMock; + let appShell: typemoq.IMock; + const message = 'Try out Preview of our new Python Language Server to get richer and faster IntelliSense completions, and syntax errors as you type.'; + const yes = 'Try it now'; + const no = 'No thanks'; + const later = 'Remind me Later'; + + setup(() => { + config = typemoq.Mock.ofType(); + appShell = typemoq.Mock.ofType(); + }); + test('Is debugger enabled upon creation?', () => { + const enabledValue: boolean = true; + const testBanner: ProposeLanguageServerBanner = preparePopup(enabledValue, 100, appShell.object, config.object); + expect(testBanner.enabled).to.be.equal(true, 'Sampling 100/100 should always enable the banner.'); + }); + test('Do not show banner when it is disabled', () => { + appShell.setup(a => a.showInformationMessage(typemoq.It.isValue(message), + typemoq.It.isValue(yes), + typemoq.It.isValue(no), + typemoq.It.isValue(later))) + .verifiable(typemoq.Times.never()); + const enabled: boolean = true; + const testBanner: ProposeLanguageServerBanner = preparePopup(enabled, 0, appShell.object, config.object); + testBanner.showBanner().ignoreErrors(); + }); + test('shouldShowBanner must return false when Banner is implicitly disabled by sampling', () => { + const enabled: boolean = true; + const testBanner: ProposeLanguageServerBanner = preparePopup(enabled, 0, appShell.object, config.object); + expect(testBanner.enabled).to.be.equal(false, 'We implicitly disabled the banner, it should never show.'); + }); + test('shouldShowBanner must return false when Banner is explicitly disabled', async () => { + const enabled: boolean = true; + const testBanner: ProposeLanguageServerBanner = preparePopup(enabled, 100, appShell.object, config.object); + + expect(await testBanner.shouldShowBanner()).to.be.equal(true, '100% sample size should always make the banner enabled.'); + await testBanner.disable(); + expect(await testBanner.shouldShowBanner()).to.be.equal(false, 'Explicitly disabled banner shouldShowBanner != false.'); + }); +}); + +function preparePopup(enabledValue: boolean, sampleValue: number, appShell: IApplicationShell, config: IConfigurationService): ProposeLanguageServerBanner { + const myfactory: typemoq.IMock = typemoq.Mock.ofType(); + const val: typemoq.IMock> = typemoq.Mock.ofType>(); + val.setup(a => a.updateValue(typemoq.It.isValue(true))).returns(() => { + enabledValue = true; + return Promise.resolve(); + }); + val.setup(a => a.updateValue(typemoq.It.isValue(false))).returns(() => { + enabledValue = false; + return Promise.resolve(); + }); + val.setup(a => a.value).returns(() => { + return enabledValue; + }); + myfactory.setup(a => a.createGlobalPersistentState(typemoq.It.isValue(ProposeLSStateKeys.ShowBanner), + typemoq.It.isValue(true))) + .returns(() => { + return val.object; + }); + myfactory.setup(a => a.createGlobalPersistentState(typemoq.It.isValue(ProposeLSStateKeys.ShowBanner), + typemoq.It.isValue(false))) + .returns(() => { + return val.object; + }); + return new ProposeLanguageServerBanner( + appShell, + myfactory.object, + config, + sampleValue); +} From 6c90b63a668b31dd4765e5f9a61a22bc35cfadc0 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Wed, 18 Jul 2018 15:35:10 -0700 Subject: [PATCH 416/433] Final release prep (#2190) Part of #2023 --- CHANGELOG.md | 25 ++++++++++++++++++++++--- news/1 Enhancements/1591.md | 1 - news/1 Enhancements/2000.md | 1 - news/1 Enhancements/2107.md | 2 -- news/1 Enhancements/2113.md | 1 - news/1 Enhancements/2127.md | 1 - news/2 Fixes/2013.md | 1 - news/2 Fixes/2044.md | 1 - news/2 Fixes/2048.md | 1 - news/2 Fixes/2057.md | 1 - news/2 Fixes/2068.md | 2 -- news/2 Fixes/2076.md | 1 - news/2 Fixes/2079.md | 1 - news/2 Fixes/2146.md | 1 - news/2 Fixes/2186.md | 1 - news/3 Code Health/1986.md | 1 - news/3 Code Health/2128.md | 1 - news/3 Code Health/2150.md | 1 - news/3 Code Health/2180.md | 1 - package-lock.json | 2 +- package.json | 2 +- 21 files changed, 24 insertions(+), 25 deletions(-) delete mode 100644 news/1 Enhancements/1591.md delete mode 100644 news/1 Enhancements/2000.md delete mode 100644 news/1 Enhancements/2107.md delete mode 100644 news/1 Enhancements/2113.md delete mode 100644 news/1 Enhancements/2127.md delete mode 100644 news/2 Fixes/2013.md delete mode 100644 news/2 Fixes/2044.md delete mode 100644 news/2 Fixes/2048.md delete mode 100644 news/2 Fixes/2057.md delete mode 100644 news/2 Fixes/2068.md delete mode 100644 news/2 Fixes/2076.md delete mode 100644 news/2 Fixes/2079.md delete mode 100644 news/2 Fixes/2146.md delete mode 100644 news/2 Fixes/2186.md delete mode 100644 news/3 Code Health/1986.md delete mode 100644 news/3 Code Health/2128.md delete mode 100644 news/3 Code Health/2150.md delete mode 100644 news/3 Code Health/2180.md diff --git a/CHANGELOG.md b/CHANGELOG.md index d57d36b8f11d..f4fa900c30c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## 2018.7.0-beta (10 July 2018) +## 2018.7.0 (18 July 2018) ### Thanks @@ -55,6 +55,15 @@ part of! 1. Language server now reports code analysis progress in the status bar. ([#1591](https://github.com/Microsoft/vscode-python/issues/1591)) +1. Only report Language Server download progress once. + ([#2000](https://github.com/Microsoft/vscode-python/issues/2000)) +1. Messages changes to reflect name of the language server: 'Microsoft Python Language Server'; + folder name changed from `analysis` to `languageServer`. + ([#2107](https://github.com/Microsoft/vscode-python/issues/2107)) +1. Set default analysis for language server to open files only. + ([#2113](https://github.com/Microsoft/vscode-python/issues/2113)) +1. Add two popups to the extension: one to ask users to move to the new language server, the other to request feedback from users of that language server. + ([#2127](https://github.com/Microsoft/vscode-python/issues/2127)) ### Fixes @@ -69,15 +78,25 @@ part of! 1. Change keyboard shortcut for `Run Selection/Line in Python Terminal` to not interfere with the Find/Replace dialog box. ([#2068](https://github.com/Microsoft/vscode-python/issues/2068)) +1. Relax validation of the environment `Path` variable. + ([#2076](https://github.com/Microsoft/vscode-python/issues/2076)) 1. `editor.formatOnType` is more reliable handling floating point numbers. ([#2079](https://github.com/Microsoft/vscode-python/issues/2079)) +1. Change the default port used in remote debugging using `Experimental` debugger to `5678`. + ([#2146](https://github.com/Microsoft/vscode-python/issues/2146)) +1. Register test manager when using the new language server. + ([#2186](https://github.com/Microsoft/vscode-python/issues/2186)) ### Code Health 1. Removed pre-commit hook that ran unit tests. ([#1986](https://github.com/Microsoft/vscode-python/issues/1986)) - - +1. Pass OS type to the debugger. + ([#2128](https://github.com/Microsoft/vscode-python/issues/2128)) +1. Ensure 'languageServer' directory is excluded from the build output. + ([#2150](https://github.com/Microsoft/vscode-python/issues/2150)) +1. Change the download links of the language server files. + ([#2180](https://github.com/Microsoft/vscode-python/issues/2180)) diff --git a/news/1 Enhancements/1591.md b/news/1 Enhancements/1591.md deleted file mode 100644 index 758c2b0b7234..000000000000 --- a/news/1 Enhancements/1591.md +++ /dev/null @@ -1 +0,0 @@ -Language server now reports code analysis progress in the status bar. diff --git a/news/1 Enhancements/2000.md b/news/1 Enhancements/2000.md deleted file mode 100644 index 319691492f78..000000000000 --- a/news/1 Enhancements/2000.md +++ /dev/null @@ -1 +0,0 @@ -Only report Language Server download progress once. (Thanks @MikhailArkhipov) diff --git a/news/1 Enhancements/2107.md b/news/1 Enhancements/2107.md deleted file mode 100644 index 22014105dda2..000000000000 --- a/news/1 Enhancements/2107.md +++ /dev/null @@ -1,2 +0,0 @@ -Messages changes to reflect name of the language server: 'Microsoft Python Language Server'. -Folder name changed from 'analysis' to 'languageServer'. \ No newline at end of file diff --git a/news/1 Enhancements/2113.md b/news/1 Enhancements/2113.md deleted file mode 100644 index 9455e9f6077d..000000000000 --- a/news/1 Enhancements/2113.md +++ /dev/null @@ -1 +0,0 @@ -Set default analysis for language server to open files only. (Thanks @MikhailArkhipov) diff --git a/news/1 Enhancements/2127.md b/news/1 Enhancements/2127.md deleted file mode 100644 index 04135259c073..000000000000 --- a/news/1 Enhancements/2127.md +++ /dev/null @@ -1 +0,0 @@ -Add two popups to the extension: one to ask users to move to the new language server, the other to request feedback from users of that language server. diff --git a/news/2 Fixes/2013.md b/news/2 Fixes/2013.md deleted file mode 100644 index 4524ae66a0ea..000000000000 --- a/news/2 Fixes/2013.md +++ /dev/null @@ -1 +0,0 @@ -Ensure dunder variables are always displayed in code completion when using the new language server. diff --git a/news/2 Fixes/2044.md b/news/2 Fixes/2044.md deleted file mode 100644 index 122287640547..000000000000 --- a/news/2 Fixes/2044.md +++ /dev/null @@ -1 +0,0 @@ -Store testId for files & suites during unittest discovery. diff --git a/news/2 Fixes/2048.md b/news/2 Fixes/2048.md deleted file mode 100644 index 1ef400d6de06..000000000000 --- a/news/2 Fixes/2048.md +++ /dev/null @@ -1 +0,0 @@ -`editor.formatOnType` no longer adds space after `*` in multi-line arguments. diff --git a/news/2 Fixes/2057.md b/news/2 Fixes/2057.md deleted file mode 100644 index 34db4fed4207..000000000000 --- a/news/2 Fixes/2057.md +++ /dev/null @@ -1 +0,0 @@ -Fix bug where tooltips would popup whenever a comma is typed within a string. diff --git a/news/2 Fixes/2068.md b/news/2 Fixes/2068.md deleted file mode 100644 index 062dfb4d3654..000000000000 --- a/news/2 Fixes/2068.md +++ /dev/null @@ -1,2 +0,0 @@ -Change keyboard shortcut for `Run Selection/Line in Python Terminal` to not -interfere with the Find/Replace dialog box. diff --git a/news/2 Fixes/2076.md b/news/2 Fixes/2076.md deleted file mode 100644 index 70ce03782a26..000000000000 --- a/news/2 Fixes/2076.md +++ /dev/null @@ -1 +0,0 @@ -Relax validation of the environment `Path` variable. diff --git a/news/2 Fixes/2079.md b/news/2 Fixes/2079.md deleted file mode 100644 index 6b0366702a18..000000000000 --- a/news/2 Fixes/2079.md +++ /dev/null @@ -1 +0,0 @@ -`editor.formatOnType` is more reliable handling floating point numbers. diff --git a/news/2 Fixes/2146.md b/news/2 Fixes/2146.md deleted file mode 100644 index 93e660fa18d3..000000000000 --- a/news/2 Fixes/2146.md +++ /dev/null @@ -1 +0,0 @@ -Change the default port used in remote debugging using `Experimental` debugger to `5678`. diff --git a/news/2 Fixes/2186.md b/news/2 Fixes/2186.md deleted file mode 100644 index a2f6df4f564a..000000000000 --- a/news/2 Fixes/2186.md +++ /dev/null @@ -1 +0,0 @@ -Register test manager when using the new language server. diff --git a/news/3 Code Health/1986.md b/news/3 Code Health/1986.md deleted file mode 100644 index e20a4c024e14..000000000000 --- a/news/3 Code Health/1986.md +++ /dev/null @@ -1 +0,0 @@ -Removed pre-commit hook that ran unit tests. diff --git a/news/3 Code Health/2128.md b/news/3 Code Health/2128.md deleted file mode 100644 index 22ac6aecbc7a..000000000000 --- a/news/3 Code Health/2128.md +++ /dev/null @@ -1 +0,0 @@ -Pass OS type to the debugger. diff --git a/news/3 Code Health/2150.md b/news/3 Code Health/2150.md deleted file mode 100644 index 77408e558502..000000000000 --- a/news/3 Code Health/2150.md +++ /dev/null @@ -1 +0,0 @@ -Ensure 'languageServer' directory is excluded from the build output. diff --git a/news/3 Code Health/2180.md b/news/3 Code Health/2180.md deleted file mode 100644 index d49e681aa8c7..000000000000 --- a/news/3 Code Health/2180.md +++ /dev/null @@ -1 +0,0 @@ -Change the download links of the language server files. diff --git a/package-lock.json b/package-lock.json index ee9849eb35e3..8bbc19bbb321 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "python", - "version": "2018.7.0-beta", + "version": "2018.7.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 167c1f3b3a93..d9bb9b5e83ef 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2018.7.0-beta", + "version": "2018.7.0", "publisher": "ms-python", "author": { "name": "Microsoft Corporation" From 6392afbc7f6fbf24a7c86d099e35e2533ecc3282 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Wed, 18 Jul 2018 16:41:06 -0700 Subject: [PATCH 417/433] Move service registrations from debug adapter host to extension host (#2193) Fixes #2191 --- src/client/activation/serviceRegistry.ts | 5 +++++ src/client/debugger/serviceRegistry.ts | 7 +------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/client/activation/serviceRegistry.ts b/src/client/activation/serviceRegistry.ts index f9d8dfdf549d..23dffa6cd365 100644 --- a/src/client/activation/serviceRegistry.ts +++ b/src/client/activation/serviceRegistry.ts @@ -3,7 +3,10 @@ 'use strict'; +import { BANNER_NAME_LS_SURVEY, BANNER_NAME_PROPOSE_LS, IPythonExtensionBanner } from '../common/types'; import { IServiceManager } from '../ioc/types'; +import { LanguageServerSurveyBanner } from '../languageServices/languageServerSurveyBanner'; +import { ProposeLanguageServerBanner } from '../languageServices/proposeLanguageServerBanner'; import { ExtensionActivationService } from './activationService'; import { JediExtensionActivator } from './jedi'; import { LanguageServerExtensionActivator } from './languageServer'; @@ -13,4 +16,6 @@ export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IExtensionActivationService, ExtensionActivationService); serviceManager.add(IExtensionActivator, JediExtensionActivator, ExtensionActivators.Jedi); serviceManager.add(IExtensionActivator, LanguageServerExtensionActivator, ExtensionActivators.DotNet); + serviceManager.addSingleton(IPythonExtensionBanner, LanguageServerSurveyBanner, BANNER_NAME_LS_SURVEY); + serviceManager.addSingleton(IPythonExtensionBanner, ProposeLanguageServerBanner, BANNER_NAME_PROPOSE_LS); } diff --git a/src/client/debugger/serviceRegistry.ts b/src/client/debugger/serviceRegistry.ts index efb44040b4cf..3030f77d91e7 100644 --- a/src/client/debugger/serviceRegistry.ts +++ b/src/client/debugger/serviceRegistry.ts @@ -9,13 +9,10 @@ import { FileSystem } from '../common/platform/fileSystem'; import { PlatformService } from '../common/platform/platformService'; import { IFileSystem, IPlatformService } from '../common/platform/types'; import { CurrentProcess } from '../common/process/currentProcess'; -import { BANNER_NAME_LS_SURVEY, BANNER_NAME_PROPOSE_LS, ICurrentProcess, - IExperimentalDebuggerBanner, IPythonExtensionBanner, ISocketServer } from '../common/types'; +import { ICurrentProcess, IExperimentalDebuggerBanner, ISocketServer } from '../common/types'; import { ServiceContainer } from '../ioc/container'; import { ServiceManager } from '../ioc/serviceManager'; import { IServiceContainer, IServiceManager } from '../ioc/types'; -import { LanguageServerSurveyBanner } from '../languageServices/languageServerSurveyBanner'; -import { ProposeLanguageServerBanner } from '../languageServices/proposeLanguageServerBanner'; import { ExperimentalDebuggerBanner } from './banner'; import { DebugStreamProvider } from './Common/debugStreamProvider'; import { ProtocolLogger } from './Common/protocolLogger'; @@ -45,6 +42,4 @@ function registerDebuggerTypes(serviceManager: IServiceManager) { export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IExperimentalDebuggerBanner, ExperimentalDebuggerBanner); - serviceManager.addSingleton(IPythonExtensionBanner, LanguageServerSurveyBanner, BANNER_NAME_LS_SURVEY); - serviceManager.addSingleton(IPythonExtensionBanner, ProposeLanguageServerBanner, BANNER_NAME_PROPOSE_LS); } From 304936624dea975943f8607a21d7e3b203086f0e Mon Sep 17 00:00:00 2001 From: Derek Keeler Date: Mon, 23 Jul 2018 18:39:41 -0600 Subject: [PATCH 418/433] Update to latest Language Server (#2234) * Update to latest Language Server - change the download version to latest build - change the name of the executable bit of the LS * Fix unit tests - Remove hardcoded strings causing issues - Expose information from downloader/platform - Update tests for explicit OS types * Update PIP version regex - make it allow 2, or 3, version segments - handles old M.m.r or new Y.r version patterns * Remove hard-coded strings /and/ formats from tests. * Correction to the PIP_VERSION_REGEX - keep it simple, keeler. --- src/client/activation/downloader.ts | 4 ++-- src/client/activation/platformData.ts | 18 +++++++++++++++--- src/client/interpreter/interpreterVersion.ts | 2 +- src/test/activation/downloader.unit.test.ts | 16 ++++++---------- src/test/activation/platformData.unit.test.ts | 9 ++++++--- .../interpreters/interpreterVersion.test.ts | 4 ++-- 6 files changed, 32 insertions(+), 21 deletions(-) diff --git a/src/client/activation/downloader.ts b/src/client/activation/downloader.ts index 8b7ed686babe..0bb6b9dbcf20 100644 --- a/src/client/activation/downloader.ts +++ b/src/client/activation/downloader.ts @@ -20,10 +20,10 @@ const StreamZip = require('node-stream-zip'); const downloadUriPrefix = 'https://pvsc.blob.core.windows.net/python-language-server'; const downloadBaseFileName = 'Python-Language-Server'; -const downloadVersion = '0.1.0'; +const downloadVersion = '0.1.18204.3'; const downloadFileExtension = '.nupkg'; -const DownloadLinks = { +export const DownloadLinks = { [PlatformName.Windows32Bit]: `${downloadUriPrefix}/${downloadBaseFileName}-${PlatformName.Windows32Bit}.${downloadVersion}${downloadFileExtension}`, [PlatformName.Windows64Bit]: `${downloadUriPrefix}/${downloadBaseFileName}-${PlatformName.Windows64Bit}.${downloadVersion}${downloadFileExtension}`, [PlatformName.Linux64Bit]: `${downloadUriPrefix}/${downloadBaseFileName}-${PlatformName.Linux64Bit}.${downloadVersion}${downloadFileExtension}`, diff --git a/src/client/activation/platformData.ts b/src/client/activation/platformData.ts index 8564b8625b44..39cce79cc82f 100644 --- a/src/client/activation/platformData.ts +++ b/src/client/activation/platformData.ts @@ -16,6 +16,12 @@ export enum PlatformName { Linux64Bit = 'linux-x64' } +export enum PlatformLSExecutables { + Windows = 'Microsoft.Python.LanguageServer.exe', + MacOS = 'Microsoft.Python.LanguageServer', + Linux = 'Microsoft.Python.LanguageServer' +} + export class PlatformData { constructor(private platform: IPlatformService, fs: IFileSystem) { } public async getPlatformName(): Promise { @@ -39,9 +45,15 @@ export class PlatformData { } public getEngineExecutableName(): string { - return this.platform.isWindows - ? 'Microsoft.Python.LanguageServer.exe' - : 'Microsoft.Python.LanguageServer.LanguageServer'; + if (this.platform.isWindows) { + return PlatformLSExecutables.Windows; + } else if (this.platform.isLinux) { + return PlatformLSExecutables.Linux; + } else if (this.platform.isMac) { + return PlatformLSExecutables.MacOS; + } else { + return 'unknown-platform'; + } } public async getExpectedHash(): Promise { diff --git a/src/client/interpreter/interpreterVersion.ts b/src/client/interpreter/interpreterVersion.ts index 4b00ddadead2..2bfb72c4f98f 100644 --- a/src/client/interpreter/interpreterVersion.ts +++ b/src/client/interpreter/interpreterVersion.ts @@ -3,7 +3,7 @@ import '../common/extensions'; import { IProcessServiceFactory } from '../common/process/types'; import { IInterpreterVersionService } from './contracts'; -export const PIP_VERSION_REGEX = '\\d+\\.\\d+(\\.\\d+)'; +export const PIP_VERSION_REGEX = '\\d+\\.\\d+(\\.\\d+)?'; @injectable() export class InterpreterVersionService implements IInterpreterVersionService { diff --git a/src/test/activation/downloader.unit.test.ts b/src/test/activation/downloader.unit.test.ts index c75b5e3b3d49..edee2e828f3c 100644 --- a/src/test/activation/downloader.unit.test.ts +++ b/src/test/activation/downloader.unit.test.ts @@ -7,16 +7,12 @@ import * as assert from 'assert'; import * as TypeMoq from 'typemoq'; -import { LanguageServerDownloader } from '../../client/activation/downloader'; +import { DownloadLinks, LanguageServerDownloader } from '../../client/activation/downloader'; +import { PlatformName } from '../../client/activation/platformData'; import { IFileSystem, IPlatformService } from '../../client/common/platform/types'; import { IOutputChannel } from '../../client/common/types'; import { IServiceContainer } from '../../client/ioc/types'; -const downloadUriPrefix = 'https://pvsc.blob.core.windows.net/python-language-server'; -const downloadBaseFileName = 'Python-Language-Server'; -const downloadVersion = '0.1.0'; -const downloadFileExtension = '.nupkg'; - suite('Activation - Downloader', () => { let languageServerDownloader: LanguageServerDownloader; let serviceContainer: TypeMoq.IMock; @@ -48,21 +44,21 @@ suite('Activation - Downloader', () => { test('Windows 32Bit', async () => { setupPlatform({ windows: true }); const link = await languageServerDownloader.getDownloadUri(); - assert.equal(link, `${downloadUriPrefix}/${downloadBaseFileName}-win-x86.${downloadVersion}${downloadFileExtension}`); + assert.equal(link, DownloadLinks[PlatformName.Windows32Bit]); }); test('Windows 64Bit', async () => { setupPlatform({ windows: true, is64Bit: true }); const link = await languageServerDownloader.getDownloadUri(); - assert.equal(link, `${downloadUriPrefix}/${downloadBaseFileName}-win-x64.${downloadVersion}${downloadFileExtension}`); + assert.equal(link, DownloadLinks[PlatformName.Windows64Bit]); }); test('Mac 64Bit', async () => { setupPlatform({ mac: true, is64Bit: true }); const link = await languageServerDownloader.getDownloadUri(); - assert.equal(link, `${downloadUriPrefix}/${downloadBaseFileName}-osx-x64.${downloadVersion}${downloadFileExtension}`); + assert.equal(link, DownloadLinks[PlatformName.Mac64Bit]); }); test('Linux 64Bit', async () => { setupPlatform({ linux: true, is64Bit: true }); const link = await languageServerDownloader.getDownloadUri(); - assert.equal(link, `${downloadUriPrefix}/${downloadBaseFileName}-linux-x64.${downloadVersion}${downloadFileExtension}`); + assert.equal(link, DownloadLinks[PlatformName.Linux64Bit]); }); }); diff --git a/src/test/activation/platformData.unit.test.ts b/src/test/activation/platformData.unit.test.ts index 80ce2ce494be..0044c27b0182 100644 --- a/src/test/activation/platformData.unit.test.ts +++ b/src/test/activation/platformData.unit.test.ts @@ -4,7 +4,7 @@ // tslint:disable:no-unused-variable import * as assert from 'assert'; import * as TypeMoq from 'typemoq'; -import { PlatformData } from '../../client/activation/platformData'; +import { PlatformData, PlatformLSExecutables } from '../../client/activation/platformData'; import { IFileSystem, IPlatformService } from '../../client/common/platform/types'; const testDataWinMac = [ @@ -24,8 +24,9 @@ const testDataLinux = [ ]; const testDataModuleName = [ - { isWindows: true, expectedName: 'Microsoft.Python.LanguageServer.exe' }, - { isWindows: false, expectedName: 'Microsoft.Python.LanguageServer.LanguageServer' } + { isWindows: true, isMac: false, isLinux: false, expectedName: PlatformLSExecutables.Windows }, + { isWindows: false, isMac: true, isLinux: false, expectedName: PlatformLSExecutables.MacOS }, + { isWindows: false, isMac: false, isLinux: true, expectedName: PlatformLSExecutables.Linux } ]; // tslint:disable-next-line:max-func-body-length @@ -70,6 +71,8 @@ suite('Activation - platform data', () => { for (const t of testDataModuleName) { const platformService = TypeMoq.Mock.ofType(); platformService.setup(x => x.isWindows).returns(() => t.isWindows); + platformService.setup(x => x.isLinux).returns(() => t.isLinux); + platformService.setup(x => x.isMac).returns(() => t.isMac); const fs = TypeMoq.Mock.ofType(); const pd = new PlatformData(platformService.object, fs.object); diff --git a/src/test/interpreters/interpreterVersion.test.ts b/src/test/interpreters/interpreterVersion.test.ts index 6b9c9da81ad7..63b0dc45b3b9 100644 --- a/src/test/interpreters/interpreterVersion.test.ts +++ b/src/test/interpreters/interpreterVersion.test.ts @@ -43,7 +43,7 @@ suite('Interpreters display version', () => { const pyVersion = await interpreterVersion.getVersion('INVALID_INTERPRETER', 'DEFAULT_TEST_VALUE'); assert.equal(pyVersion, 'DEFAULT_TEST_VALUE', 'Incorrect version'); }); - test('Must return the pip Version', async () => { + test('Must return the pip Version.', async () => { const pythonProcess = await ioc.serviceContainer.get(IProcessServiceFactory).create(); const result = await pythonProcess.exec(PYTHON_PATH, ['-m', 'pip', '--version'], { cwd: __dirname, mergeStdOutErr: true }); const output = result.stdout.splitLines()[0]; @@ -60,7 +60,7 @@ suite('Interpreters display version', () => { // tslint:disable-next-line:no-non-null-assertion await expect(pipVersionPromise).to.eventually.equal(matches![0].trim()); }); - test('Must throw an exceptionn when pip version cannot be determine', async () => { + test('Must throw an exception when pip version cannot be determined', async () => { const interpreterVersion = ioc.serviceContainer.get(IInterpreterVersionService); const pipVersionPromise = interpreterVersion.getPipVersion('INVALID_INTERPRETER'); await expect(pipVersionPromise).to.be.rejectedWith(); From a29d60a38c1522f9bf5297b0caece63502e32ba6 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Mon, 23 Jul 2018 17:40:22 -0700 Subject: [PATCH 419/433] 2018.7.1 release (#2235) * Bump version number * Touch up news entries * Update changelog * 2018.7.1 release for new language server --- CHANGELOG.md | 9 +++++++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4fa900c30c5..bc8c712281b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 2018.7.1 (23 July 2018) + +### Fixes + +1. Update the language server to code as of + [651468731500ec1cc644029c3666c57b82f77d76](https://github.com/Microsoft/PTVS/commit/651468731500ec1cc644029c3666c57b82f77d76). + ([#2233](https://github.com/Microsoft/vscode-python/issues/2233)) + + ## 2018.7.0 (18 July 2018) ### Thanks diff --git a/package-lock.json b/package-lock.json index 8bbc19bbb321..8f6914bd4808 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "python", - "version": "2018.7.0", + "version": "2018.7.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index d9bb9b5e83ef..c502de7d7d68 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "Linting, Debugging (multi-threaded, remote), Intellisense, code formatting, refactoring, unit tests, snippets, and more.", - "version": "2018.7.0", + "version": "2018.7.1", "publisher": "ms-python", "author": { "name": "Microsoft Corporation" From 62d041b5dc034ce46039a78067f2ddbc145333e2 Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Fri, 2 Jun 2017 12:57:31 -0700 Subject: [PATCH 420/433] Use visualstudio_py_launcher in custom launcher --- pythonFiles/PythonTools/visualstudio_py_launcher.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pythonFiles/PythonTools/visualstudio_py_launcher.py b/pythonFiles/PythonTools/visualstudio_py_launcher.py index 9e202ff2064e..20bd34d237ba 100644 --- a/pythonFiles/PythonTools/visualstudio_py_launcher.py +++ b/pythonFiles/PythonTools/visualstudio_py_launcher.py @@ -72,6 +72,9 @@ def launch(): # preserve filename before we del sys filename = sys.argv[0] + # fix sys.path to be the script file dir + sys.path[0] = '' + # exclude ourselves from being debugged vspd.DONT_DEBUG.append(os.path.normcase(__file__)) From a57688b3d90ce2d30b4106f71b9f1166664a4ead Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Tue, 1 Aug 2017 14:55:56 -0700 Subject: [PATCH 421/433] Enable remote debugging of Django apps --- src/client/debugger/Common/Contracts.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/client/debugger/Common/Contracts.ts b/src/client/debugger/Common/Contracts.ts index 416f257dfe70..adfdd0fee083 100644 --- a/src/client/debugger/Common/Contracts.ts +++ b/src/client/debugger/Common/Contracts.ts @@ -25,12 +25,15 @@ export class TelemetryEvent extends OutputEvent { } } -export const VALID_DEBUG_OPTIONS = ['WaitOnAbnormalExit', +export const DjangoApp = 'DJANGO'; +export const VALID_DEBUG_OPTIONS = [ + 'WaitOnAbnormalExit', 'WaitOnNormalExit', 'RedirectOutput', 'DebugStdLib', 'BreakOnSystemExitZero', - 'DjangoDebugging']; + 'Django', +]; export enum DebugFlags { None = 0, @@ -42,6 +45,7 @@ export enum DebugOptions { WaitOnNormalExit = 'WaitOnNormalExit', RedirectOutput = 'RedirectOutput', Django = 'Django', + DjangoDebugging = 'DjangoDebugging', Jinja = 'Jinja', DebugStdLib = 'DebugStdLib', BreakOnSystemExitZero = 'BreakOnSystemExitZero', From b6eb6965b99ae850bfd1a2c3a5282b3fcb80bfc0 Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Sat, 1 Jul 2017 01:41:50 -0700 Subject: [PATCH 422/433] sys.path[0] should only be reset when debugging a file --- pythonFiles/PythonTools/visualstudio_py_launcher.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pythonFiles/PythonTools/visualstudio_py_launcher.py b/pythonFiles/PythonTools/visualstudio_py_launcher.py index 20bd34d237ba..9e202ff2064e 100644 --- a/pythonFiles/PythonTools/visualstudio_py_launcher.py +++ b/pythonFiles/PythonTools/visualstudio_py_launcher.py @@ -72,9 +72,6 @@ def launch(): # preserve filename before we del sys filename = sys.argv[0] - # fix sys.path to be the script file dir - sys.path[0] = '' - # exclude ourselves from being debugged vspd.DONT_DEBUG.append(os.path.normcase(__file__)) From 431f38adbd2e39e67594105063ead532c96ae4a2 Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Tue, 1 Aug 2017 14:38:50 -0700 Subject: [PATCH 423/433] Enable debugger engine to notify a UI server (if one exists) about its existence/readiness to attach --- pythonFiles/PythonTools/ptvsd/attach_server.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pythonFiles/PythonTools/ptvsd/attach_server.py b/pythonFiles/PythonTools/ptvsd/attach_server.py index 48dbda79b434..672a027ec2bc 100644 --- a/pythonFiles/PythonTools/ptvsd/attach_server.py +++ b/pythonFiles/PythonTools/ptvsd/attach_server.py @@ -175,6 +175,11 @@ def enable_attach(secret, address = ('0.0.0.0', DEFAULT_PORT), certfile = None, server = socket.socket(proto=socket.IPPROTO_TCP) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + if address is None: + if register_options is not None: + address = ('0.0.0.0', 0) + else: + address = ('0.0.0.0', DEFAULT_PORT) server.bind(address) server.listen(1) global _attach_port From 38d803b5ae0c302ed8e5c42a0883eb0e71fd1067 Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Tue, 1 Aug 2017 14:55:56 -0700 Subject: [PATCH 424/433] Enable remote debugging of Django apps --- src/client/debugger/Main.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/client/debugger/Main.ts b/src/client/debugger/Main.ts index c964ce47c2df..7b3a9a3ab3b1 100644 --- a/src/client/debugger/Main.ts +++ b/src/client/debugger/Main.ts @@ -340,6 +340,11 @@ export class PythonDebugger extends LoggingDebugSession { this.attachArgs.debugOptions.indexOf(DebugOptions.DjangoDebugging) >= 0) { isDjangoFile = filePath.toUpperCase().endsWith(".HTML"); } + if (this.attachArgs != null && + Array.isArray(this.attachArgs.debugOptions) && + this.attachArgs.debugOptions.indexOf(DebugOptions.DjangoDebugging) >= 0) { + isDjangoFile = filePath.toUpperCase().endsWith(".HTML"); + } condition = typeof condition === "string" ? condition : ""; From befc78bf331fbb2cb673cf933d9cb7571fd37d9c Mon Sep 17 00:00:00 2001 From: Mostafa Eweda Date: Thu, 3 Aug 2017 16:09:01 -0700 Subject: [PATCH 425/433] Enabling setting UI attach options while not enabling the attachability yet --- pythonFiles/PythonTools/ptvsd/attach_server.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pythonFiles/PythonTools/ptvsd/attach_server.py b/pythonFiles/PythonTools/ptvsd/attach_server.py index 672a027ec2bc..48dbda79b434 100644 --- a/pythonFiles/PythonTools/ptvsd/attach_server.py +++ b/pythonFiles/PythonTools/ptvsd/attach_server.py @@ -175,11 +175,6 @@ def enable_attach(secret, address = ('0.0.0.0', DEFAULT_PORT), certfile = None, server = socket.socket(proto=socket.IPPROTO_TCP) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - if address is None: - if register_options is not None: - address = ('0.0.0.0', 0) - else: - address = ('0.0.0.0', DEFAULT_PORT) server.bind(address) server.listen(1) global _attach_port From 950224276e2d0a708c7ae77ed3b0c9ba237e90c7 Mon Sep 17 00:00:00 2001 From: Aman Agarwal Date: Wed, 29 Aug 2018 18:02:37 -0700 Subject: [PATCH 426/433] Sync with Microsoft/vscode-python v2018.7.1 --- package-lock.json | 3288 ++++++++++++++++++++++----------------------- 1 file changed, 1644 insertions(+), 1644 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8f6914bd4808..8f255d8aabab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,11 +10,11 @@ "integrity": "sha1-z6I7xYQPkQTOMqZedNt+epdLvuE=", "dev": true, "requires": { - "acorn": "^5.0.3", - "css": "^2.2.1", - "normalize-path": "^2.1.1", - "source-map": "^0.5.6", - "through2": "^2.0.3" + "acorn": "5.5.3", + "css": "2.2.3", + "normalize-path": "2.1.1", + "source-map": "0.5.7", + "through2": "2.0.3" } }, "@gulp-sourcemaps/map-sources": { @@ -23,8 +23,8 @@ "integrity": "sha1-iQrnxdjId/bThIYCFazp1+yUW9o=", "dev": true, "requires": { - "normalize-path": "^2.0.1", - "through2": "^2.0.3" + "normalize-path": "2.1.1", + "through2": "2.0.3" } }, "@sindresorhus/is": { @@ -60,7 +60,7 @@ "integrity": "sha512-/kgYvj5Pwiv/bOlJ6c5GlRF/W6lUGSLrpQGl/7Gg6w7tvBYcf0iF91+wwyuwDYGO2zM0wNpcoPixZVif8I/r6g==", "dev": true, "requires": { - "@types/chai": "*" + "@types/chai": "4.1.3" } }, "@types/chai-as-promised": { @@ -69,7 +69,7 @@ "integrity": "sha512-MFiW54UOSt+f2bRw8J7LgQeIvE/9b4oGvwU7XW30S9QGAiHGnU/fmiOprsyMkdmH2rl8xSPc0/yrQw8juXU6bQ==", "dev": true, "requires": { - "@types/chai": "*" + "@types/chai": "4.1.3" } }, "@types/commander": { @@ -78,7 +78,7 @@ "integrity": "sha512-0QEFiR8ljcHp9bAbWxecjVRuAMr16ivPiGOw6KFQBVrVd0RQIcM3xKdRisH2EDWgVWujiYtHwhSkSUoAAGzH7Q==", "dev": true, "requires": { - "commander": "*" + "commander": "2.15.1" } }, "@types/decompress": { @@ -87,7 +87,7 @@ "integrity": "sha512-2jlSsNAVhrWJtgOV3V85MJ09yRoeUTUWQeeusNYAcJVkUmoVRVElvmkWN0TK+Lgdlyd9pIRyja/DTBcyqD8xyA==", "dev": true, "requires": { - "@types/node": "*" + "@types/node": "9.4.7" } }, "@types/del": { @@ -96,7 +96,7 @@ "integrity": "sha512-y6qRq6raBuu965clKgx6FHuiPu3oHdtmzMPXi8Uahsjdq1L6DL5fS/aY5/s71YwM7k6K1QIWvem5vNwlnNGIkQ==", "dev": true, "requires": { - "@types/glob": "*" + "@types/glob": "5.0.35" } }, "@types/dotenv": { @@ -105,7 +105,7 @@ "integrity": "sha512-mmhpINC/HcLGQK5ikFJlLXINVvcxhlrV+ZOUJSN7/ottYl+8X4oSXzS9lBtDkmWAl96EGyGyLrNvk9zqdSH8Fw==", "dev": true, "requires": { - "@types/node": "*" + "@types/node": "9.4.7" } }, "@types/download": { @@ -114,9 +114,9 @@ "integrity": "sha512-gwRnrp1yFweJhPGBR01nfesxYcml8SayxHEwA6x+1T+Lqez5iMdCRJgt/I9HqpjMi5Mmtb/7MswY6FN4bMypNg==", "dev": true, "requires": { - "@types/decompress": "*", - "@types/got": "*", - "@types/node": "*" + "@types/decompress": "4.2.2", + "@types/got": "8.3.1", + "@types/node": "9.4.7" } }, "@types/event-stream": { @@ -125,7 +125,7 @@ "integrity": "sha512-LLiivgWKii4JeMzFy3trrxqkRrVSdue8WmbXyHuSJLwNrhIQU5MTrc65jhxEPwMyh5HR1xevSdD+k2nnSRKw9g==", "dev": true, "requires": { - "@types/node": "*" + "@types/node": "9.4.7" } }, "@types/events": { @@ -140,7 +140,7 @@ "integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==", "dev": true, "requires": { - "@types/node": "*" + "@types/node": "9.4.7" } }, "@types/fs-extra": { @@ -149,7 +149,7 @@ "integrity": "sha512-Q3FWsbdmkQd1ib11A4XNWQvRD//5KpPoGawA8aB2DR7pWKoW9XQv3+dGxD/Z1eVFze23Okdo27ZQytVFlweKvQ==", "dev": true, "requires": { - "@types/node": "*" + "@types/node": "9.4.7" } }, "@types/get-port": { @@ -164,9 +164,9 @@ "integrity": "sha512-wc+VveszMLyMWFvXLkloixT4n0harUIVZjnpzztaZ0nKLuul7Z32iMt2fUFGAaZ4y1XWjFRMtCI5ewvyh4aIeg==", "dev": true, "requires": { - "@types/events": "*", - "@types/minimatch": "*", - "@types/node": "*" + "@types/events": "1.2.0", + "@types/minimatch": "3.0.3", + "@types/node": "9.4.7" } }, "@types/got": { @@ -175,7 +175,7 @@ "integrity": "sha512-CGEPw67/Ub6gNMusk062tueurxN+HyjDCvYl4QVBKiSO+fqluXmRX/wSqST/4RtKth4mz8lDZiaZIpXr/uPROg==", "dev": true, "requires": { - "@types/node": "*" + "@types/node": "9.4.7" } }, "@types/iconv-lite": { @@ -184,7 +184,7 @@ "integrity": "sha1-qjuL2ivlErGuCgV7lC6GnDcKVWk=", "dev": true, "requires": { - "@types/node": "*" + "@types/node": "9.4.7" } }, "@types/istanbul": { @@ -205,7 +205,7 @@ "integrity": "sha1-k+I0N/zRenucqY0CqmAC6DWEL+g=", "dev": true, "requires": { - "@types/node": "*" + "@types/node": "9.4.7" } }, "@types/minimatch": { @@ -232,10 +232,10 @@ "integrity": "sha512-/KXM5oev+nNCLIgBjkwbk8VqxmzI56woD4VUxn95O+YeQ8hJzcSmIZ1IN3WexiqBb6srzDo2bdMbsXxgXNkz5Q==", "dev": true, "requires": { - "@types/caseless": "*", - "@types/form-data": "*", - "@types/node": "*", - "@types/tough-cookie": "*" + "@types/caseless": "0.12.1", + "@types/form-data": "2.2.1", + "@types/node": "9.4.7", + "@types/tough-cookie": "2.3.3" } }, "@types/semver": { @@ -280,7 +280,7 @@ "integrity": "sha512-5fRLCYhLtDb3hMWqQyH10qtF+Ud2JnNCXTCZ+9ktNdCcgslcuXkDTkFcJNk++MT29yDntDnlF1+jD+uVGumsbw==", "dev": true, "requires": { - "@types/node": "*" + "@types/node": "9.4.7" } }, "@types/winreg": { @@ -295,7 +295,7 @@ "integrity": "sha512-8aKUBSj3oGcnuiBmDLm3BIk09RYg01mz9HlQ2u4aS17oJ25DxjQrEUVGFSBVNOfM45pQW4OjcBPplq6r/exJdA==", "dev": true, "requires": { - "@types/node": "*" + "@types/node": "9.4.7" } }, "JSONStream": { @@ -304,8 +304,8 @@ "integrity": "sha512-3Sp6WZZ/lXl+nTDoGpGWHEpTnnC6X5fnkolYZR6nwIfzbxxvA8utPWe1gCt7i0m9uVGsSz2IS8K8mJ7HmlduMg==", "dev": true, "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" + "jsonparse": "1.2.0", + "through": "2.3.8" } }, "abbrev": { @@ -325,10 +325,10 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "co": "4.6.0", + "fast-deep-equal": "1.1.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" } }, "align-text": { @@ -337,9 +337,9 @@ "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", "dev": true, "requires": { - "kind-of": "^3.0.2", - "longest": "^1.0.1", - "repeat-string": "^1.5.2" + "kind-of": "3.2.2", + "longest": "1.0.1", + "repeat-string": "1.6.1" }, "dependencies": { "kind-of": { @@ -348,7 +348,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -365,7 +365,7 @@ "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", "dev": true, "requires": { - "ansi-wrap": "^0.1.0" + "ansi-wrap": "0.1.0" } }, "ansi-cyan": { @@ -419,8 +419,8 @@ "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", "dev": true, "requires": { - "micromatch": "^2.1.5", - "normalize-path": "^2.0.0" + "micromatch": "2.3.11", + "normalize-path": "2.1.1" }, "dependencies": { "arr-diff": { @@ -429,7 +429,7 @@ "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", "dev": true, "requires": { - "arr-flatten": "^1.0.1" + "arr-flatten": "1.1.0" } }, "array-unique": { @@ -444,9 +444,9 @@ "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", "dev": true, "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.2" } }, "expand-brackets": { @@ -455,7 +455,7 @@ "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", "dev": true, "requires": { - "is-posix-bracket": "^0.1.0" + "is-posix-bracket": "0.1.1" } }, "extglob": { @@ -464,7 +464,7 @@ "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "is-extglob": "1.0.0" } }, "is-extglob": { @@ -479,7 +479,7 @@ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "is-extglob": "1.0.0" } }, "kind-of": { @@ -488,7 +488,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } }, "micromatch": { @@ -497,19 +497,19 @@ "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", "dev": true, "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" + "arr-diff": "2.0.0", + "array-unique": "0.2.1", + "braces": "1.8.5", + "expand-brackets": "0.1.5", + "extglob": "0.3.2", + "filename-regex": "2.0.1", + "is-extglob": "1.0.0", + "is-glob": "2.0.1", + "kind-of": "3.2.2", + "normalize-path": "2.1.1", + "object.omit": "2.0.1", + "parse-glob": "3.0.4", + "regex-cache": "0.4.4" } } } @@ -520,7 +520,7 @@ "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", "dev": true, "requires": { - "buffer-equal": "^1.0.0" + "buffer-equal": "1.0.0" } }, "applicationinsights": { @@ -550,7 +550,7 @@ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { - "sprintf-js": "~1.0.2" + "sprintf-js": "1.0.3" } }, "argv": { @@ -607,7 +607,7 @@ "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", "dev": true, "requires": { - "array-uniq": "^1.0.1" + "array-uniq": "1.0.3" } }, "array-uniq": { @@ -689,15 +689,15 @@ "integrity": "sha512-gcbdUY0tLivJvjUZD9BAxWrRDcige4OLFHhN3kY0p9oZYAFNNNqwgO7rBXvV+zdoX9HajeMOEog9/S/wxabeGg==", "dev": true, "requires": { - "browserify-mime": "~1.2.9", - "extend": "~1.2.1", + "browserify-mime": "1.2.9", + "extend": "1.2.1", "json-edm-parser": "0.1.2", "md5.js": "1.3.4", - "readable-stream": "~2.0.0", - "request": "^2.86.0", - "underscore": "~1.8.3", - "uuid": "^3.0.0", - "validator": "~9.4.1", + "readable-stream": "2.0.6", + "request": "2.87.0", + "underscore": "1.8.3", + "uuid": "3.2.1", + "validator": "9.4.1", "xml2js": "0.2.8", "xmlbuilder": "0.4.3" }, @@ -714,26 +714,26 @@ "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", "dev": true, "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.6.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.1", - "forever-agent": "~0.6.1", - "form-data": "~2.3.1", - "har-validator": "~5.0.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.17", - "oauth-sign": "~0.8.2", - "performance-now": "^2.1.0", - "qs": "~6.5.1", - "safe-buffer": "^5.1.1", - "tough-cookie": "~2.3.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.1.0" + "aws-sign2": "0.7.0", + "aws4": "1.7.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.3.2", + "har-validator": "5.0.3", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.18", + "oauth-sign": "0.8.2", + "performance-now": "2.1.0", + "qs": "6.5.2", + "safe-buffer": "5.1.2", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.6.0", + "uuid": "3.2.1" }, "dependencies": { "extend": { @@ -756,7 +756,7 @@ "integrity": "sha1-m4FpCTFjH/CdGVdUn69U9PmAs8I=", "dev": true, "requires": { - "sax": "0.5.x" + "sax": "0.5.8" } }, "xmlbuilder": { @@ -773,9 +773,9 @@ "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", "dev": true, "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" } }, "balanced-match": { @@ -789,13 +789,13 @@ "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", "dev": true, "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" + "cache-base": "1.0.1", + "class-utils": "0.3.6", + "component-emitter": "1.2.1", + "define-property": "1.0.0", + "isobject": "3.0.1", + "mixin-deep": "1.3.1", + "pascalcase": "0.1.1" }, "dependencies": { "define-property": { @@ -804,7 +804,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "is-accessor-descriptor": { @@ -813,7 +813,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-data-descriptor": { @@ -822,7 +822,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-descriptor": { @@ -831,9 +831,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" } } } @@ -850,7 +850,7 @@ "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", "optional": true, "requires": { - "tweetnacl": "^0.14.3" + "tweetnacl": "0.14.5" } }, "beeper": { @@ -871,8 +871,8 @@ "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "dev": true, "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" + "readable-stream": "2.3.6", + "safe-buffer": "5.1.2" }, "dependencies": { "process-nextick-args": { @@ -887,13 +887,13 @@ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -902,7 +902,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.2" } } } @@ -913,7 +913,7 @@ "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", "dev": true, "requires": { - "inherits": "~2.0.0" + "inherits": "2.0.3" } }, "bluebird": { @@ -927,7 +927,7 @@ "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", "requires": { - "hoek": "4.x.x" + "hoek": "4.2.1" } }, "brace-expansion": { @@ -935,7 +935,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { - "balanced-match": "^1.0.0", + "balanced-match": "1.0.0", "concat-map": "0.0.1" } }, @@ -945,16 +945,16 @@ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.2", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" }, "dependencies": { "extend-shallow": { @@ -963,7 +963,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -987,8 +987,8 @@ "dev": true, "requires": { "base64-js": "0.0.8", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" + "ieee754": "1.1.11", + "isarray": "1.0.0" } }, "buffer-alloc": { @@ -997,8 +997,8 @@ "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", "dev": true, "requires": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" + "buffer-alloc-unsafe": "1.1.0", + "buffer-fill": "1.0.0" } }, "buffer-alloc-unsafe": { @@ -1043,15 +1043,15 @@ "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", "dev": true, "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" + "collection-visit": "1.0.0", + "component-emitter": "1.2.1", + "get-value": "2.0.6", + "has-value": "1.0.0", + "isobject": "3.0.1", + "set-value": "2.0.0", + "to-object-path": "0.3.0", + "union-value": "1.0.0", + "unset-value": "1.0.0" } }, "cacheable-request": { @@ -1095,8 +1095,8 @@ "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "dev": true, "requires": { - "camelcase": "^2.0.0", - "map-obj": "^1.0.0" + "camelcase": "2.1.1", + "map-obj": "1.0.1" } }, "caseless": { @@ -1110,10 +1110,10 @@ "integrity": "sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==", "dev": true, "requires": { - "get-proxy": "^2.0.0", - "isurl": "^1.0.0-alpha5", - "tunnel-agent": "^0.6.0", - "url-to-options": "^1.0.1" + "get-proxy": "2.1.0", + "isurl": "1.0.0", + "tunnel-agent": "0.6.0", + "url-to-options": "1.0.1" } }, "center-align": { @@ -1123,8 +1123,8 @@ "dev": true, "optional": true, "requires": { - "align-text": "^0.1.3", - "lazy-cache": "^1.0.3" + "align-text": "0.1.4", + "lazy-cache": "1.0.4" } }, "chai": { @@ -1133,12 +1133,12 @@ "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", "dev": true, "requires": { - "assertion-error": "^1.0.1", - "check-error": "^1.0.1", - "deep-eql": "^3.0.0", - "get-func-name": "^2.0.0", - "pathval": "^1.0.0", - "type-detect": "^4.0.0" + "assertion-error": "1.1.0", + "check-error": "1.0.2", + "deep-eql": "3.0.1", + "get-func-name": "2.0.0", + "pathval": "1.1.0", + "type-detect": "4.0.8" } }, "chai-arrays": { @@ -1153,7 +1153,7 @@ "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", "dev": true, "requires": { - "check-error": "^1.0.2" + "check-error": "1.0.2" } }, "chalk": { @@ -1162,11 +1162,11 @@ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" } }, "charenc": { @@ -1186,15 +1186,15 @@ "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", "dev": true, "requires": { - "anymatch": "^1.3.0", - "async-each": "^1.0.0", - "fsevents": "^1.0.0", - "glob-parent": "^2.0.0", - "inherits": "^2.0.1", - "is-binary-path": "^1.0.0", - "is-glob": "^2.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0" + "anymatch": "1.3.2", + "async-each": "1.0.1", + "fsevents": "1.2.4", + "glob-parent": "2.0.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "2.0.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.1.0" }, "dependencies": { "glob-parent": { @@ -1203,7 +1203,7 @@ "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", "dev": true, "requires": { - "is-glob": "^2.0.0" + "is-glob": "2.0.1" } }, "is-extglob": { @@ -1218,7 +1218,7 @@ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "is-extglob": "1.0.0" } } } @@ -1241,7 +1241,7 @@ "integrity": "sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA==", "dev": true, "requires": { - "chalk": "^1.1.3" + "chalk": "1.1.3" } }, "class-utils": { @@ -1250,10 +1250,10 @@ "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", "dev": true, "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" + "arr-union": "3.1.0", + "define-property": "0.2.5", + "isobject": "3.0.1", + "static-extend": "0.1.2" }, "dependencies": { "define-property": { @@ -1262,7 +1262,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } } } @@ -1274,8 +1274,8 @@ "dev": true, "optional": true, "requires": { - "center-align": "^0.1.1", - "right-align": "^0.1.1", + "center-align": "0.1.3", + "right-align": "0.1.3", "wordwrap": "0.0.2" }, "dependencies": { @@ -1306,7 +1306,7 @@ "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", "dev": true, "requires": { - "mimic-response": "^1.0.0" + "mimic-response": "1.0.0" } }, "clone-stats": { @@ -1321,9 +1321,9 @@ "integrity": "sha512-Bq6+4t+lbM8vhTs/Bef5c5AdEMtapp/iFb6+s4/Hh9MVTt8OLKH7ZOOZSCT+Ys7hsHvqv0GuMPJ1lnQJVHvxpg==", "dev": true, "requires": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" + "inherits": "2.0.3", + "process-nextick-args": "2.0.0", + "readable-stream": "2.3.6" }, "dependencies": { "process-nextick-args": { @@ -1338,13 +1338,13 @@ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -1353,7 +1353,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.2" } } } @@ -1369,7 +1369,7 @@ "integrity": "sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0=", "dev": true, "requires": { - "q": "^1.1.2" + "q": "1.5.1" } }, "codecov": { @@ -1379,7 +1379,7 @@ "dev": true, "requires": { "argv": "0.0.2", - "request": "^2.81.0", + "request": "2.85.0", "urlgrey": "0.4.4" } }, @@ -1389,8 +1389,8 @@ "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", "dev": true, "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" + "map-visit": "1.0.0", + "object-visit": "1.0.1" } }, "color-convert": { @@ -1399,7 +1399,7 @@ "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", "dev": true, "requires": { - "color-name": "^1.1.1" + "color-name": "1.1.3" } }, "color-name": { @@ -1425,7 +1425,7 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", "requires": { - "delayed-stream": "~1.0.0" + "delayed-stream": "1.0.0" } }, "commander": { @@ -1457,8 +1457,8 @@ "integrity": "sha1-q6CXR9++TD5w52am5BWG4YWfxvI=", "dev": true, "requires": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" + "ini": "1.3.5", + "proto-list": "1.2.4" } }, "content-disposition": { @@ -1490,11 +1490,11 @@ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "nice-try": "1.0.4", + "path-key": "2.0.1", + "semver": "5.5.0", + "shebang-command": "1.2.0", + "which": "1.3.1" } }, "crypt": { @@ -1507,7 +1507,7 @@ "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", "requires": { - "boom": "5.x.x" + "boom": "5.2.0" }, "dependencies": { "boom": { @@ -1515,7 +1515,7 @@ "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", "requires": { - "hoek": "4.x.x" + "hoek": "4.2.1" } } } @@ -1526,10 +1526,10 @@ "integrity": "sha512-0W171WccAjQGGTKLhw4m2nnl0zPHUlTO/I8td4XzJgIB8Hg3ZZx71qT4G4eX8OVsSiaAKiUMy73E3nsbPlg2DQ==", "dev": true, "requires": { - "inherits": "^2.0.1", - "source-map": "^0.1.38", - "source-map-resolve": "^0.5.1", - "urix": "^0.1.0" + "inherits": "2.0.3", + "source-map": "0.1.43", + "source-map-resolve": "0.5.2", + "urix": "0.1.0" }, "dependencies": { "source-map": { @@ -1538,7 +1538,7 @@ "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", "dev": true, "requires": { - "amdefine": ">=0.0.4" + "amdefine": "1.0.1" } } } @@ -1549,8 +1549,8 @@ "integrity": "sha512-XC6xLW/JqIGirnZuUWHXCHRaAjje2b3OIB0Vj5RIJo6mIi/AdJo30quQl5LxUl0gkXDIrTrFGbMlcZjyFplz1A==", "dev": true, "requires": { - "mdn-data": "^1.0.0", - "source-map": "^0.5.3" + "mdn-data": "1.1.4", + "source-map": "0.5.7" } }, "csso": { @@ -1568,7 +1568,7 @@ "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", "dev": true, "requires": { - "array-find-index": "^1.0.1" + "array-find-index": "1.0.2" } }, "d": { @@ -1577,7 +1577,7 @@ "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "dev": true, "requires": { - "es5-ext": "^0.10.9" + "es5-ext": "0.10.43" } }, "dashdash": { @@ -1585,7 +1585,7 @@ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "requires": { - "assert-plus": "^1.0.0" + "assert-plus": "1.0.0" } }, "dateformat": { @@ -1621,9 +1621,9 @@ "integrity": "sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==", "dev": true, "requires": { - "debug": "3.X", - "memoizee": "0.4.X", - "object-assign": "4.X" + "debug": "3.1.0", + "memoizee": "0.4.12", + "object-assign": "4.1.1" }, "dependencies": { "debug": { @@ -1643,7 +1643,7 @@ "integrity": "sha1-b232uF1+fEQQqTL/wmSJt46azRM=", "dev": true, "requires": { - "callsite": "^1.0.0" + "callsite": "1.0.0" } }, "decamelize": { @@ -1664,14 +1664,14 @@ "integrity": "sha1-eu3YVCflqS2s/lVnSnxQXpbQH50=", "dev": true, "requires": { - "decompress-tar": "^4.0.0", - "decompress-tarbz2": "^4.0.0", - "decompress-targz": "^4.0.0", - "decompress-unzip": "^4.0.1", - "graceful-fs": "^4.1.10", - "make-dir": "^1.0.0", - "pify": "^2.3.0", - "strip-dirs": "^2.0.0" + "decompress-tar": "4.1.1", + "decompress-tarbz2": "4.1.1", + "decompress-targz": "4.1.1", + "decompress-unzip": "4.0.1", + "graceful-fs": "4.1.11", + "make-dir": "1.3.0", + "pify": "2.3.0", + "strip-dirs": "2.1.0" }, "dependencies": { "pify": { @@ -1688,7 +1688,7 @@ "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", "dev": true, "requires": { - "mimic-response": "^1.0.0" + "mimic-response": "1.0.0" } }, "decompress-tar": { @@ -1697,9 +1697,9 @@ "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", "dev": true, "requires": { - "file-type": "^5.2.0", - "is-stream": "^1.1.0", - "tar-stream": "^1.5.2" + "file-type": "5.2.0", + "is-stream": "1.1.0", + "tar-stream": "1.6.1" }, "dependencies": { "file-type": { @@ -1716,11 +1716,11 @@ "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", "dev": true, "requires": { - "decompress-tar": "^4.1.0", - "file-type": "^6.1.0", - "is-stream": "^1.1.0", - "seek-bzip": "^1.0.5", - "unbzip2-stream": "^1.0.9" + "decompress-tar": "4.1.1", + "file-type": "6.2.0", + "is-stream": "1.1.0", + "seek-bzip": "1.0.5", + "unbzip2-stream": "1.2.5" }, "dependencies": { "file-type": { @@ -1737,9 +1737,9 @@ "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", "dev": true, "requires": { - "decompress-tar": "^4.1.1", - "file-type": "^5.2.0", - "is-stream": "^1.1.0" + "decompress-tar": "4.1.1", + "file-type": "5.2.0", + "is-stream": "1.1.0" }, "dependencies": { "file-type": { @@ -1756,10 +1756,10 @@ "integrity": "sha1-3qrM39FK6vhVePczroIQ+bSEj2k=", "dev": true, "requires": { - "file-type": "^3.8.0", - "get-stream": "^2.2.0", - "pify": "^2.3.0", - "yauzl": "^2.4.2" + "file-type": "3.9.0", + "get-stream": "2.3.1", + "pify": "2.3.0", + "yauzl": "2.9.1" }, "dependencies": { "file-type": { @@ -1774,8 +1774,8 @@ "integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=", "dev": true, "requires": { - "object-assign": "^4.0.1", - "pinkie-promise": "^2.0.0" + "object-assign": "4.1.1", + "pinkie-promise": "2.0.1" } }, "pify": { @@ -1792,7 +1792,7 @@ "integrity": "sha1-sJJ0O+hCfcYh6gBnzex+cN0Z83s=", "dev": true, "requires": { - "is-obj": "^1.0.0" + "is-obj": "1.0.1" } }, "deep-eql": { @@ -1801,7 +1801,7 @@ "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", "dev": true, "requires": { - "type-detect": "^4.0.0" + "type-detect": "4.0.8" } }, "deep-is": { @@ -1822,7 +1822,7 @@ "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", "dev": true, "requires": { - "clone": "^1.0.2" + "clone": "1.0.4" } }, "define-properties": { @@ -1831,8 +1831,8 @@ "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", "dev": true, "requires": { - "foreach": "^2.0.5", - "object-keys": "^1.0.8" + "foreach": "2.0.5", + "object-keys": "1.0.11" } }, "define-property": { @@ -1841,8 +1841,8 @@ "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", "dev": true, "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" + "is-descriptor": "1.0.2", + "isobject": "3.0.1" }, "dependencies": { "is-accessor-descriptor": { @@ -1851,7 +1851,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-data-descriptor": { @@ -1860,7 +1860,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-descriptor": { @@ -1869,9 +1869,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" } } } @@ -1882,12 +1882,12 @@ "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=", "dev": true, "requires": { - "globby": "^6.1.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "p-map": "^1.1.1", - "pify": "^3.0.0", - "rimraf": "^2.2.8" + "globby": "6.1.0", + "is-path-cwd": "1.0.0", + "is-path-in-cwd": "1.0.1", + "p-map": "1.2.0", + "pify": "3.0.0", + "rimraf": "2.6.2" } }, "delayed-stream": { @@ -1924,7 +1924,7 @@ "resolved": "https://registry.npmjs.org/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz", "integrity": "sha1-zJmvlhLCP7H/8TYSxy8sv6qNWhc=", "requires": { - "semver": "^5.3.0" + "semver": "5.5.0" } }, "diagnostic-channel-publishers": { @@ -1949,7 +1949,7 @@ "integrity": "sha1-fLhgNZujvpDgQLJrcpzkv6ZUxSM=", "dev": true, "requires": { - "esutils": "^1.1.6", + "esutils": "1.1.6", "isarray": "0.0.1" }, "dependencies": { @@ -1973,8 +1973,8 @@ "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", "dev": true, "requires": { - "domelementtype": "~1.1.1", - "entities": "~1.1.1" + "domelementtype": "1.1.3", + "entities": "1.1.1" }, "dependencies": { "domelementtype": { @@ -1997,7 +1997,7 @@ "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", "dev": true, "requires": { - "domelementtype": "1" + "domelementtype": "1.3.0" } }, "domutils": { @@ -2006,8 +2006,8 @@ "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", "dev": true, "requires": { - "dom-serializer": "0", - "domelementtype": "1" + "dom-serializer": "0.1.0", + "domelementtype": "1.3.0" } }, "dotenv": { @@ -2021,17 +2021,17 @@ "integrity": "sha512-0Fe/CAjKycx12IG9We9gYlLP03BEcWTpttg7P5mwfOiQTg584kpuHqP7F61RkUJM+mfEdEU9TJonm0PJp5rQLw==", "dev": true, "requires": { - "caw": "^2.0.1", - "content-disposition": "^0.5.2", - "decompress": "^4.2.0", - "ext-name": "^5.0.0", - "file-type": "^7.7.1", - "filenamify": "^2.0.0", - "get-stream": "^3.0.0", - "got": "^8.3.1", - "make-dir": "^1.2.0", - "p-event": "^1.3.0", - "pify": "^3.0.0" + "caw": "2.0.1", + "content-disposition": "0.5.2", + "decompress": "4.2.0", + "ext-name": "5.0.0", + "file-type": "7.7.1", + "filenamify": "2.0.0", + "get-stream": "3.0.0", + "got": "8.3.1", + "make-dir": "1.3.0", + "p-event": "1.3.0", + "pify": "3.0.0" } }, "duplexer": { @@ -2046,7 +2046,7 @@ "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", "dev": true, "requires": { - "readable-stream": "~1.1.9" + "readable-stream": "1.1.14" }, "dependencies": { "isarray": { @@ -2061,10 +2061,10 @@ "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", + "core-util-is": "1.0.2", + "inherits": "2.0.3", "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "string_decoder": "0.10.31" } } } @@ -2081,10 +2081,10 @@ "integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==", "dev": true, "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" + "end-of-stream": "1.4.1", + "inherits": "2.0.3", + "readable-stream": "2.0.6", + "stream-shift": "1.0.0" }, "dependencies": { "end-of-stream": { @@ -2093,7 +2093,7 @@ "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "dev": true, "requires": { - "once": "^1.4.0" + "once": "1.4.0" } } } @@ -2104,7 +2104,7 @@ "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", "optional": true, "requires": { - "jsbn": "~0.1.0" + "jsbn": "0.1.1" } }, "editorconfig": { @@ -2113,11 +2113,11 @@ "integrity": "sha512-WkjsUNVCu+ITKDj73QDvi0trvpdDWdkDyHybDGSXPfekLCqwmpD7CP7iPbvBgosNuLcI96XTDwNa75JyFl7tEQ==", "dev": true, "requires": { - "bluebird": "^3.0.5", - "commander": "^2.9.0", - "lru-cache": "^3.2.0", - "semver": "^5.1.0", - "sigmund": "^1.0.1" + "bluebird": "3.5.1", + "commander": "2.15.1", + "lru-cache": "3.2.0", + "semver": "5.5.0", + "sigmund": "1.0.1" }, "dependencies": { "lru-cache": { @@ -2126,7 +2126,7 @@ "integrity": "sha1-cXibO39Tmb7IVl3aOKow0qCX7+4=", "dev": true, "requires": { - "pseudomap": "^1.0.1" + "pseudomap": "1.0.2" } } } @@ -2137,7 +2137,7 @@ "integrity": "sha1-jhdyBsPICDfYVjLouTWd/osvbq8=", "dev": true, "requires": { - "once": "~1.3.0" + "once": "1.3.3" }, "dependencies": { "once": { @@ -2146,7 +2146,7 @@ "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", "dev": true, "requires": { - "wrappy": "1" + "wrappy": "1.0.2" } } } @@ -2163,7 +2163,7 @@ "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", "dev": true, "requires": { - "is-arrayish": "^0.2.1" + "is-arrayish": "0.2.1" } }, "es5-ext": { @@ -2172,9 +2172,9 @@ "integrity": "sha512-cZd1vezWuTM5qMlasKWqQFioFKwO352nVBzhOTMUf/pKQl5Gcq5EdJzqtSNXKnFQSCJDiQZjCYlYbnzFB657OA==", "dev": true, "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.1", - "next-tick": "1" + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1", + "next-tick": "1.0.0" } }, "es6-iterator": { @@ -2183,9 +2183,9 @@ "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", "dev": true, "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" + "d": "1.0.0", + "es5-ext": "0.10.43", + "es6-symbol": "3.1.1" } }, "es6-symbol": { @@ -2194,8 +2194,8 @@ "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", "dev": true, "requires": { - "d": "1", - "es5-ext": "~0.10.14" + "d": "1.0.0", + "es5-ext": "0.10.43" } }, "es6-weak-map": { @@ -2204,10 +2204,10 @@ "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", "dev": true, "requires": { - "d": "1", - "es5-ext": "^0.10.14", - "es6-iterator": "^2.0.1", - "es6-symbol": "^3.1.1" + "d": "1.0.0", + "es5-ext": "0.10.43", + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1" } }, "escape-string-regexp": { @@ -2222,11 +2222,11 @@ "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", "dev": true, "requires": { - "esprima": "^2.7.1", - "estraverse": "^1.9.1", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.2.0" + "esprima": "2.7.3", + "estraverse": "1.9.3", + "esutils": "2.0.2", + "optionator": "0.8.2", + "source-map": "0.2.0" }, "dependencies": { "source-map": { @@ -2236,7 +2236,7 @@ "dev": true, "optional": true, "requires": { - "amdefine": ">=0.0.4" + "amdefine": "1.0.1" } } } @@ -2265,8 +2265,8 @@ "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", "dev": true, "requires": { - "d": "1", - "es5-ext": "~0.10.14" + "d": "1.0.0", + "es5-ext": "0.10.43" } }, "event-stream": { @@ -2275,13 +2275,13 @@ "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", "dev": true, "requires": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", + "duplexer": "0.1.1", + "from": "0.1.7", + "map-stream": "0.1.0", "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" + "split": "0.3.3", + "stream-combiner": "0.0.4", + "through": "2.3.8" } }, "expand-brackets": { @@ -2290,13 +2290,13 @@ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "define-property": { @@ -2305,7 +2305,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "extend-shallow": { @@ -2314,7 +2314,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -2325,7 +2325,7 @@ "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", "dev": true, "requires": { - "fill-range": "^2.1.0" + "fill-range": "2.2.4" }, "dependencies": { "fill-range": { @@ -2334,11 +2334,11 @@ "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", "dev": true, "requires": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^3.0.0", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" + "is-number": "2.1.0", + "isobject": "2.1.0", + "randomatic": "3.0.0", + "repeat-element": "1.1.2", + "repeat-string": "1.6.1" } }, "is-number": { @@ -2347,7 +2347,7 @@ "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" } }, "isobject": { @@ -2365,7 +2365,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -2376,7 +2376,7 @@ "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", "dev": true, "requires": { - "homedir-polyfill": "^1.0.1" + "homedir-polyfill": "1.0.1" } }, "ext-list": { @@ -2385,7 +2385,7 @@ "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", "dev": true, "requires": { - "mime-db": "^1.28.0" + "mime-db": "1.33.0" } }, "ext-name": { @@ -2394,8 +2394,8 @@ "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", "dev": true, "requires": { - "ext-list": "^2.0.0", - "sort-keys-length": "^1.0.0" + "ext-list": "2.2.2", + "sort-keys-length": "1.0.1" } }, "extend": { @@ -2409,8 +2409,8 @@ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "dev": true, "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" + "assign-symbols": "1.0.0", + "is-extendable": "1.0.1" }, "dependencies": { "is-extendable": { @@ -2419,7 +2419,7 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "is-plain-object": "^2.0.4" + "is-plain-object": "2.0.4" } } } @@ -2430,14 +2430,14 @@ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "define-property": { @@ -2446,7 +2446,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "extend-shallow": { @@ -2455,7 +2455,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } }, "is-accessor-descriptor": { @@ -2464,7 +2464,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-data-descriptor": { @@ -2473,7 +2473,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-descriptor": { @@ -2482,9 +2482,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" } } } @@ -2500,9 +2500,9 @@ "integrity": "sha1-9BEl49hPLn2JpD0G2VjI94vha+E=", "dev": true, "requires": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "time-stamp": "^1.0.0" + "ansi-gray": "0.1.1", + "color-support": "1.1.3", + "time-stamp": "1.1.0" } }, "fast-deep-equal": { @@ -2527,7 +2527,7 @@ "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", "dev": true, "requires": { - "pend": "~1.2.0" + "pend": "1.2.0" } }, "file-type": { @@ -2554,9 +2554,9 @@ "integrity": "sha1-vRYiYsC26Uv7zc8Zo7uzdk94VpU=", "dev": true, "requires": { - "filename-reserved-regex": "^2.0.0", - "strip-outer": "^1.0.0", - "trim-repeated": "^1.0.0" + "filename-reserved-regex": "2.0.0", + "strip-outer": "1.0.1", + "trim-repeated": "1.0.0" } }, "fill-range": { @@ -2565,10 +2565,10 @@ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" }, "dependencies": { "extend-shallow": { @@ -2577,7 +2577,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -2594,8 +2594,8 @@ "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "dev": true, "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" } }, "findup-sync": { @@ -2604,10 +2604,10 @@ "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", "dev": true, "requires": { - "detect-file": "^1.0.0", - "is-glob": "^3.1.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" + "detect-file": "1.0.0", + "is-glob": "3.1.0", + "micromatch": "3.1.10", + "resolve-dir": "1.0.1" } }, "fined": { @@ -2616,11 +2616,11 @@ "integrity": "sha1-s33IRLdqL15wgeiE98CuNE8VNHY=", "dev": true, "requires": { - "expand-tilde": "^2.0.2", - "is-plain-object": "^2.0.3", - "object.defaults": "^1.1.0", - "object.pick": "^1.2.0", - "parse-filepath": "^1.0.1" + "expand-tilde": "2.0.2", + "is-plain-object": "2.0.4", + "object.defaults": "1.1.0", + "object.pick": "1.3.0", + "parse-filepath": "1.0.2" } }, "first-chunk-stream": { @@ -2641,7 +2641,7 @@ "integrity": "sha512-ji/WMv2jdsE+LaznpkIF9Haax0sdpTBozrz/Dtg4qSRMfbs8oVg4ypJunIRYPiMLvH/ed6OflXbnbTIKJhtgeg==", "dev": true, "requires": { - "is-buffer": "~1.1.5" + "is-buffer": "1.1.6" } }, "flush-write-stream": { @@ -2650,8 +2650,8 @@ "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", "dev": true, "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.4" + "inherits": "2.0.3", + "readable-stream": "2.0.6" } }, "for-in": { @@ -2666,7 +2666,7 @@ "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", "dev": true, "requires": { - "for-in": "^1.0.1" + "for-in": "1.0.2" } }, "foreach": { @@ -2685,9 +2685,9 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", "requires": { - "asynckit": "^0.4.0", + "asynckit": "0.4.0", "combined-stream": "1.0.6", - "mime-types": "^2.1.12" + "mime-types": "2.1.18" } }, "fragment-cache": { @@ -2696,7 +2696,7 @@ "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", "dev": true, "requires": { - "map-cache": "^0.2.2" + "map-cache": "0.2.2" } }, "from": { @@ -2711,8 +2711,8 @@ "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", "dev": true, "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" + "inherits": "2.0.3", + "readable-stream": "2.0.6" } }, "fs-constants": { @@ -2726,9 +2726,9 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "graceful-fs": "4.1.11", + "jsonfile": "4.0.0", + "universalify": "0.1.1" } }, "fs-mkdirp-stream": { @@ -2737,8 +2737,8 @@ "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", "dev": true, "requires": { - "graceful-fs": "^4.1.11", - "through2": "^2.0.3" + "graceful-fs": "4.1.11", + "through2": "2.0.3" } }, "fs-walk": { @@ -2747,7 +2747,7 @@ "integrity": "sha1-9/yRw64e6tB8mYvF0N1B8tvr0zU=", "dev": true, "requires": { - "async": "*" + "async": "1.5.2" } }, "fs.realpath": { @@ -2762,8 +2762,8 @@ "dev": true, "optional": true, "requires": { - "nan": "^2.9.2", - "node-pre-gyp": "^0.10.0" + "nan": "2.10.0", + "node-pre-gyp": "0.10.0" }, "dependencies": { "abbrev": { @@ -2789,8 +2789,8 @@ "dev": true, "optional": true, "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" + "delegates": "1.0.0", + "readable-stream": "2.3.6" } }, "balanced-match": { @@ -2803,7 +2803,7 @@ "bundled": true, "dev": true, "requires": { - "balanced-match": "^1.0.0", + "balanced-match": "1.0.0", "concat-map": "0.0.1" } }, @@ -2867,7 +2867,7 @@ "dev": true, "optional": true, "requires": { - "minipass": "^2.2.1" + "minipass": "2.2.4" } }, "fs.realpath": { @@ -2882,14 +2882,14 @@ "dev": true, "optional": true, "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" } }, "glob": { @@ -2898,12 +2898,12 @@ "dev": true, "optional": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "has-unicode": { @@ -2918,7 +2918,7 @@ "dev": true, "optional": true, "requires": { - "safer-buffer": "^2.1.0" + "safer-buffer": "2.1.2" } }, "ignore-walk": { @@ -2927,7 +2927,7 @@ "dev": true, "optional": true, "requires": { - "minimatch": "^3.0.4" + "minimatch": "3.0.4" } }, "inflight": { @@ -2936,8 +2936,8 @@ "dev": true, "optional": true, "requires": { - "once": "^1.3.0", - "wrappy": "1" + "once": "1.4.0", + "wrappy": "1.0.2" } }, "inherits": { @@ -2956,7 +2956,7 @@ "bundled": true, "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "number-is-nan": "1.0.1" } }, "isarray": { @@ -2970,7 +2970,7 @@ "bundled": true, "dev": true, "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "1.1.11" } }, "minimist": { @@ -2983,8 +2983,8 @@ "bundled": true, "dev": true, "requires": { - "safe-buffer": "^5.1.1", - "yallist": "^3.0.0" + "safe-buffer": "5.1.1", + "yallist": "3.0.2" } }, "minizlib": { @@ -2993,7 +2993,7 @@ "dev": true, "optional": true, "requires": { - "minipass": "^2.2.1" + "minipass": "2.2.4" } }, "mkdirp": { @@ -3016,9 +3016,9 @@ "dev": true, "optional": true, "requires": { - "debug": "^2.1.2", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" + "debug": "2.6.9", + "iconv-lite": "0.4.21", + "sax": "1.2.4" } }, "node-pre-gyp": { @@ -3027,16 +3027,16 @@ "dev": true, "optional": true, "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.0", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.1.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" + "detect-libc": "1.0.3", + "mkdirp": "0.5.1", + "needle": "2.2.0", + "nopt": "4.0.1", + "npm-packlist": "1.1.10", + "npmlog": "4.1.2", + "rc": "1.2.7", + "rimraf": "2.6.2", + "semver": "5.5.0", + "tar": "4.4.1" } }, "nopt": { @@ -3045,8 +3045,8 @@ "dev": true, "optional": true, "requires": { - "abbrev": "1", - "osenv": "^0.1.4" + "abbrev": "1.1.1", + "osenv": "0.1.5" } }, "npm-bundled": { @@ -3061,8 +3061,8 @@ "dev": true, "optional": true, "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" + "ignore-walk": "3.0.1", + "npm-bundled": "1.0.3" } }, "npmlog": { @@ -3071,10 +3071,10 @@ "dev": true, "optional": true, "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" } }, "number-is-nan": { @@ -3093,7 +3093,7 @@ "bundled": true, "dev": true, "requires": { - "wrappy": "1" + "wrappy": "1.0.2" } }, "os-homedir": { @@ -3114,8 +3114,8 @@ "dev": true, "optional": true, "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" } }, "path-is-absolute": { @@ -3136,10 +3136,10 @@ "dev": true, "optional": true, "requires": { - "deep-extend": "^0.5.1", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" + "deep-extend": "0.5.1", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" }, "dependencies": { "minimist": { @@ -3156,13 +3156,13 @@ "dev": true, "optional": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, "rimraf": { @@ -3171,7 +3171,7 @@ "dev": true, "optional": true, "requires": { - "glob": "^7.0.5" + "glob": "7.1.2" } }, "safe-buffer": { @@ -3214,9 +3214,9 @@ "bundled": true, "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" } }, "string_decoder": { @@ -3225,7 +3225,7 @@ "dev": true, "optional": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } }, "strip-ansi": { @@ -3233,7 +3233,7 @@ "bundled": true, "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "strip-json-comments": { @@ -3248,13 +3248,13 @@ "dev": true, "optional": true, "requires": { - "chownr": "^1.0.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.2.4", - "minizlib": "^1.1.0", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.1", - "yallist": "^3.0.2" + "chownr": "1.0.1", + "fs-minipass": "1.2.5", + "minipass": "2.2.4", + "minizlib": "1.1.0", + "mkdirp": "0.5.1", + "safe-buffer": "5.1.1", + "yallist": "3.0.2" } }, "util-deprecate": { @@ -3269,7 +3269,7 @@ "dev": true, "optional": true, "requires": { - "string-width": "^1.0.2" + "string-width": "1.0.2" } }, "wrappy": { @@ -3290,10 +3290,10 @@ "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "inherits": "~2.0.0", - "mkdirp": ">=0.5 0", - "rimraf": "2" + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.2" } }, "function-bind": { @@ -3313,7 +3313,7 @@ "integrity": "sha1-QLcJU30k0dRXZ9takIaJ3+aaxE8=", "dev": true, "requires": { - "globule": "~0.1.0" + "globule": "0.1.0" } }, "get-func-name": { @@ -3333,7 +3333,7 @@ "integrity": "sha512-zmZIaQTWnNQb4R4fJUEp/FC51eZsc6EkErspy3xtIYStaq8EB/hDIWipxsal+E8rz0qD7f2sL/NA9Xee4RInJw==", "dev": true, "requires": { - "npm-conf": "^1.1.0" + "npm-conf": "1.1.3" } }, "get-stdin": { @@ -3359,7 +3359,7 @@ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "requires": { - "assert-plus": "^1.0.0" + "assert-plus": "1.0.0" } }, "glob": { @@ -3367,12 +3367,12 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "glob-base": { @@ -3381,8 +3381,8 @@ "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", "dev": true, "requires": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" + "glob-parent": "2.0.0", + "is-glob": "2.0.1" }, "dependencies": { "glob-parent": { @@ -3391,7 +3391,7 @@ "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", "dev": true, "requires": { - "is-glob": "^2.0.0" + "is-glob": "2.0.1" } }, "is-extglob": { @@ -3406,7 +3406,7 @@ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "is-extglob": "1.0.0" } } } @@ -3417,8 +3417,8 @@ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "dev": true, "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" + "is-glob": "3.1.0", + "path-dirname": "1.0.2" } }, "glob-stream": { @@ -3427,12 +3427,12 @@ "integrity": "sha1-kXCl8St5Awb9/lmPMT+PeVT9FDs=", "dev": true, "requires": { - "glob": "^4.3.1", - "glob2base": "^0.0.12", - "minimatch": "^2.0.1", - "ordered-read-streams": "^0.1.0", - "through2": "^0.6.1", - "unique-stream": "^1.0.0" + "glob": "4.5.3", + "glob2base": "0.0.12", + "minimatch": "2.0.10", + "ordered-read-streams": "0.1.0", + "through2": "0.6.5", + "unique-stream": "1.0.0" }, "dependencies": { "glob": { @@ -3441,10 +3441,10 @@ "integrity": "sha1-xstz0yJsHv7wTePFbQEvAzd+4V8=", "dev": true, "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^2.0.1", - "once": "^1.3.0" + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "2.0.10", + "once": "1.4.0" } }, "isarray": { @@ -3459,7 +3459,7 @@ "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", "dev": true, "requires": { - "brace-expansion": "^1.0.0" + "brace-expansion": "1.1.11" } }, "readable-stream": { @@ -3468,10 +3468,10 @@ "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", + "core-util-is": "1.0.2", + "inherits": "2.0.3", "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "string_decoder": "0.10.31" } }, "through2": { @@ -3480,8 +3480,8 @@ "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, "requires": { - "readable-stream": ">=1.0.33-1 <1.1.0-0", - "xtend": ">=4.0.0 <4.1.0-0" + "readable-stream": "1.0.34", + "xtend": "4.0.1" } } } @@ -3492,7 +3492,7 @@ "integrity": "sha1-uVtKjfdLOcgymLDAXJeLTZo7cQs=", "dev": true, "requires": { - "gaze": "^0.5.1" + "gaze": "0.5.2" } }, "glob2base": { @@ -3501,7 +3501,7 @@ "integrity": "sha1-nUGbPijxLoOjYhZKJ3BVkiycDVY=", "dev": true, "requires": { - "find-index": "^0.1.1" + "find-index": "0.1.1" } }, "global-modules": { @@ -3510,9 +3510,9 @@ "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", "dev": true, "requires": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" + "global-prefix": "1.0.2", + "is-windows": "1.0.2", + "resolve-dir": "1.0.1" } }, "global-prefix": { @@ -3521,11 +3521,11 @@ "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", "dev": true, "requires": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" + "expand-tilde": "2.0.2", + "homedir-polyfill": "1.0.1", + "ini": "1.3.5", + "is-windows": "1.0.2", + "which": "1.3.1" } }, "globby": { @@ -3534,11 +3534,11 @@ "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", "dev": true, "requires": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "array-union": "1.0.2", + "glob": "7.1.2", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" }, "dependencies": { "pify": { @@ -3555,9 +3555,9 @@ "integrity": "sha1-2cjt3h2nnRJaFRt5UzuXhnY0auU=", "dev": true, "requires": { - "glob": "~3.1.21", - "lodash": "~1.0.1", - "minimatch": "~0.2.11" + "glob": "3.1.21", + "lodash": "1.0.2", + "minimatch": "0.2.14" }, "dependencies": { "glob": { @@ -3566,9 +3566,9 @@ "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", "dev": true, "requires": { - "graceful-fs": "~1.2.0", - "inherits": "1", - "minimatch": "~0.2.11" + "graceful-fs": "1.2.3", + "inherits": "1.0.2", + "minimatch": "0.2.14" } }, "graceful-fs": { @@ -3595,8 +3595,8 @@ "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", "dev": true, "requires": { - "lru-cache": "2", - "sigmund": "~1.0.0" + "lru-cache": "2.7.3", + "sigmund": "1.0.1" } } } @@ -3607,7 +3607,7 @@ "integrity": "sha512-ynYqXLoluBKf9XGR1gA59yEJisIL7YHEH4xr3ZziHB5/yl4qWfaK8Js9jGe6gBGCSCKVqiyO30WnRZADvemUNw==", "dev": true, "requires": { - "sparkles": "^1.0.0" + "sparkles": "1.0.1" } }, "got": { @@ -3616,23 +3616,23 @@ "integrity": "sha512-tiLX+bnYm5A56T5N/n9Xo89vMaO1mrS9qoDqj3u/anVooqGozvY/HbXzEpDfbNeKsHCBpK40gSbz8wGYSp3i1w==", "dev": true, "requires": { - "@sindresorhus/is": "^0.7.0", - "cacheable-request": "^2.1.1", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "into-stream": "^3.1.0", - "is-retry-allowed": "^1.1.0", - "isurl": "^1.0.0-alpha5", - "lowercase-keys": "^1.0.0", - "mimic-response": "^1.0.0", - "p-cancelable": "^0.4.0", - "p-timeout": "^2.0.1", - "pify": "^3.0.0", - "safe-buffer": "^5.1.1", - "timed-out": "^4.0.1", - "url-parse-lax": "^3.0.0", - "url-to-options": "^1.0.1" + "@sindresorhus/is": "0.7.0", + "cacheable-request": "2.1.4", + "decompress-response": "3.3.0", + "duplexer3": "0.1.4", + "get-stream": "3.0.0", + "into-stream": "3.1.0", + "is-retry-allowed": "1.1.0", + "isurl": "1.0.0", + "lowercase-keys": "1.0.1", + "mimic-response": "1.0.0", + "p-cancelable": "0.4.1", + "p-timeout": "2.0.1", + "pify": "3.0.0", + "safe-buffer": "5.1.2", + "timed-out": "4.0.1", + "url-parse-lax": "3.0.0", + "url-to-options": "1.0.1" } }, "graceful-fs": { @@ -3658,19 +3658,19 @@ "integrity": "sha1-VxzkWSjdQK9lFPxAEYZgFsE4RbQ=", "dev": true, "requires": { - "archy": "^1.0.0", - "chalk": "^1.0.0", - "deprecated": "^0.0.1", - "gulp-util": "^3.0.0", - "interpret": "^1.0.0", - "liftoff": "^2.1.0", - "minimist": "^1.1.0", - "orchestrator": "^0.3.0", - "pretty-hrtime": "^1.0.0", - "semver": "^4.1.0", - "tildify": "^1.0.0", - "v8flags": "^2.0.2", - "vinyl-fs": "^0.3.0" + "archy": "1.0.0", + "chalk": "1.1.3", + "deprecated": "0.0.1", + "gulp-util": "3.0.8", + "interpret": "1.1.0", + "liftoff": "2.5.0", + "minimist": "1.2.0", + "orchestrator": "0.3.8", + "pretty-hrtime": "1.0.3", + "semver": "4.3.6", + "tildify": "1.2.0", + "v8flags": "2.1.1", + "vinyl-fs": "0.3.14" }, "dependencies": { "semver": { @@ -3687,9 +3687,9 @@ "integrity": "sha1-AMOQuSigeZslGsz2MaoJ4BzGKZw=", "dev": true, "requires": { - "deep-assign": "^1.0.0", - "stat-mode": "^0.2.0", - "through2": "^2.0.0" + "deep-assign": "1.0.0", + "stat-mode": "0.2.2", + "through2": "2.0.3" } }, "gulp-debounced-watch": { @@ -3698,9 +3698,9 @@ "integrity": "sha1-WkfU4kzkY2XOguysMqKjA+QysSo=", "dev": true, "requires": { - "debounce-hashed": "^0.1.1", - "gulp-watch": "^4.3.4", - "object-assign": "^3.0.0" + "debounce-hashed": "0.1.2", + "gulp-watch": "4.3.11", + "object-assign": "3.0.0" }, "dependencies": { "gulp-watch": { @@ -3709,16 +3709,16 @@ "integrity": "sha1-Fi/FY96fx3DpH5p845VVE6mhGMA=", "dev": true, "requires": { - "anymatch": "^1.3.0", - "chokidar": "^1.6.1", - "glob-parent": "^3.0.1", - "gulp-util": "^3.0.7", - "object-assign": "^4.1.0", - "path-is-absolute": "^1.0.1", - "readable-stream": "^2.2.2", - "slash": "^1.0.0", - "vinyl": "^1.2.0", - "vinyl-file": "^2.0.0" + "anymatch": "1.3.2", + "chokidar": "1.7.0", + "glob-parent": "3.1.0", + "gulp-util": "3.0.8", + "object-assign": "4.1.1", + "path-is-absolute": "1.0.1", + "readable-stream": "2.3.6", + "slash": "1.0.0", + "vinyl": "1.2.0", + "vinyl-file": "2.0.0" }, "dependencies": { "object-assign": { @@ -3747,13 +3747,13 @@ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -3762,7 +3762,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.2" } }, "vinyl": { @@ -3771,8 +3771,8 @@ "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", + "clone": "1.0.4", + "clone-stats": "0.0.1", "replace-ext": "0.0.1" } } @@ -3784,9 +3784,9 @@ "integrity": "sha1-oF4Rr/sHz33PQafeHLe2OsN4PnM=", "dev": true, "requires": { - "multimatch": "^2.0.0", - "plugin-error": "^0.1.2", - "streamfilter": "^1.0.5" + "multimatch": "2.1.0", + "plugin-error": "0.1.2", + "streamfilter": "1.0.7" } }, "gulp-gitmodified": { @@ -3795,11 +3795,11 @@ "integrity": "sha1-hfNnWRXB1RtmgH8o3g67WR5+nfQ=", "dev": true, "requires": { - "gulp-util": "~2.2.12", - "lodash.find": "^3.2.1", - "through2": "^2.0.0", - "vinyl": "^0.4.3", - "which": "~1.0.5" + "gulp-util": "2.2.20", + "lodash.find": "3.2.1", + "through2": "2.0.3", + "vinyl": "0.4.6", + "which": "1.0.9" }, "dependencies": { "ansi-regex": { @@ -3820,11 +3820,11 @@ "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", "dev": true, "requires": { - "ansi-styles": "^1.1.0", - "escape-string-regexp": "^1.0.0", - "has-ansi": "^0.1.0", - "strip-ansi": "^0.3.0", - "supports-color": "^0.2.0" + "ansi-styles": "1.1.0", + "escape-string-regexp": "1.0.5", + "has-ansi": "0.1.0", + "strip-ansi": "0.3.0", + "supports-color": "0.2.0" } }, "clone": { @@ -3839,8 +3839,8 @@ "integrity": "sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk=", "dev": true, "requires": { - "get-stdin": "^4.0.1", - "meow": "^3.3.0" + "get-stdin": "4.0.1", + "meow": "3.7.0" } }, "gulp-util": { @@ -3849,14 +3849,14 @@ "integrity": "sha1-1xRuVyiRC9jwR6awseVJvCLb1kw=", "dev": true, "requires": { - "chalk": "^0.5.0", - "dateformat": "^1.0.7-1.2.3", - "lodash._reinterpolate": "^2.4.1", - "lodash.template": "^2.4.1", - "minimist": "^0.2.0", - "multipipe": "^0.1.0", - "through2": "^0.5.0", - "vinyl": "^0.2.1" + "chalk": "0.5.1", + "dateformat": "1.0.12", + "lodash._reinterpolate": "2.4.1", + "lodash.template": "2.4.1", + "minimist": "0.2.0", + "multipipe": "0.1.2", + "through2": "0.5.1", + "vinyl": "0.2.3" }, "dependencies": { "through2": { @@ -3865,8 +3865,8 @@ "integrity": "sha1-390BLrnHAOIyP9M084rGIqs3Lac=", "dev": true, "requires": { - "readable-stream": "~1.0.17", - "xtend": "~3.0.0" + "readable-stream": "1.0.34", + "xtend": "3.0.0" } }, "vinyl": { @@ -3875,7 +3875,7 @@ "integrity": "sha1-vKk4IJWC7FpJrVOKAPofEl5RMlI=", "dev": true, "requires": { - "clone-stats": "~0.0.1" + "clone-stats": "0.0.1" } } } @@ -3886,7 +3886,7 @@ "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=", "dev": true, "requires": { - "ansi-regex": "^0.2.0" + "ansi-regex": "0.2.1" } }, "isarray": { @@ -3907,9 +3907,9 @@ "integrity": "sha1-LOEsXghNsKV92l5dHu659dF1o7Q=", "dev": true, "requires": { - "lodash._escapehtmlchar": "~2.4.1", - "lodash._reunescapedhtml": "~2.4.1", - "lodash.keys": "~2.4.1" + "lodash._escapehtmlchar": "2.4.1", + "lodash._reunescapedhtml": "2.4.1", + "lodash.keys": "2.4.1" } }, "lodash.keys": { @@ -3918,9 +3918,9 @@ "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", "dev": true, "requires": { - "lodash._isnative": "~2.4.1", - "lodash._shimkeys": "~2.4.1", - "lodash.isobject": "~2.4.1" + "lodash._isnative": "2.4.1", + "lodash._shimkeys": "2.4.1", + "lodash.isobject": "2.4.1" } }, "lodash.template": { @@ -3929,13 +3929,13 @@ "integrity": "sha1-nmEQB+32KRKal0qzxIuBez4c8g0=", "dev": true, "requires": { - "lodash._escapestringchar": "~2.4.1", - "lodash._reinterpolate": "~2.4.1", - "lodash.defaults": "~2.4.1", - "lodash.escape": "~2.4.1", - "lodash.keys": "~2.4.1", - "lodash.templatesettings": "~2.4.1", - "lodash.values": "~2.4.1" + "lodash._escapestringchar": "2.4.1", + "lodash._reinterpolate": "2.4.1", + "lodash.defaults": "2.4.1", + "lodash.escape": "2.4.1", + "lodash.keys": "2.4.1", + "lodash.templatesettings": "2.4.1", + "lodash.values": "2.4.1" } }, "lodash.templatesettings": { @@ -3944,8 +3944,8 @@ "integrity": "sha1-6nbHXRHrhtTb6JqDiTu4YZKaxpk=", "dev": true, "requires": { - "lodash._reinterpolate": "~2.4.1", - "lodash.escape": "~2.4.1" + "lodash._reinterpolate": "2.4.1", + "lodash.escape": "2.4.1" } }, "minimist": { @@ -3960,10 +3960,10 @@ "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", + "core-util-is": "1.0.2", + "inherits": "2.0.3", "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "string_decoder": "0.10.31" } }, "strip-ansi": { @@ -3972,7 +3972,7 @@ "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", "dev": true, "requires": { - "ansi-regex": "^0.2.1" + "ansi-regex": "0.2.1" } }, "supports-color": { @@ -3987,8 +3987,8 @@ "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", "dev": true, "requires": { - "clone": "^0.2.0", - "clone-stats": "^0.0.1" + "clone": "0.2.0", + "clone-stats": "0.0.1" } }, "which": { @@ -4011,8 +4011,8 @@ "integrity": "sha1-FbdBFF6Dqcb1CIYkG1fMWHHxUak=", "dev": true, "requires": { - "through2": "~0.6.5", - "vinyl": "~0.4.6" + "through2": "0.6.5", + "vinyl": "0.4.6" }, "dependencies": { "clone": { @@ -4033,10 +4033,10 @@ "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", + "core-util-is": "1.0.2", + "inherits": "2.0.3", "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "string_decoder": "0.10.31" } }, "through2": { @@ -4045,8 +4045,8 @@ "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, "requires": { - "readable-stream": ">=1.0.33-1 <1.1.0-0", - "xtend": ">=4.0.0 <4.1.0-0" + "readable-stream": "1.0.34", + "xtend": "4.0.1" } }, "vinyl": { @@ -4055,8 +4055,8 @@ "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", "dev": true, "requires": { - "clone": "^0.2.0", - "clone-stats": "^0.0.1" + "clone": "0.2.0", + "clone-stats": "0.0.1" } } } @@ -4067,9 +4067,9 @@ "integrity": "sha512-Ky5SKDgM517QZ6Jw9rKhYz+ynX9T4sr72iUW2fbOhqzN74D37DzWZDZnbKLSwdlTbbqt7JFq/JmUmT12qroW+A==", "dev": true, "requires": { - "inline-source": "~5.2.6", - "plugin-error": "~1.0.1", - "through2": "~2.0.0" + "inline-source": "5.2.7", + "plugin-error": "1.0.1", + "through2": "2.0.3" }, "dependencies": { "plugin-error": { @@ -4078,10 +4078,10 @@ "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", "dev": true, "requires": { - "ansi-colors": "^1.0.1", - "arr-diff": "^4.0.0", - "arr-union": "^3.1.0", - "extend-shallow": "^3.0.2" + "ansi-colors": "1.1.0", + "arr-diff": "4.0.0", + "arr-union": "3.1.0", + "extend-shallow": "3.0.2" } } } @@ -4092,11 +4092,11 @@ "integrity": "sha512-20nYwO5Bec5X6DfXmBmHEtDAyluTkMguhuvCzqwrHDv/NzwOn3qS4ofAMw9L2gnWAmzxKzHAkFO19LNDWyTwlg==", "dev": true, "requires": { - "deepmerge": "^2.1.0", - "detect-indent": "^5.0.0", - "js-beautify": "^1.7.5", - "plugin-error": "^1.0.1", - "through2": "^2.0.3" + "deepmerge": "2.1.1", + "detect-indent": "5.0.0", + "js-beautify": "1.7.5", + "plugin-error": "1.0.1", + "through2": "2.0.3" }, "dependencies": { "plugin-error": { @@ -4105,10 +4105,10 @@ "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", "dev": true, "requires": { - "ansi-colors": "^1.0.1", - "arr-diff": "^4.0.0", - "arr-union": "^3.1.0", - "extend-shallow": "^3.0.2" + "ansi-colors": "1.1.0", + "arr-diff": "4.0.0", + "arr-union": "3.1.0", + "extend-shallow": "3.0.2" } } } @@ -4119,11 +4119,11 @@ "integrity": "sha512-/9vtSk9eI9DEWCqzGieglPqmx0WUQ9pwPHyHFpKmfxqdgqGJC2l0vFMdYs54hLdDsMDEZFLDL2J4ikjc4hQ5HQ==", "dev": true, "requires": { - "event-stream": "^3.3.4", - "node.extend": "^1.1.2", - "request": "^2.79.0", - "through2": "^2.0.3", - "vinyl": "^2.0.1" + "event-stream": "3.3.4", + "node.extend": "1.1.6", + "request": "2.85.0", + "through2": "2.0.3", + "vinyl": "2.1.0" }, "dependencies": { "clone": { @@ -4150,12 +4150,12 @@ "integrity": "sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=", "dev": true, "requires": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" + "clone": "2.1.1", + "clone-buffer": "1.0.0", + "clone-stats": "1.0.0", + "cloneable-readable": "1.1.2", + "remove-trailing-separator": "1.1.0", + "replace-ext": "1.0.0" } } } @@ -4166,17 +4166,17 @@ "integrity": "sha1-y7IAhFCxvM5s0jv5gze+dRv24wo=", "dev": true, "requires": { - "@gulp-sourcemaps/identity-map": "1.X", - "@gulp-sourcemaps/map-sources": "1.X", - "acorn": "5.X", - "convert-source-map": "1.X", - "css": "2.X", - "debug-fabulous": "1.X", - "detect-newline": "2.X", - "graceful-fs": "4.X", - "source-map": "~0.6.0", - "strip-bom-string": "1.X", - "through2": "2.X" + "@gulp-sourcemaps/identity-map": "1.0.1", + "@gulp-sourcemaps/map-sources": "1.0.0", + "acorn": "5.5.3", + "convert-source-map": "1.5.1", + "css": "2.2.3", + "debug-fabulous": "1.1.0", + "detect-newline": "2.1.0", + "graceful-fs": "4.1.11", + "source-map": "0.6.1", + "strip-bom-string": "1.0.0", + "through2": "2.0.3" }, "dependencies": { "source-map": { @@ -4193,10 +4193,10 @@ "integrity": "sha1-wWUyBzLRks5W/ZQnH/oSMjS/KuA=", "dev": true, "requires": { - "event-stream": "^3.3.1", - "mkdirp": "^0.5.1", - "queue": "^3.1.0", - "vinyl-fs": "^2.4.3" + "event-stream": "3.3.4", + "mkdirp": "0.5.1", + "queue": "3.1.0", + "vinyl-fs": "2.4.4" }, "dependencies": { "arr-diff": { @@ -4205,7 +4205,7 @@ "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", "dev": true, "requires": { - "arr-flatten": "^1.0.1" + "arr-flatten": "1.1.0" } }, "array-unique": { @@ -4220,9 +4220,9 @@ "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", "dev": true, "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.2" } }, "expand-brackets": { @@ -4231,7 +4231,7 @@ "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", "dev": true, "requires": { - "is-posix-bracket": "^0.1.0" + "is-posix-bracket": "0.1.1" } }, "extend-shallow": { @@ -4240,7 +4240,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } }, "extglob": { @@ -4249,7 +4249,7 @@ "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "is-extglob": "1.0.0" } }, "glob": { @@ -4258,11 +4258,11 @@ "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", "dev": true, "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "glob-stream": { @@ -4271,14 +4271,14 @@ "integrity": "sha1-pVZlqajM3EGRWofHAeMtTgFvrSI=", "dev": true, "requires": { - "extend": "^3.0.0", - "glob": "^5.0.3", - "glob-parent": "^3.0.0", - "micromatch": "^2.3.7", - "ordered-read-streams": "^0.3.0", - "through2": "^0.6.0", - "to-absolute-glob": "^0.1.1", - "unique-stream": "^2.0.2" + "extend": "3.0.1", + "glob": "5.0.15", + "glob-parent": "3.1.0", + "micromatch": "2.3.11", + "ordered-read-streams": "0.3.0", + "through2": "0.6.5", + "to-absolute-glob": "0.1.1", + "unique-stream": "2.2.1" }, "dependencies": { "readable-stream": { @@ -4287,10 +4287,10 @@ "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", + "core-util-is": "1.0.2", + "inherits": "2.0.3", "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "string_decoder": "0.10.31" } }, "through2": { @@ -4299,8 +4299,8 @@ "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, "requires": { - "readable-stream": ">=1.0.33-1 <1.1.0-0", - "xtend": ">=4.0.0 <4.1.0-0" + "readable-stream": "1.0.34", + "xtend": "4.0.1" } } } @@ -4311,11 +4311,11 @@ "integrity": "sha1-uG/zSdgBzrVuHZ59x7vLS33uYAw=", "dev": true, "requires": { - "convert-source-map": "^1.1.1", - "graceful-fs": "^4.1.2", - "strip-bom": "^2.0.0", - "through2": "^2.0.0", - "vinyl": "^1.0.0" + "convert-source-map": "1.5.1", + "graceful-fs": "4.1.11", + "strip-bom": "2.0.0", + "through2": "2.0.3", + "vinyl": "1.2.0" } }, "is-extglob": { @@ -4330,7 +4330,7 @@ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "is-extglob": "1.0.0" } }, "is-valid-glob": { @@ -4351,7 +4351,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } }, "micromatch": { @@ -4360,19 +4360,19 @@ "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", "dev": true, "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" + "arr-diff": "2.0.0", + "array-unique": "0.2.1", + "braces": "1.8.5", + "expand-brackets": "0.1.5", + "extglob": "0.3.2", + "filename-regex": "2.0.1", + "is-extglob": "1.0.0", + "is-glob": "2.0.1", + "kind-of": "3.2.2", + "normalize-path": "2.1.1", + "object.omit": "2.0.1", + "parse-glob": "3.0.4", + "regex-cache": "0.4.4" } }, "ordered-read-streams": { @@ -4381,8 +4381,8 @@ "integrity": "sha1-cTfmmzKYuzQiR6G77jiByA4v14s=", "dev": true, "requires": { - "is-stream": "^1.0.1", - "readable-stream": "^2.0.1" + "is-stream": "1.1.0", + "readable-stream": "2.0.6" } }, "strip-bom": { @@ -4391,7 +4391,7 @@ "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, "requires": { - "is-utf8": "^0.2.0" + "is-utf8": "0.2.1" } }, "strip-bom-stream": { @@ -4400,8 +4400,8 @@ "integrity": "sha1-5xRDmFd9Uaa+0PoZlPoF9D/ZiO4=", "dev": true, "requires": { - "first-chunk-stream": "^1.0.0", - "strip-bom": "^2.0.0" + "first-chunk-stream": "1.0.0", + "strip-bom": "2.0.0" } }, "to-absolute-glob": { @@ -4410,7 +4410,7 @@ "integrity": "sha1-HN+kcqnvUMI57maZm2YsoOs5k38=", "dev": true, "requires": { - "extend-shallow": "^2.0.1" + "extend-shallow": "2.0.1" } }, "unique-stream": { @@ -4419,8 +4419,8 @@ "integrity": "sha1-WqADz76Uxf+GbE59ZouxxNuts2k=", "dev": true, "requires": { - "json-stable-stringify": "^1.0.0", - "through2-filter": "^2.0.0" + "json-stable-stringify": "1.0.1", + "through2-filter": "2.0.0" } }, "vinyl": { @@ -4429,8 +4429,8 @@ "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", + "clone": "1.0.4", + "clone-stats": "0.0.1", "replace-ext": "0.0.1" } }, @@ -4440,23 +4440,23 @@ "integrity": "sha1-vm/zJwy1Xf19MGNkDegfJddTIjk=", "dev": true, "requires": { - "duplexify": "^3.2.0", - "glob-stream": "^5.3.2", - "graceful-fs": "^4.0.0", + "duplexify": "3.6.0", + "glob-stream": "5.3.5", + "graceful-fs": "4.1.11", "gulp-sourcemaps": "1.6.0", - "is-valid-glob": "^0.3.0", - "lazystream": "^1.0.0", - "lodash.isequal": "^4.0.0", - "merge-stream": "^1.0.0", - "mkdirp": "^0.5.0", - "object-assign": "^4.0.0", - "readable-stream": "^2.0.4", - "strip-bom": "^2.0.0", - "strip-bom-stream": "^1.0.0", - "through2": "^2.0.0", - "through2-filter": "^2.0.0", - "vali-date": "^1.0.0", - "vinyl": "^1.0.0" + "is-valid-glob": "0.3.0", + "lazystream": "1.0.0", + "lodash.isequal": "4.5.0", + "merge-stream": "1.0.1", + "mkdirp": "0.5.1", + "object-assign": "4.1.1", + "readable-stream": "2.0.6", + "strip-bom": "2.0.0", + "strip-bom-stream": "1.0.0", + "through2": "2.0.3", + "through2-filter": "2.0.0", + "vali-date": "1.0.0", + "vinyl": "1.2.0" } } } @@ -4467,12 +4467,12 @@ "integrity": "sha512-Hhbn5Aa2l3T+tnn0KqsG6RRJmcYEsr3byTL2nBpNBeAK8pqug9Od4AwddU4JEI+hRw7mzZyjRbB8DDWR6paGVA==", "dev": true, "requires": { - "ansi-colors": "^1.0.1", - "plugin-error": "^0.1.2", - "source-map": "^0.6.1", - "through2": "^2.0.3", - "vinyl": "^2.1.0", - "vinyl-fs": "^3.0.0" + "ansi-colors": "1.1.0", + "plugin-error": "0.1.2", + "source-map": "0.6.1", + "through2": "2.0.3", + "vinyl": "2.1.0", + "vinyl-fs": "3.0.3" }, "dependencies": { "clone": { @@ -4493,16 +4493,16 @@ "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", "dev": true, "requires": { - "extend": "^3.0.0", - "glob": "^7.1.1", - "glob-parent": "^3.1.0", - "is-negated-glob": "^1.0.0", - "ordered-read-streams": "^1.0.0", - "pumpify": "^1.3.5", - "readable-stream": "^2.1.5", - "remove-trailing-separator": "^1.0.1", - "to-absolute-glob": "^2.0.0", - "unique-stream": "^2.0.2" + "extend": "3.0.1", + "glob": "7.1.2", + "glob-parent": "3.1.0", + "is-negated-glob": "1.0.0", + "ordered-read-streams": "1.0.1", + "pumpify": "1.5.1", + "readable-stream": "2.3.6", + "remove-trailing-separator": "1.1.0", + "to-absolute-glob": "2.0.2", + "unique-stream": "2.2.1" } }, "ordered-read-streams": { @@ -4511,7 +4511,7 @@ "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", "dev": true, "requires": { - "readable-stream": "^2.0.1" + "readable-stream": "2.3.6" } }, "process-nextick-args": { @@ -4526,13 +4526,13 @@ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, "replace-ext": { @@ -4553,7 +4553,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.2" } }, "unique-stream": { @@ -4562,8 +4562,8 @@ "integrity": "sha1-WqADz76Uxf+GbE59ZouxxNuts2k=", "dev": true, "requires": { - "json-stable-stringify": "^1.0.0", - "through2-filter": "^2.0.0" + "json-stable-stringify": "1.0.1", + "through2-filter": "2.0.0" } }, "vinyl": { @@ -4572,12 +4572,12 @@ "integrity": "sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=", "dev": true, "requires": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" + "clone": "2.1.1", + "clone-buffer": "1.0.0", + "clone-stats": "1.0.0", + "cloneable-readable": "1.1.2", + "remove-trailing-separator": "1.1.0", + "replace-ext": "1.0.0" } }, "vinyl-fs": { @@ -4586,23 +4586,23 @@ "integrity": "sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==", "dev": true, "requires": { - "fs-mkdirp-stream": "^1.0.0", - "glob-stream": "^6.1.0", - "graceful-fs": "^4.0.0", - "is-valid-glob": "^1.0.0", - "lazystream": "^1.0.0", - "lead": "^1.0.0", - "object.assign": "^4.0.4", - "pumpify": "^1.3.5", - "readable-stream": "^2.3.3", - "remove-bom-buffer": "^3.0.0", - "remove-bom-stream": "^1.2.0", - "resolve-options": "^1.1.0", - "through2": "^2.0.0", - "to-through": "^2.0.0", - "value-or-function": "^3.0.0", - "vinyl": "^2.0.0", - "vinyl-sourcemap": "^1.1.0" + "fs-mkdirp-stream": "1.0.0", + "glob-stream": "6.1.0", + "graceful-fs": "4.1.11", + "is-valid-glob": "1.0.0", + "lazystream": "1.0.0", + "lead": "1.0.0", + "object.assign": "4.1.0", + "pumpify": "1.5.1", + "readable-stream": "2.3.6", + "remove-bom-buffer": "3.0.0", + "remove-bom-stream": "1.2.0", + "resolve-options": "1.1.0", + "through2": "2.0.3", + "to-through": "2.0.0", + "value-or-function": "3.0.0", + "vinyl": "2.1.0", + "vinyl-sourcemap": "1.1.0" } } } @@ -4613,11 +4613,11 @@ "integrity": "sha512-0QfbCH2a1k2qkTLWPqTX+QO4qNsHn3kC546YhAP3/n0h+nvtyGITDuDrYBMDZeW4WnFijmkOvBWa5HshTic1tw==", "dev": true, "requires": { - "event-stream": "~3.3.4", - "streamifier": "~0.1.1", - "tar": "^2.2.1", - "through2": "~2.0.3", - "vinyl": "^1.2.0" + "event-stream": "3.3.4", + "streamifier": "0.1.1", + "tar": "2.2.1", + "through2": "2.0.3", + "vinyl": "1.2.0" }, "dependencies": { "vinyl": { @@ -4626,8 +4626,8 @@ "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", + "clone": "1.0.4", + "clone-stats": "0.0.1", "replace-ext": "0.0.1" } } @@ -4639,24 +4639,24 @@ "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", "dev": true, "requires": { - "array-differ": "^1.0.0", - "array-uniq": "^1.0.2", - "beeper": "^1.0.0", - "chalk": "^1.0.0", - "dateformat": "^2.0.0", - "fancy-log": "^1.1.0", - "gulplog": "^1.0.0", - "has-gulplog": "^0.1.0", - "lodash._reescape": "^3.0.0", - "lodash._reevaluate": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.template": "^3.0.0", - "minimist": "^1.1.0", - "multipipe": "^0.1.2", - "object-assign": "^3.0.0", + "array-differ": "1.0.0", + "array-uniq": "1.0.3", + "beeper": "1.1.1", + "chalk": "1.1.3", + "dateformat": "2.2.0", + "fancy-log": "1.3.2", + "gulplog": "1.0.0", + "has-gulplog": "0.1.0", + "lodash._reescape": "3.0.0", + "lodash._reevaluate": "3.0.0", + "lodash._reinterpolate": "3.0.0", + "lodash.template": "3.6.2", + "minimist": "1.2.0", + "multipipe": "0.1.2", + "object-assign": "3.0.0", "replace-ext": "0.0.1", - "through2": "^2.0.0", - "vinyl": "^0.5.0" + "through2": "2.0.3", + "vinyl": "0.5.3" }, "dependencies": { "object-assign": { @@ -4673,13 +4673,13 @@ "integrity": "sha1-JOQGhdwFtxSZlSRQmeBZAmO+ja0=", "dev": true, "requires": { - "event-stream": "^3.3.1", - "queue": "^4.2.1", - "through2": "^2.0.3", - "vinyl": "^2.0.2", - "vinyl-fs": "^2.0.0", - "yauzl": "^2.2.1", - "yazl": "^2.2.1" + "event-stream": "3.3.4", + "queue": "4.4.2", + "through2": "2.0.3", + "vinyl": "2.1.0", + "vinyl-fs": "2.4.4", + "yauzl": "2.9.1", + "yazl": "2.4.3" }, "dependencies": { "arr-diff": { @@ -4688,7 +4688,7 @@ "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", "dev": true, "requires": { - "arr-flatten": "^1.0.1" + "arr-flatten": "1.1.0" } }, "array-unique": { @@ -4703,9 +4703,9 @@ "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", "dev": true, "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.2" } }, "clone": { @@ -4726,7 +4726,7 @@ "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", "dev": true, "requires": { - "is-posix-bracket": "^0.1.0" + "is-posix-bracket": "0.1.1" } }, "extend-shallow": { @@ -4735,7 +4735,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } }, "extglob": { @@ -4744,7 +4744,7 @@ "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "is-extglob": "1.0.0" } }, "glob": { @@ -4753,11 +4753,11 @@ "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", "dev": true, "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "glob-stream": { @@ -4766,14 +4766,14 @@ "integrity": "sha1-pVZlqajM3EGRWofHAeMtTgFvrSI=", "dev": true, "requires": { - "extend": "^3.0.0", - "glob": "^5.0.3", - "glob-parent": "^3.0.0", - "micromatch": "^2.3.7", - "ordered-read-streams": "^0.3.0", - "through2": "^0.6.0", - "to-absolute-glob": "^0.1.1", - "unique-stream": "^2.0.2" + "extend": "3.0.1", + "glob": "5.0.15", + "glob-parent": "3.1.0", + "micromatch": "2.3.11", + "ordered-read-streams": "0.3.0", + "through2": "0.6.5", + "to-absolute-glob": "0.1.1", + "unique-stream": "2.2.1" }, "dependencies": { "readable-stream": { @@ -4782,10 +4782,10 @@ "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", + "core-util-is": "1.0.2", + "inherits": "2.0.3", "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "string_decoder": "0.10.31" } }, "through2": { @@ -4794,8 +4794,8 @@ "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, "requires": { - "readable-stream": ">=1.0.33-1 <1.1.0-0", - "xtend": ">=4.0.0 <4.1.0-0" + "readable-stream": "1.0.34", + "xtend": "4.0.1" } } } @@ -4806,11 +4806,11 @@ "integrity": "sha1-uG/zSdgBzrVuHZ59x7vLS33uYAw=", "dev": true, "requires": { - "convert-source-map": "^1.1.1", - "graceful-fs": "^4.1.2", - "strip-bom": "^2.0.0", - "through2": "^2.0.0", - "vinyl": "^1.0.0" + "convert-source-map": "1.5.1", + "graceful-fs": "4.1.11", + "strip-bom": "2.0.0", + "through2": "2.0.3", + "vinyl": "1.2.0" }, "dependencies": { "clone": { @@ -4837,8 +4837,8 @@ "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", + "clone": "1.0.4", + "clone-stats": "0.0.1", "replace-ext": "0.0.1" } } @@ -4856,7 +4856,7 @@ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "is-extglob": "1.0.0" } }, "is-valid-glob": { @@ -4877,7 +4877,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } }, "micromatch": { @@ -4886,19 +4886,19 @@ "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", "dev": true, "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" + "arr-diff": "2.0.0", + "array-unique": "0.2.1", + "braces": "1.8.5", + "expand-brackets": "0.1.5", + "extglob": "0.3.2", + "filename-regex": "2.0.1", + "is-extglob": "1.0.0", + "is-glob": "2.0.1", + "kind-of": "3.2.2", + "normalize-path": "2.1.1", + "object.omit": "2.0.1", + "parse-glob": "3.0.4", + "regex-cache": "0.4.4" } }, "ordered-read-streams": { @@ -4907,8 +4907,8 @@ "integrity": "sha1-cTfmmzKYuzQiR6G77jiByA4v14s=", "dev": true, "requires": { - "is-stream": "^1.0.1", - "readable-stream": "^2.0.1" + "is-stream": "1.1.0", + "readable-stream": "2.0.6" } }, "queue": { @@ -4917,7 +4917,7 @@ "integrity": "sha512-fSMRXbwhMwipcDZ08enW2vl+YDmAmhcNcr43sCJL8DIg+CFOsoRLG23ctxA+fwNk1w55SePSiS7oqQQSgQoVJQ==", "dev": true, "requires": { - "inherits": "~2.0.0" + "inherits": "2.0.3" } }, "replace-ext": { @@ -4932,7 +4932,7 @@ "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, "requires": { - "is-utf8": "^0.2.0" + "is-utf8": "0.2.1" } }, "strip-bom-stream": { @@ -4941,8 +4941,8 @@ "integrity": "sha1-5xRDmFd9Uaa+0PoZlPoF9D/ZiO4=", "dev": true, "requires": { - "first-chunk-stream": "^1.0.0", - "strip-bom": "^2.0.0" + "first-chunk-stream": "1.0.0", + "strip-bom": "2.0.0" } }, "to-absolute-glob": { @@ -4951,7 +4951,7 @@ "integrity": "sha1-HN+kcqnvUMI57maZm2YsoOs5k38=", "dev": true, "requires": { - "extend-shallow": "^2.0.1" + "extend-shallow": "2.0.1" } }, "unique-stream": { @@ -4960,8 +4960,8 @@ "integrity": "sha1-WqADz76Uxf+GbE59ZouxxNuts2k=", "dev": true, "requires": { - "json-stable-stringify": "^1.0.0", - "through2-filter": "^2.0.0" + "json-stable-stringify": "1.0.1", + "through2-filter": "2.0.0" } }, "vinyl": { @@ -4970,12 +4970,12 @@ "integrity": "sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=", "dev": true, "requires": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" + "clone": "2.1.1", + "clone-buffer": "1.0.0", + "clone-stats": "1.0.0", + "cloneable-readable": "1.1.2", + "remove-trailing-separator": "1.1.0", + "replace-ext": "1.0.0" } }, "vinyl-fs": { @@ -4984,23 +4984,23 @@ "integrity": "sha1-vm/zJwy1Xf19MGNkDegfJddTIjk=", "dev": true, "requires": { - "duplexify": "^3.2.0", - "glob-stream": "^5.3.2", - "graceful-fs": "^4.0.0", + "duplexify": "3.6.0", + "glob-stream": "5.3.5", + "graceful-fs": "4.1.11", "gulp-sourcemaps": "1.6.0", - "is-valid-glob": "^0.3.0", - "lazystream": "^1.0.0", - "lodash.isequal": "^4.0.0", - "merge-stream": "^1.0.0", - "mkdirp": "^0.5.0", - "object-assign": "^4.0.0", - "readable-stream": "^2.0.4", - "strip-bom": "^2.0.0", - "strip-bom-stream": "^1.0.0", - "through2": "^2.0.0", - "through2-filter": "^2.0.0", - "vali-date": "^1.0.0", - "vinyl": "^1.0.0" + "is-valid-glob": "0.3.0", + "lazystream": "1.0.0", + "lodash.isequal": "4.5.0", + "merge-stream": "1.0.1", + "mkdirp": "0.5.1", + "object-assign": "4.1.1", + "readable-stream": "2.0.6", + "strip-bom": "2.0.0", + "strip-bom-stream": "1.0.0", + "through2": "2.0.3", + "through2-filter": "2.0.0", + "vali-date": "1.0.0", + "vinyl": "1.2.0" }, "dependencies": { "clone": { @@ -5027,8 +5027,8 @@ "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", + "clone": "1.0.4", + "clone-stats": "0.0.1", "replace-ext": "0.0.1" } } @@ -5042,16 +5042,16 @@ "integrity": "sha512-q+HLppxXd11z9ndqql4Z0sd5xOAesJjycl0PRaq6ImK7b1BqBRL37YvxEE8ngUdIfpfHa0O9OCoovoggcFpCaQ==", "dev": true, "requires": { - "anymatch": "^1.3.0", - "chokidar": "^2.0.0", - "glob-parent": "^3.0.1", - "gulp-util": "^3.0.7", - "object-assign": "^4.1.0", - "path-is-absolute": "^1.0.1", - "readable-stream": "^2.2.2", - "slash": "^1.0.0", - "vinyl": "^2.1.0", - "vinyl-file": "^2.0.0" + "anymatch": "1.3.2", + "chokidar": "2.0.3", + "glob-parent": "3.1.0", + "gulp-util": "3.0.8", + "object-assign": "4.1.1", + "path-is-absolute": "1.0.1", + "readable-stream": "2.3.6", + "slash": "1.0.0", + "vinyl": "2.1.0", + "vinyl-file": "2.0.0" }, "dependencies": { "chokidar": { @@ -5060,18 +5060,18 @@ "integrity": "sha512-zW8iXYZtXMx4kux/nuZVXjkLP+CyIK5Al5FHnj1OgTKGZfp4Oy6/ymtMSKFv3GD8DviEmUPmJg9eFdJ/JzudMg==", "dev": true, "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.0", - "braces": "^2.3.0", - "fsevents": "^1.1.2", - "glob-parent": "^3.1.0", - "inherits": "^2.0.1", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^2.1.1", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0", - "upath": "^1.0.0" + "anymatch": "2.0.0", + "async-each": "1.0.1", + "braces": "2.3.2", + "fsevents": "1.2.4", + "glob-parent": "3.1.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "4.0.0", + "normalize-path": "2.1.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.1.0", + "upath": "1.1.0" }, "dependencies": { "anymatch": { @@ -5080,8 +5080,8 @@ "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", "dev": true, "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" + "micromatch": "3.1.10", + "normalize-path": "2.1.1" } } } @@ -5104,7 +5104,7 @@ "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", "dev": true, "requires": { - "is-extglob": "^2.1.1" + "is-extglob": "2.1.1" } }, "process-nextick-args": { @@ -5119,13 +5119,13 @@ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, "replace-ext": { @@ -5140,7 +5140,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.2" } }, "vinyl": { @@ -5149,12 +5149,12 @@ "integrity": "sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=", "dev": true, "requires": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" + "clone": "2.1.1", + "clone-buffer": "1.0.0", + "clone-stats": "1.0.0", + "cloneable-readable": "1.1.2", + "remove-trailing-separator": "1.1.0", + "replace-ext": "1.0.0" } } } @@ -5165,7 +5165,7 @@ "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", "dev": true, "requires": { - "glogg": "^1.0.0" + "glogg": "1.0.1" } }, "handlebars": { @@ -5174,10 +5174,10 @@ "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", "dev": true, "requires": { - "async": "^1.4.0", - "optimist": "^0.6.1", - "source-map": "^0.4.4", - "uglify-js": "^2.6" + "async": "1.5.2", + "optimist": "0.6.1", + "source-map": "0.4.4", + "uglify-js": "2.8.29" }, "dependencies": { "source-map": { @@ -5186,7 +5186,7 @@ "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", "dev": true, "requires": { - "amdefine": ">=0.0.4" + "amdefine": "1.0.1" } } } @@ -5201,8 +5201,8 @@ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", "requires": { - "ajv": "^5.1.0", - "har-schema": "^2.0.0" + "ajv": "5.5.2", + "har-schema": "2.0.0" } }, "has-ansi": { @@ -5211,7 +5211,7 @@ "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "has-flag": { @@ -5226,7 +5226,7 @@ "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", "dev": true, "requires": { - "sparkles": "^1.0.0" + "sparkles": "1.0.1" } }, "has-symbol-support-x": { @@ -5247,7 +5247,7 @@ "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", "dev": true, "requires": { - "has-symbol-support-x": "^1.4.1" + "has-symbol-support-x": "1.4.2" } }, "has-value": { @@ -5256,9 +5256,9 @@ "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", "dev": true, "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" + "get-value": "2.0.6", + "has-values": "1.0.0", + "isobject": "3.0.1" } }, "has-values": { @@ -5267,8 +5267,8 @@ "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", "dev": true, "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" + "is-number": "3.0.0", + "kind-of": "4.0.0" }, "dependencies": { "kind-of": { @@ -5277,7 +5277,7 @@ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -5288,8 +5288,8 @@ "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", "dev": true, "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "inherits": "2.0.3", + "safe-buffer": "5.1.2" } }, "hawk": { @@ -5297,10 +5297,10 @@ "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", "requires": { - "boom": "4.x.x", - "cryptiles": "3.x.x", - "hoek": "4.x.x", - "sntp": "2.x.x" + "boom": "4.3.1", + "cryptiles": "3.1.2", + "hoek": "4.2.1", + "sntp": "2.1.0" } }, "he": { @@ -5320,7 +5320,7 @@ "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", "dev": true, "requires": { - "parse-passwd": "^1.0.0" + "parse-passwd": "1.0.0" } }, "hosted-git-info": { @@ -5335,12 +5335,12 @@ "integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=", "dev": true, "requires": { - "domelementtype": "^1.3.0", - "domhandler": "^2.3.0", - "domutils": "^1.5.1", - "entities": "^1.1.1", - "inherits": "^2.0.1", - "readable-stream": "^2.0.2" + "domelementtype": "1.3.0", + "domhandler": "2.4.2", + "domutils": "1.7.0", + "entities": "1.1.1", + "inherits": "2.0.3", + "readable-stream": "2.0.6" } }, "http-cache-semantics": { @@ -5354,9 +5354,9 @@ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" + "assert-plus": "1.0.0", + "jsprim": "1.4.1", + "sshpk": "1.14.1" } }, "husky": { @@ -5365,9 +5365,9 @@ "integrity": "sha512-e21wivqHpstpoiWA/Yi8eFti8E+sQDSS53cpJsPptPs295QTOQR0ZwnHo2TXy1XOpZFD9rPOd3NpmqTK6uMLJA==", "dev": true, "requires": { - "is-ci": "^1.0.10", - "normalize-path": "^1.0.0", - "strip-indent": "^2.0.0" + "is-ci": "1.1.0", + "normalize-path": "1.0.0", + "strip-indent": "2.0.0" }, "dependencies": { "normalize-path": { @@ -5389,7 +5389,7 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.21.tgz", "integrity": "sha512-En5V9za5mBt2oUA03WGD3TwDv0MKAruqsuxstbMUZaj9W9k/m1CV/9py3l0L5kw9Bln8fdHQmzHSYtvpvTLpKw==", "requires": { - "safer-buffer": "^2.1.0" + "safer-buffer": "2.1.2" } }, "ieee754": { @@ -5404,7 +5404,7 @@ "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", "dev": true, "requires": { - "repeating": "^2.0.0" + "repeating": "2.0.1" } }, "inflight": { @@ -5412,8 +5412,8 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { - "once": "^1.3.0", - "wrappy": "1" + "once": "1.4.0", + "wrappy": "1.0.2" } }, "inherits": { @@ -5433,12 +5433,12 @@ "integrity": "sha512-RvMOGMXxAqqve4ld128B7TYyNR2aP1LB38dcSpWFmqXrhKPuey1+yFU6kFUgyH8IWX+gRZdWtHN4eQ9d0IpFZg==", "dev": true, "requires": { - "csso": "3.4.x", - "htmlparser2": "3.9.x", - "is-plain-obj": "1.1.x", - "object-assign": "4.1.x", - "svgo": "0.7.x", - "uglify-js": "3.3.x" + "csso": "3.4.0", + "htmlparser2": "3.9.2", + "is-plain-obj": "1.1.0", + "object-assign": "4.1.1", + "svgo": "0.7.2", + "uglify-js": "3.3.28" }, "dependencies": { "source-map": { @@ -5453,8 +5453,8 @@ "integrity": "sha512-68Rc/aA6cswiaQ5SrE979UJcXX+ADA1z33/ZsPd+fbAiVdjZ16OXdbtGO+rJUUBgK6qdf3SOPhQf3K/ybF5Miw==", "dev": true, "requires": { - "commander": "~2.15.0", - "source-map": "~0.6.1" + "commander": "2.15.1", + "source-map": "0.6.1" } } } @@ -5471,8 +5471,8 @@ "integrity": "sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY=", "dev": true, "requires": { - "from2": "^2.1.1", - "p-is-promise": "^1.1.0" + "from2": "2.3.0", + "p-is-promise": "1.1.0" } }, "inversify": { @@ -5492,8 +5492,8 @@ "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", "dev": true, "requires": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" + "is-relative": "1.0.0", + "is-windows": "1.0.2" } }, "is-accessor-descriptor": { @@ -5502,7 +5502,7 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -5511,7 +5511,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -5528,7 +5528,7 @@ "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", "dev": true, "requires": { - "binary-extensions": "^1.0.0" + "binary-extensions": "1.11.0" } }, "is-buffer": { @@ -5542,7 +5542,7 @@ "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "dev": true, "requires": { - "builtin-modules": "^1.0.0" + "builtin-modules": "1.1.1" } }, "is-ci": { @@ -5551,7 +5551,7 @@ "integrity": "sha512-c7TnwxLePuqIlxHgr7xtxzycJPegNHFuIrBkwbf8hc58//+Op1CqFkyS+xnIMkwn9UsJIwc174BIjkyBmSpjKg==", "dev": true, "requires": { - "ci-info": "^1.0.0" + "ci-info": "1.1.3" } }, "is-data-descriptor": { @@ -5560,7 +5560,7 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -5569,7 +5569,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -5580,9 +5580,9 @@ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" }, "dependencies": { "kind-of": { @@ -5605,7 +5605,7 @@ "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", "dev": true, "requires": { - "is-primitive": "^2.0.0" + "is-primitive": "2.0.0" } }, "is-extendable": { @@ -5626,7 +5626,7 @@ "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "number-is-nan": "1.0.1" } }, "is-glob": { @@ -5635,7 +5635,7 @@ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, "requires": { - "is-extglob": "^2.1.0" + "is-extglob": "2.1.1" } }, "is-natural-number": { @@ -5656,7 +5656,7 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -5665,7 +5665,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -5688,7 +5688,7 @@ "integrity": "sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==", "dev": true, "requires": { - "is-number": "^4.0.0" + "is-number": "4.0.0" }, "dependencies": { "is-number": { @@ -5711,7 +5711,7 @@ "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", "dev": true, "requires": { - "is-path-inside": "^1.0.0" + "is-path-inside": "1.0.1" } }, "is-path-inside": { @@ -5720,7 +5720,7 @@ "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", "dev": true, "requires": { - "path-is-inside": "^1.0.1" + "path-is-inside": "1.0.2" } }, "is-plain-obj": { @@ -5735,7 +5735,7 @@ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "requires": { - "isobject": "^3.0.1" + "isobject": "3.0.1" } }, "is-posix-bracket": { @@ -5762,7 +5762,7 @@ "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", "dev": true, "requires": { - "is-unc-path": "^1.0.0" + "is-unc-path": "1.0.0" } }, "is-retry-allowed": { @@ -5794,7 +5794,7 @@ "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", "dev": true, "requires": { - "unc-path-regex": "^0.1.2" + "unc-path-regex": "0.1.2" } }, "is-utf8": { @@ -5849,20 +5849,20 @@ "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", "dev": true, "requires": { - "abbrev": "1.0.x", - "async": "1.x", - "escodegen": "1.8.x", - "esprima": "2.7.x", - "glob": "^5.0.15", - "handlebars": "^4.0.1", - "js-yaml": "3.x", - "mkdirp": "0.5.x", - "nopt": "3.x", - "once": "1.x", - "resolve": "1.1.x", - "supports-color": "^3.1.0", - "which": "^1.1.1", - "wordwrap": "^1.0.0" + "abbrev": "1.0.9", + "async": "1.5.2", + "escodegen": "1.8.1", + "esprima": "2.7.3", + "glob": "5.0.15", + "handlebars": "4.0.11", + "js-yaml": "3.11.0", + "mkdirp": "0.5.1", + "nopt": "3.0.6", + "once": "1.4.0", + "resolve": "1.1.7", + "supports-color": "3.2.3", + "which": "1.3.1", + "wordwrap": "1.0.0" }, "dependencies": { "abbrev": { @@ -5877,11 +5877,11 @@ "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", "dev": true, "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "resolve": { @@ -5896,7 +5896,7 @@ "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "1.0.0" } } } @@ -5907,8 +5907,8 @@ "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", "dev": true, "requires": { - "has-to-string-tag-x": "^1.2.0", - "is-object": "^1.0.1" + "has-to-string-tag-x": "1.4.1", + "is-object": "1.0.1" } }, "js-beautify": { @@ -5917,10 +5917,10 @@ "integrity": "sha512-9OhfAqGOrD7hoQBLJMTA+BKuKmoEtTJXzZ7WDF/9gvjtey1koVLuZqIY6c51aPDjbNdNtIXAkiWKVhziawE9Og==", "dev": true, "requires": { - "config-chain": "~1.1.5", - "editorconfig": "^0.13.2", - "mkdirp": "~0.5.0", - "nopt": "~3.0.1" + "config-chain": "1.1.11", + "editorconfig": "0.13.3", + "mkdirp": "0.5.1", + "nopt": "3.0.6" } }, "js-tokens": { @@ -5935,8 +5935,8 @@ "integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==", "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "1.0.10", + "esprima": "4.0.0" }, "dependencies": { "esprima": { @@ -5965,7 +5965,7 @@ "integrity": "sha1-HmCw/vG8CvZ7wNFG393lSGzWFbQ=", "dev": true, "requires": { - "jsonparse": "~1.2.0" + "jsonparse": "1.2.0" } }, "json-schema": { @@ -5984,7 +5984,7 @@ "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", "dev": true, "requires": { - "jsonify": "~0.0.0" + "jsonify": "0.0.0" } }, "json-stringify-safe": { @@ -5997,7 +5997,7 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "requires": { - "graceful-fs": "^4.1.6" + "graceful-fs": "4.1.11" } }, "jsonify": { @@ -6057,7 +6057,7 @@ "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", "dev": true, "requires": { - "readable-stream": "^2.0.5" + "readable-stream": "2.0.6" } }, "lead": { @@ -6066,7 +6066,7 @@ "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", "dev": true, "requires": { - "flush-write-stream": "^1.0.2" + "flush-write-stream": "1.0.3" } }, "levn": { @@ -6075,8 +6075,8 @@ "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", "dev": true, "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" + "prelude-ls": "1.1.2", + "type-check": "0.3.2" } }, "liftoff": { @@ -6085,14 +6085,14 @@ "integrity": "sha1-IAkpG7Mc6oYbvxCnwVooyvdcMew=", "dev": true, "requires": { - "extend": "^3.0.0", - "findup-sync": "^2.0.0", - "fined": "^1.0.1", - "flagged-respawn": "^1.0.0", - "is-plain-object": "^2.0.4", - "object.map": "^1.0.0", - "rechoir": "^0.6.2", - "resolve": "^1.1.7" + "extend": "3.0.1", + "findup-sync": "2.0.0", + "fined": "1.1.0", + "flagged-respawn": "1.0.0", + "is-plain-object": "2.0.4", + "object.map": "1.0.1", + "rechoir": "0.6.2", + "resolve": "1.7.1" } }, "line-by-line": { @@ -6106,11 +6106,11 @@ "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "strip-bom": "2.0.0" }, "dependencies": { "pify": { @@ -6125,7 +6125,7 @@ "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, "requires": { - "is-utf8": "^0.2.0" + "is-utf8": "0.2.1" } } } @@ -6141,10 +6141,10 @@ "integrity": "sha1-t7K7Q9whYEJKIczybFfkQ3cqjic=", "dev": true, "requires": { - "lodash._baseisequal": "^3.0.0", - "lodash._bindcallback": "^3.0.0", - "lodash.isarray": "^3.0.0", - "lodash.pairs": "^3.0.0" + "lodash._baseisequal": "3.0.7", + "lodash._bindcallback": "3.0.1", + "lodash.isarray": "3.0.4", + "lodash.pairs": "3.0.1" } }, "lodash._basecopy": { @@ -6159,7 +6159,7 @@ "integrity": "sha1-z4cGVyyhROjZ11InyZDamC+TKvM=", "dev": true, "requires": { - "lodash.keys": "^3.0.0" + "lodash.keys": "3.1.2" } }, "lodash._basefind": { @@ -6180,9 +6180,9 @@ "integrity": "sha1-2AJfdjOdKTQnZ9zIh85cuVpbUfE=", "dev": true, "requires": { - "lodash.isarray": "^3.0.0", - "lodash.istypedarray": "^3.0.0", - "lodash.keys": "^3.0.0" + "lodash.isarray": "3.0.4", + "lodash.istypedarray": "3.0.6", + "lodash.keys": "3.1.2" } }, "lodash._basetostring": { @@ -6209,7 +6209,7 @@ "integrity": "sha1-32fDu2t+jh6DGrSL+geVuSr+iZ0=", "dev": true, "requires": { - "lodash._htmlescapes": "~2.4.1" + "lodash._htmlescapes": "2.4.1" } }, "lodash._escapestringchar": { @@ -6272,8 +6272,8 @@ "integrity": "sha1-dHxPxAED6zu4oJduVx96JlnpO6c=", "dev": true, "requires": { - "lodash._htmlescapes": "~2.4.1", - "lodash.keys": "~2.4.1" + "lodash._htmlescapes": "2.4.1", + "lodash.keys": "2.4.1" }, "dependencies": { "lodash.keys": { @@ -6282,9 +6282,9 @@ "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", "dev": true, "requires": { - "lodash._isnative": "~2.4.1", - "lodash._shimkeys": "~2.4.1", - "lodash.isobject": "~2.4.1" + "lodash._isnative": "2.4.1", + "lodash._shimkeys": "2.4.1", + "lodash.isobject": "2.4.1" } } } @@ -6301,7 +6301,7 @@ "integrity": "sha1-bpzJZm/wgfC1psl4uD4kLmlJ0gM=", "dev": true, "requires": { - "lodash._objecttypes": "~2.4.1" + "lodash._objecttypes": "2.4.1" } }, "lodash.defaults": { @@ -6310,8 +6310,8 @@ "integrity": "sha1-p+iIXwXmiFEUS24SqPNngCa8TFQ=", "dev": true, "requires": { - "lodash._objecttypes": "~2.4.1", - "lodash.keys": "~2.4.1" + "lodash._objecttypes": "2.4.1", + "lodash.keys": "2.4.1" }, "dependencies": { "lodash.keys": { @@ -6320,9 +6320,9 @@ "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", "dev": true, "requires": { - "lodash._isnative": "~2.4.1", - "lodash._shimkeys": "~2.4.1", - "lodash.isobject": "~2.4.1" + "lodash._isnative": "2.4.1", + "lodash._shimkeys": "2.4.1", + "lodash.isobject": "2.4.1" } } } @@ -6333,7 +6333,7 @@ "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", "dev": true, "requires": { - "lodash._root": "^3.0.0" + "lodash._root": "3.0.1" } }, "lodash.find": { @@ -6342,12 +6342,12 @@ "integrity": "sha1-BG4xnzrOkSrGySRsf2g8XsB7Nq0=", "dev": true, "requires": { - "lodash._basecallback": "^3.0.0", - "lodash._baseeach": "^3.0.0", - "lodash._basefind": "^3.0.0", - "lodash._basefindindex": "^3.0.0", - "lodash.isarray": "^3.0.0", - "lodash.keys": "^3.0.0" + "lodash._basecallback": "3.3.1", + "lodash._baseeach": "3.0.4", + "lodash._basefind": "3.0.0", + "lodash._basefindindex": "3.6.0", + "lodash.isarray": "3.0.4", + "lodash.keys": "3.1.2" } }, "lodash.get": { @@ -6380,7 +6380,7 @@ "integrity": "sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU=", "dev": true, "requires": { - "lodash._objecttypes": "~2.4.1" + "lodash._objecttypes": "2.4.1" } }, "lodash.istypedarray": { @@ -6395,9 +6395,9 @@ "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", "dev": true, "requires": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" + "lodash._getnative": "3.9.1", + "lodash.isarguments": "3.1.0", + "lodash.isarray": "3.0.4" } }, "lodash.pairs": { @@ -6406,7 +6406,7 @@ "integrity": "sha1-u+CNV4bu6qCaFckevw3LfSvjJqk=", "dev": true, "requires": { - "lodash.keys": "^3.0.0" + "lodash.keys": "3.1.2" } }, "lodash.restparam": { @@ -6421,15 +6421,15 @@ "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", "dev": true, "requires": { - "lodash._basecopy": "^3.0.0", - "lodash._basetostring": "^3.0.0", - "lodash._basevalues": "^3.0.0", - "lodash._isiterateecall": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0", - "lodash.keys": "^3.0.0", - "lodash.restparam": "^3.0.0", - "lodash.templatesettings": "^3.0.0" + "lodash._basecopy": "3.0.1", + "lodash._basetostring": "3.0.1", + "lodash._basevalues": "3.0.0", + "lodash._isiterateecall": "3.0.9", + "lodash._reinterpolate": "3.0.0", + "lodash.escape": "3.2.0", + "lodash.keys": "3.1.2", + "lodash.restparam": "3.6.1", + "lodash.templatesettings": "3.1.1" } }, "lodash.templatesettings": { @@ -6438,8 +6438,8 @@ "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", "dev": true, "requires": { - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0" + "lodash._reinterpolate": "3.0.0", + "lodash.escape": "3.2.0" } }, "lodash.values": { @@ -6448,7 +6448,7 @@ "integrity": "sha1-q/UUQ2s8twUAFieXjLzzCxKA7qQ=", "dev": true, "requires": { - "lodash.keys": "~2.4.1" + "lodash.keys": "2.4.1" }, "dependencies": { "lodash.keys": { @@ -6457,9 +6457,9 @@ "integrity": "sha1-SN6kbfj/djKxDXBrissmWR4rNyc=", "dev": true, "requires": { - "lodash._isnative": "~2.4.1", - "lodash._shimkeys": "~2.4.1", - "lodash.isobject": "~2.4.1" + "lodash._isnative": "2.4.1", + "lodash._shimkeys": "2.4.1", + "lodash.isobject": "2.4.1" } } } @@ -6482,8 +6482,8 @@ "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", "dev": true, "requires": { - "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" + "currently-unhandled": "0.4.1", + "signal-exit": "3.0.2" } }, "lowercase-keys": { @@ -6504,7 +6504,7 @@ "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", "dev": true, "requires": { - "es5-ext": "~0.10.2" + "es5-ext": "0.10.43" } }, "make-dir": { @@ -6513,7 +6513,7 @@ "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", "dev": true, "requires": { - "pify": "^3.0.0" + "pify": "3.0.0" } }, "make-iterator": { @@ -6522,7 +6522,7 @@ "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", "dev": true, "requires": { - "kind-of": "^6.0.2" + "kind-of": "6.0.2" } }, "map-cache": { @@ -6549,7 +6549,7 @@ "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", "dev": true, "requires": { - "object-visit": "^1.0.0" + "object-visit": "1.0.1" } }, "math-random": { @@ -6563,9 +6563,9 @@ "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=", "requires": { - "charenc": "~0.0.1", - "crypt": "~0.0.1", - "is-buffer": "~1.1.1" + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "1.1.6" } }, "md5.js": { @@ -6574,8 +6574,8 @@ "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", "dev": true, "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" + "hash-base": "3.0.4", + "inherits": "2.0.3" } }, "mdn-data": { @@ -6590,14 +6590,14 @@ "integrity": "sha512-sprBu6nwxBWBvBOh5v2jcsGqiGLlL2xr2dLub3vR8dnE8YB17omwtm/0NSHl8jjNbcsJd5GMWJAnTSVe/O0Wfg==", "dev": true, "requires": { - "d": "1", - "es5-ext": "^0.10.30", - "es6-weak-map": "^2.0.2", - "event-emitter": "^0.3.5", - "is-promise": "^2.1", - "lru-queue": "0.1", - "next-tick": "1", - "timers-ext": "^0.1.2" + "d": "1.0.0", + "es5-ext": "0.10.43", + "es6-weak-map": "2.0.2", + "event-emitter": "0.3.5", + "is-promise": "2.1.0", + "lru-queue": "0.1.0", + "next-tick": "1.0.0", + "timers-ext": "0.1.5" } }, "meow": { @@ -6606,16 +6606,16 @@ "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "dev": true, "requires": { - "camelcase-keys": "^2.0.0", - "decamelize": "^1.1.2", - "loud-rejection": "^1.0.0", - "map-obj": "^1.0.1", - "minimist": "^1.1.3", - "normalize-package-data": "^2.3.4", - "object-assign": "^4.0.1", - "read-pkg-up": "^1.0.1", - "redent": "^1.0.0", - "trim-newlines": "^1.0.0" + "camelcase-keys": "2.1.0", + "decamelize": "1.2.0", + "loud-rejection": "1.6.0", + "map-obj": "1.0.1", + "minimist": "1.2.0", + "normalize-package-data": "2.4.0", + "object-assign": "4.1.1", + "read-pkg-up": "1.0.1", + "redent": "1.0.0", + "trim-newlines": "1.0.0" } }, "merge-stream": { @@ -6624,7 +6624,7 @@ "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", "dev": true, "requires": { - "readable-stream": "^2.0.1" + "readable-stream": "2.0.6" } }, "micromatch": { @@ -6633,19 +6633,19 @@ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.9", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" } }, "mime-db": { @@ -6658,7 +6658,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", "requires": { - "mime-db": "~1.33.0" + "mime-db": "1.33.0" } }, "mimic-response": { @@ -6672,7 +6672,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "1.1.11" } }, "minimist": { @@ -6687,8 +6687,8 @@ "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", "dev": true, "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" + "for-in": "1.0.2", + "is-extendable": "1.0.1" }, "dependencies": { "is-extendable": { @@ -6697,7 +6697,7 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "is-plain-object": "^2.0.4" + "is-plain-object": "2.0.4" } } } @@ -6759,7 +6759,7 @@ "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } @@ -6770,11 +6770,11 @@ "integrity": "sha1-LlFJ7UD8XS48px5C21qx/snG2Fw=", "dev": true, "requires": { - "debug": "^2.2.0", - "md5": "^2.1.0", - "mkdirp": "~0.5.1", - "strip-ansi": "^4.0.0", - "xml": "^1.0.0" + "debug": "2.6.9", + "md5": "2.2.1", + "mkdirp": "0.5.1", + "strip-ansi": "4.0.0", + "xml": "1.0.1" }, "dependencies": { "ansi-regex": { @@ -6789,7 +6789,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } } } @@ -6806,10 +6806,10 @@ "integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=", "dev": true, "requires": { - "array-differ": "^1.0.0", - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "minimatch": "^3.0.0" + "array-differ": "1.0.0", + "array-union": "1.0.2", + "arrify": "1.0.1", + "minimatch": "3.0.4" } }, "multipipe": { @@ -6839,18 +6839,18 @@ "integrity": "sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-odd": "^2.0.0", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "fragment-cache": "0.2.1", + "is-odd": "2.0.0", + "is-windows": "1.0.2", + "kind-of": "6.0.2", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" } }, "natives": { @@ -6877,11 +6877,11 @@ "integrity": "sha512-v1J/FLUB9PfGqZLGDBhQqODkbLotP0WtLo9R4EJY2PPu5f5Xg4o0rA8FDlmrjFSv9vBBKcfnOSpfYYuu5RTHqg==", "dev": true, "requires": { - "@sinonjs/formatio": "^2.0.0", - "just-extend": "^1.1.27", - "lolex": "^2.3.2", - "path-to-regexp": "^1.7.0", - "text-encoding": "^0.6.4" + "@sinonjs/formatio": "2.0.0", + "just-extend": "1.1.27", + "lolex": "2.7.0", + "path-to-regexp": "1.7.0", + "text-encoding": "0.6.4" } }, "node-has-native-dependencies": { @@ -6904,7 +6904,7 @@ "integrity": "sha1-p7iCyC1sk6SGOlUEvV3o7IYli5Y=", "dev": true, "requires": { - "is": "^3.1.0" + "is": "3.2.1" } }, "nopt": { @@ -6913,7 +6913,7 @@ "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", "dev": true, "requires": { - "abbrev": "1" + "abbrev": "1.1.1" } }, "normalize-package-data": { @@ -6922,10 +6922,10 @@ "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", "dev": true, "requires": { - "hosted-git-info": "^2.1.4", - "is-builtin-module": "^1.0.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" + "hosted-git-info": "2.6.0", + "is-builtin-module": "1.0.0", + "semver": "5.5.0", + "validate-npm-package-license": "3.0.3" } }, "normalize-path": { @@ -6934,7 +6934,7 @@ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, "requires": { - "remove-trailing-separator": "^1.0.1" + "remove-trailing-separator": "1.1.0" } }, "normalize-url": { @@ -6943,9 +6943,9 @@ "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", "dev": true, "requires": { - "prepend-http": "^2.0.0", - "query-string": "^5.0.1", - "sort-keys": "^2.0.0" + "prepend-http": "2.0.0", + "query-string": "5.1.1", + "sort-keys": "2.0.0" }, "dependencies": { "sort-keys": { @@ -6954,7 +6954,7 @@ "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=", "dev": true, "requires": { - "is-plain-obj": "^1.0.0" + "is-plain-obj": "1.1.0" } } } @@ -6965,7 +6965,7 @@ "integrity": "sha1-vGHLtFbXnLMiB85HygUTb/Ln1u4=", "dev": true, "requires": { - "once": "^1.3.2" + "once": "1.4.0" } }, "npm-conf": { @@ -6974,8 +6974,8 @@ "integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==", "dev": true, "requires": { - "config-chain": "^1.1.11", - "pify": "^3.0.0" + "config-chain": "1.1.11", + "pify": "3.0.0" } }, "number-is-nan": { @@ -7001,9 +7001,9 @@ "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", "dev": true, "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" + "copy-descriptor": "0.1.1", + "define-property": "0.2.5", + "kind-of": "3.2.2" }, "dependencies": { "define-property": { @@ -7012,7 +7012,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "kind-of": { @@ -7021,7 +7021,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -7038,7 +7038,7 @@ "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", "dev": true, "requires": { - "isobject": "^3.0.0" + "isobject": "3.0.1" } }, "object.assign": { @@ -7047,10 +7047,10 @@ "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" + "define-properties": "1.1.2", + "function-bind": "1.1.1", + "has-symbols": "1.0.0", + "object-keys": "1.0.11" } }, "object.defaults": { @@ -7059,10 +7059,10 @@ "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", "dev": true, "requires": { - "array-each": "^1.0.1", - "array-slice": "^1.0.0", - "for-own": "^1.0.0", - "isobject": "^3.0.0" + "array-each": "1.0.1", + "array-slice": "1.1.0", + "for-own": "1.0.0", + "isobject": "3.0.1" } }, "object.map": { @@ -7071,8 +7071,8 @@ "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", "dev": true, "requires": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" + "for-own": "1.0.0", + "make-iterator": "1.0.1" } }, "object.omit": { @@ -7081,8 +7081,8 @@ "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", "dev": true, "requires": { - "for-own": "^0.1.4", - "is-extendable": "^0.1.1" + "for-own": "0.1.5", + "is-extendable": "0.1.1" }, "dependencies": { "for-own": { @@ -7091,7 +7091,7 @@ "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", "dev": true, "requires": { - "for-in": "^1.0.1" + "for-in": "1.0.2" } } } @@ -7102,7 +7102,7 @@ "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "dev": true, "requires": { - "isobject": "^3.0.1" + "isobject": "3.0.1" } }, "once": { @@ -7110,7 +7110,7 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { - "wrappy": "1" + "wrappy": "1.0.2" } }, "opn": { @@ -7118,7 +7118,7 @@ "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", "requires": { - "is-wsl": "^1.1.0" + "is-wsl": "1.1.0" } }, "optimist": { @@ -7127,8 +7127,8 @@ "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", "dev": true, "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" + "minimist": "0.0.10", + "wordwrap": "0.0.3" }, "dependencies": { "minimist": { @@ -7151,12 +7151,12 @@ "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", "dev": true, "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "wordwrap": "~1.0.0" + "deep-is": "0.1.3", + "fast-levenshtein": "2.0.6", + "levn": "0.3.0", + "prelude-ls": "1.1.2", + "type-check": "0.3.2", + "wordwrap": "1.0.0" } }, "orchestrator": { @@ -7165,9 +7165,9 @@ "integrity": "sha1-FOfp4nZPcxX7rBhOUGx6pt+UrX4=", "dev": true, "requires": { - "end-of-stream": "~0.1.5", - "sequencify": "~0.0.7", - "stream-consume": "~0.1.0" + "end-of-stream": "0.1.5", + "sequencify": "0.0.7", + "stream-consume": "0.1.1" } }, "ordered-read-streams": { @@ -7199,7 +7199,7 @@ "integrity": "sha1-jmtPT2XHK8W2/ii3XtqHT5akoIU=", "dev": true, "requires": { - "p-timeout": "^1.1.1" + "p-timeout": "1.2.1" }, "dependencies": { "p-timeout": { @@ -7208,7 +7208,7 @@ "integrity": "sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=", "dev": true, "requires": { - "p-finally": "^1.0.0" + "p-finally": "1.0.0" } } } @@ -7237,7 +7237,7 @@ "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", "dev": true, "requires": { - "p-finally": "^1.0.0" + "p-finally": "1.0.0" } }, "parse-filepath": { @@ -7246,9 +7246,9 @@ "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", "dev": true, "requires": { - "is-absolute": "^1.0.0", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" + "is-absolute": "1.0.0", + "map-cache": "0.2.2", + "path-root": "0.1.1" } }, "parse-glob": { @@ -7257,10 +7257,10 @@ "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", "dev": true, "requires": { - "glob-base": "^0.3.0", - "is-dotfile": "^1.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.0" + "glob-base": "0.3.0", + "is-dotfile": "1.0.3", + "is-extglob": "1.0.0", + "is-glob": "2.0.1" }, "dependencies": { "is-extglob": { @@ -7275,7 +7275,7 @@ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "is-extglob": "1.0.0" } } } @@ -7286,7 +7286,7 @@ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { - "error-ex": "^1.2.0" + "error-ex": "1.3.1" } }, "parse-passwd": { @@ -7313,7 +7313,7 @@ "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "dev": true, "requires": { - "pinkie-promise": "^2.0.0" + "pinkie-promise": "2.0.1" } }, "path-is-absolute": { @@ -7345,7 +7345,7 @@ "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", "dev": true, "requires": { - "path-root-regex": "^0.1.0" + "path-root-regex": "0.1.2" } }, "path-root-regex": { @@ -7377,9 +7377,9 @@ "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "graceful-fs": "4.1.11", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" }, "dependencies": { "pify": { @@ -7402,7 +7402,7 @@ "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", "dev": true, "requires": { - "through": "~2.3" + "through": "2.3.8" } }, "pend": { @@ -7439,7 +7439,7 @@ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "dev": true, "requires": { - "pinkie": "^2.0.0" + "pinkie": "2.0.4" } }, "plugin-error": { @@ -7448,11 +7448,11 @@ "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", "dev": true, "requires": { - "ansi-cyan": "^0.1.1", - "ansi-red": "^0.1.1", - "arr-diff": "^1.0.1", - "arr-union": "^2.0.1", - "extend-shallow": "^1.1.2" + "ansi-cyan": "0.1.1", + "ansi-red": "0.1.1", + "arr-diff": "1.1.0", + "arr-union": "2.1.0", + "extend-shallow": "1.1.4" }, "dependencies": { "arr-diff": { @@ -7461,8 +7461,8 @@ "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", "dev": true, "requires": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" + "arr-flatten": "1.1.0", + "array-slice": "0.2.3" } }, "arr-union": { @@ -7483,7 +7483,7 @@ "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", "dev": true, "requires": { - "kind-of": "^1.1.0" + "kind-of": "1.1.0" } }, "kind-of": { @@ -7554,8 +7554,8 @@ "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", "dev": true, "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "end-of-stream": "1.4.1", + "once": "1.4.0" }, "dependencies": { "end-of-stream": { @@ -7564,7 +7564,7 @@ "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "dev": true, "requires": { - "once": "^1.4.0" + "once": "1.4.0" } } } @@ -7575,9 +7575,9 @@ "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", "dev": true, "requires": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" + "duplexify": "3.6.0", + "inherits": "2.0.3", + "pump": "2.0.1" } }, "punycode": { @@ -7602,9 +7602,9 @@ "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", "dev": true, "requires": { - "decode-uri-component": "^0.2.0", - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" + "decode-uri-component": "0.2.0", + "object-assign": "4.1.1", + "strict-uri-encode": "1.1.0" } }, "querystringify": { @@ -7619,7 +7619,7 @@ "integrity": "sha1-bEnQHwCeIlZ4h4nyv/rGuLmZBYU=", "dev": true, "requires": { - "inherits": "~2.0.0" + "inherits": "2.0.3" } }, "randomatic": { @@ -7628,9 +7628,9 @@ "integrity": "sha512-VdxFOIEY3mNO5PtSRkkle/hPJDHvQhK21oa73K4yAc9qmp6N429gAyF1gZMOTMeS0/AYzaV/2Trcef+NaIonSA==", "dev": true, "requires": { - "is-number": "^4.0.0", - "kind-of": "^6.0.0", - "math-random": "^1.0.1" + "is-number": "4.0.0", + "kind-of": "6.0.2", + "math-random": "1.0.1" }, "dependencies": { "is-number": { @@ -7647,9 +7647,9 @@ "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", "dev": true, "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" + "load-json-file": "1.1.0", + "normalize-package-data": "2.4.0", + "path-type": "1.1.0" } }, "read-pkg-up": { @@ -7658,8 +7658,8 @@ "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", "dev": true, "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" + "find-up": "1.1.2", + "read-pkg": "1.1.0" } }, "readable-stream": { @@ -7668,12 +7668,12 @@ "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "string_decoder": "~0.10.x", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" } }, "readdirp": { @@ -7682,10 +7682,10 @@ "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "minimatch": "^3.0.2", - "readable-stream": "^2.0.2", - "set-immediate-shim": "^1.0.1" + "graceful-fs": "4.1.11", + "minimatch": "3.0.4", + "readable-stream": "2.0.6", + "set-immediate-shim": "1.0.1" } }, "rechoir": { @@ -7694,7 +7694,7 @@ "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", "dev": true, "requires": { - "resolve": "^1.1.6" + "resolve": "1.7.1" } }, "redent": { @@ -7703,8 +7703,8 @@ "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", "dev": true, "requires": { - "indent-string": "^2.1.0", - "strip-indent": "^1.0.1" + "indent-string": "2.1.0", + "strip-indent": "1.0.1" } }, "reflect-metadata": { @@ -7718,7 +7718,7 @@ "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", "dev": true, "requires": { - "is-equal-shallow": "^0.1.3" + "is-equal-shallow": "0.1.3" } }, "regex-not": { @@ -7727,8 +7727,8 @@ "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", "dev": true, "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" + "extend-shallow": "3.0.2", + "safe-regex": "1.1.0" } }, "relative": { @@ -7737,7 +7737,7 @@ "integrity": "sha1-Dc2OxUpdNaPBXhBFA9ZTdbWlNn8=", "dev": true, "requires": { - "isobject": "^2.0.0" + "isobject": "2.1.0" }, "dependencies": { "isobject": { @@ -7757,11 +7757,11 @@ "integrity": "sha512-gsNQXs5kJLhErICSyYhzVZ++C8LBW8dgwr874Y2QvzAUS75zBlD/juZgXs39nbYJ09fZDlX2AVLVJAY2jbFJoQ==", "dev": true, "requires": { - "amdefine": "^1.0.0", + "amdefine": "1.0.1", "istanbul": "0.4.5", - "minimatch": "^3.0.3", - "plugin-error": "^0.1.2", - "source-map": "^0.6.1", + "minimatch": "3.0.4", + "plugin-error": "0.1.2", + "source-map": "0.6.1", "through2": "2.0.1" }, "dependencies": { @@ -7777,8 +7777,8 @@ "integrity": "sha1-OE51MU1J8y3hLuu4E2uOtrXVnak=", "dev": true, "requires": { - "readable-stream": "~2.0.0", - "xtend": "~4.0.0" + "readable-stream": "2.0.6", + "xtend": "4.0.1" } } } @@ -7789,8 +7789,8 @@ "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", "dev": true, "requires": { - "is-buffer": "^1.1.5", - "is-utf8": "^0.2.1" + "is-buffer": "1.1.6", + "is-utf8": "0.2.1" } }, "remove-bom-stream": { @@ -7799,9 +7799,9 @@ "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", "dev": true, "requires": { - "remove-bom-buffer": "^3.0.0", - "safe-buffer": "^5.1.0", - "through2": "^2.0.3" + "remove-bom-buffer": "3.0.0", + "safe-buffer": "5.1.2", + "through2": "2.0.3" } }, "remove-trailing-separator": { @@ -7828,7 +7828,7 @@ "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "dev": true, "requires": { - "is-finite": "^1.0.0" + "is-finite": "1.0.2" } }, "replace-ext": { @@ -7842,28 +7842,28 @@ "resolved": "https://registry.npmjs.org/request/-/request-2.85.0.tgz", "integrity": "sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==", "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.6.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.1", - "forever-agent": "~0.6.1", - "form-data": "~2.3.1", - "har-validator": "~5.0.3", - "hawk": "~6.0.2", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.17", - "oauth-sign": "~0.8.2", - "performance-now": "^2.1.0", - "qs": "~6.5.1", - "safe-buffer": "^5.1.1", - "stringstream": "~0.0.5", - "tough-cookie": "~2.3.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.1.0" + "aws-sign2": "0.7.0", + "aws4": "1.7.0", + "caseless": "0.12.0", + "combined-stream": "1.0.6", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.3.2", + "har-validator": "5.0.3", + "hawk": "6.0.2", + "http-signature": "1.2.0", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.18", + "oauth-sign": "0.8.2", + "performance-now": "2.1.0", + "qs": "6.5.2", + "safe-buffer": "5.1.2", + "stringstream": "0.0.6", + "tough-cookie": "2.3.4", + "tunnel-agent": "0.6.0", + "uuid": "3.2.1" } }, "request-progress": { @@ -7871,7 +7871,7 @@ "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", "integrity": "sha1-TKdUCBx/7GP1BeT6qCWqBs1mnb4=", "requires": { - "throttleit": "^1.0.0" + "throttleit": "1.0.0" } }, "requires-port": { @@ -7886,7 +7886,7 @@ "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==", "dev": true, "requires": { - "path-parse": "^1.0.5" + "path-parse": "1.0.5" } }, "resolve-dir": { @@ -7895,8 +7895,8 @@ "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", "dev": true, "requires": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" + "expand-tilde": "2.0.2", + "global-modules": "1.0.0" } }, "resolve-options": { @@ -7905,7 +7905,7 @@ "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", "dev": true, "requires": { - "value-or-function": "^3.0.0" + "value-or-function": "3.0.0" } }, "resolve-url": { @@ -7920,7 +7920,7 @@ "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", "dev": true, "requires": { - "lowercase-keys": "^1.0.0" + "lowercase-keys": "1.0.1" } }, "ret": { @@ -7942,7 +7942,7 @@ "dev": true, "optional": true, "requires": { - "align-text": "^0.1.1" + "align-text": "0.1.4" } }, "rimraf": { @@ -7951,7 +7951,7 @@ "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "dev": true, "requires": { - "glob": "^7.0.5" + "glob": "7.1.2" } }, "rxjs": { @@ -7973,7 +7973,7 @@ "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "requires": { - "ret": "~0.1.10" + "ret": "0.1.15" } }, "safer-buffer": { @@ -7998,7 +7998,7 @@ "integrity": "sha1-z+kXyz0nS8/6x5J1ivUxc+sfq9w=", "dev": true, "requires": { - "commander": "~2.8.1" + "commander": "2.8.1" }, "dependencies": { "commander": { @@ -8007,7 +8007,7 @@ "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", "dev": true, "requires": { - "graceful-readlink": ">= 1.0.0" + "graceful-readlink": "1.0.1" } } } @@ -8035,10 +8035,10 @@ "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "split-string": "3.1.0" }, "dependencies": { "extend-shallow": { @@ -8047,7 +8047,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -8058,7 +8058,7 @@ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { - "shebang-regex": "^1.0.0" + "shebang-regex": "1.0.0" } }, "shebang-regex": { @@ -8091,13 +8091,13 @@ "integrity": "sha512-trdx+mB0VBBgoYucy6a9L7/jfQOmvGeaKZT4OOJ+lPAtI8623xyGr8wLiE4eojzBS8G9yXbhx42GHUOVLr4X2w==", "dev": true, "requires": { - "@sinonjs/formatio": "^2.0.0", - "diff": "^3.1.0", - "lodash.get": "^4.4.2", - "lolex": "^2.2.0", - "nise": "^1.2.0", - "supports-color": "^5.1.0", - "type-detect": "^4.0.5" + "@sinonjs/formatio": "2.0.0", + "diff": "3.5.0", + "lodash.get": "4.4.2", + "lolex": "2.7.0", + "nise": "1.3.3", + "supports-color": "5.4.0", + "type-detect": "4.0.8" }, "dependencies": { "has-flag": { @@ -8112,7 +8112,7 @@ "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } @@ -8129,14 +8129,14 @@ "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", "dev": true, "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" + "base": "0.11.2", + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "map-cache": "0.2.2", + "source-map": "0.5.7", + "source-map-resolve": "0.5.2", + "use": "3.1.0" }, "dependencies": { "define-property": { @@ -8145,7 +8145,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "extend-shallow": { @@ -8154,7 +8154,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -8165,9 +8165,9 @@ "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", "dev": true, "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" + "define-property": "1.0.0", + "isobject": "3.0.1", + "snapdragon-util": "3.0.1" }, "dependencies": { "define-property": { @@ -8176,7 +8176,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "is-accessor-descriptor": { @@ -8185,7 +8185,7 @@ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-data-descriptor": { @@ -8194,7 +8194,7 @@ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "6.0.2" } }, "is-descriptor": { @@ -8203,9 +8203,9 @@ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" } } } @@ -8216,7 +8216,7 @@ "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", "dev": true, "requires": { - "kind-of": "^3.2.0" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -8225,7 +8225,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -8235,7 +8235,7 @@ "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", "requires": { - "hoek": "4.x.x" + "hoek": "4.2.1" } }, "sort-keys": { @@ -8244,7 +8244,7 @@ "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", "dev": true, "requires": { - "is-plain-obj": "^1.0.0" + "is-plain-obj": "1.1.0" } }, "sort-keys-length": { @@ -8253,7 +8253,7 @@ "integrity": "sha1-nLb09OnkgVWmqgZx7dM2/xR5oYg=", "dev": true, "requires": { - "sort-keys": "^1.0.0" + "sort-keys": "1.1.2" } }, "source-map": { @@ -8268,11 +8268,11 @@ "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", "dev": true, "requires": { - "atob": "^2.1.1", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" + "atob": "2.1.1", + "decode-uri-component": "0.2.0", + "resolve-url": "0.2.1", + "source-map-url": "0.4.0", + "urix": "0.1.0" } }, "source-map-support": { @@ -8281,8 +8281,8 @@ "integrity": "sha512-N4KXEz7jcKqPf2b2vZF11lQIz9W5ZMuUcIOGj243lduidkf2fjkVKJS9vNxVWn3u/uxX38AcE8U9nnH9FPcq+g==", "dev": true, "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" + "buffer-from": "1.0.0", + "source-map": "0.6.1" }, "dependencies": { "source-map": { @@ -8311,8 +8311,8 @@ "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", "dev": true, "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" + "spdx-expression-parse": "3.0.0", + "spdx-license-ids": "3.0.0" } }, "spdx-exceptions": { @@ -8327,8 +8327,8 @@ "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", "dev": true, "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "spdx-exceptions": "2.1.0", + "spdx-license-ids": "3.0.0" } }, "spdx-license-ids": { @@ -8343,7 +8343,7 @@ "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", "dev": true, "requires": { - "through": "2" + "through": "2.3.8" } }, "split-string": { @@ -8352,7 +8352,7 @@ "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", "dev": true, "requires": { - "extend-shallow": "^3.0.0" + "extend-shallow": "3.0.2" } }, "sprintf-js": { @@ -8366,14 +8366,14 @@ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "tweetnacl": "~0.14.0" + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" } }, "stat-mode": { @@ -8388,8 +8388,8 @@ "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", "dev": true, "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" + "define-property": "0.2.5", + "object-copy": "0.1.0" }, "dependencies": { "define-property": { @@ -8398,7 +8398,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } } } @@ -8409,7 +8409,7 @@ "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", "dev": true, "requires": { - "duplexer": "~0.1.1" + "duplexer": "0.1.1" } }, "stream-consume": { @@ -8430,7 +8430,7 @@ "integrity": "sha512-Gk6KZM+yNA1JpW0KzlZIhjo3EaBJDkYfXtYSbOwNIQ7Zd6006E6+sCFlW1NDvFG/vnXhKmw6TJJgiEQg/8lXfQ==", "dev": true, "requires": { - "readable-stream": "^2.0.2" + "readable-stream": "2.0.6" } }, "streamifier": { @@ -8462,7 +8462,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "strip-bom": { @@ -8471,8 +8471,8 @@ "integrity": "sha1-hbiGLzhEtabV7IRnqTWYFzo295Q=", "dev": true, "requires": { - "first-chunk-stream": "^1.0.0", - "is-utf8": "^0.2.0" + "first-chunk-stream": "1.0.0", + "is-utf8": "0.2.1" } }, "strip-bom-stream": { @@ -8481,8 +8481,8 @@ "integrity": "sha1-+H217yYT9paKpUWr/h7HKLaoKco=", "dev": true, "requires": { - "first-chunk-stream": "^2.0.0", - "strip-bom": "^2.0.0" + "first-chunk-stream": "2.0.0", + "strip-bom": "2.0.0" }, "dependencies": { "first-chunk-stream": { @@ -8491,7 +8491,7 @@ "integrity": "sha1-G97NuOCDwGZLkZRVgVd6Q6nzHXA=", "dev": true, "requires": { - "readable-stream": "^2.0.2" + "readable-stream": "2.0.6" } }, "strip-bom": { @@ -8500,7 +8500,7 @@ "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, "requires": { - "is-utf8": "^0.2.0" + "is-utf8": "0.2.1" } } } @@ -8517,7 +8517,7 @@ "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", "dev": true, "requires": { - "is-natural-number": "^4.0.1" + "is-natural-number": "4.0.1" } }, "strip-indent": { @@ -8526,7 +8526,7 @@ "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", "dev": true, "requires": { - "get-stdin": "^4.0.1" + "get-stdin": "4.0.1" } }, "strip-outer": { @@ -8535,7 +8535,7 @@ "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", "dev": true, "requires": { - "escape-string-regexp": "^1.0.2" + "escape-string-regexp": "1.0.5" } }, "sudo-prompt": { @@ -8555,13 +8555,13 @@ "integrity": "sha1-n1dyQTlSE1xv779Ar+ak+qiLS7U=", "dev": true, "requires": { - "coa": "~1.0.1", - "colors": "~1.1.2", - "csso": "~2.3.1", - "js-yaml": "~3.7.0", - "mkdirp": "~0.5.1", - "sax": "~1.2.1", - "whet.extend": "~0.9.9" + "coa": "1.0.4", + "colors": "1.1.2", + "csso": "2.3.2", + "js-yaml": "3.7.0", + "mkdirp": "0.5.1", + "sax": "1.2.4", + "whet.extend": "0.9.9" }, "dependencies": { "colors": { @@ -8576,8 +8576,8 @@ "integrity": "sha1-3dUsWHAz9J6Utx/FVWnyUuj/X4U=", "dev": true, "requires": { - "clap": "^1.0.9", - "source-map": "^0.5.3" + "clap": "1.2.3", + "source-map": "0.5.7" } }, "js-yaml": { @@ -8586,8 +8586,8 @@ "integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=", "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^2.6.0" + "argparse": "1.0.10", + "esprima": "2.7.3" } } } @@ -8603,9 +8603,9 @@ "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", "dev": true, "requires": { - "block-stream": "*", - "fstream": "^1.0.2", - "inherits": "2" + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" } }, "tar-stream": { @@ -8614,13 +8614,13 @@ "integrity": "sha512-IFLM5wp3QrJODQFPm6/to3LJZrONdBY/otxcvDIQzu217zKye6yVR3hhi9lAjrC2Z+m/j5oDxMPb1qcd8cIvpA==", "dev": true, "requires": { - "bl": "^1.0.0", - "buffer-alloc": "^1.1.0", - "end-of-stream": "^1.0.0", - "fs-constants": "^1.0.0", - "readable-stream": "^2.3.0", - "to-buffer": "^1.1.0", - "xtend": "^4.0.0" + "bl": "1.2.2", + "buffer-alloc": "1.2.0", + "end-of-stream": "1.4.1", + "fs-constants": "1.0.0", + "readable-stream": "2.3.6", + "to-buffer": "1.1.1", + "xtend": "4.0.1" }, "dependencies": { "end-of-stream": { @@ -8629,7 +8629,7 @@ "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "dev": true, "requires": { - "once": "^1.4.0" + "once": "1.4.0" } }, "process-nextick-args": { @@ -8644,13 +8644,13 @@ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -8659,7 +8659,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.2" } } } @@ -8687,8 +8687,8 @@ "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", "dev": true, "requires": { - "readable-stream": "^2.1.5", - "xtend": "~4.0.1" + "readable-stream": "2.3.6", + "xtend": "4.0.1" }, "dependencies": { "process-nextick-args": { @@ -8703,13 +8703,13 @@ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -8718,7 +8718,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.2" } } } @@ -8729,8 +8729,8 @@ "integrity": "sha1-YLxVoNrLdghdsfna6Zq0P4PWIuw=", "dev": true, "requires": { - "through2": "~2.0.0", - "xtend": "~4.0.0" + "through2": "2.0.3", + "xtend": "4.0.1" } }, "tildify": { @@ -8739,7 +8739,7 @@ "integrity": "sha1-3OwD9V3Km3qj5bBPIYF+tW5jWIo=", "dev": true, "requires": { - "os-homedir": "^1.0.0" + "os-homedir": "1.0.2" } }, "time-stamp": { @@ -8760,8 +8760,8 @@ "integrity": "sha512-tsEStd7kmACHENhsUPaxb8Jf8/+GZZxyNFQbZD07HQOyooOa6At1rQqjffgvg7n+dxscQa9cjjMdWhJtsP2sxg==", "dev": true, "requires": { - "es5-ext": "~0.10.14", - "next-tick": "1" + "es5-ext": "0.10.43", + "next-tick": "1.0.0" } }, "tmp": { @@ -8769,7 +8769,7 @@ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", "integrity": "sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA=", "requires": { - "os-tmpdir": "~1.0.1" + "os-tmpdir": "1.0.2" } }, "to-absolute-glob": { @@ -8778,8 +8778,8 @@ "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", "dev": true, "requires": { - "is-absolute": "^1.0.0", - "is-negated-glob": "^1.0.0" + "is-absolute": "1.0.0", + "is-negated-glob": "1.0.0" } }, "to-buffer": { @@ -8794,7 +8794,7 @@ "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { "kind-of": { @@ -8803,7 +8803,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" } } } @@ -8814,10 +8814,10 @@ "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", "dev": true, "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "regex-not": "1.0.2", + "safe-regex": "1.1.0" } }, "to-regex-range": { @@ -8826,8 +8826,8 @@ "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "is-number": "3.0.0", + "repeat-string": "1.6.1" } }, "to-through": { @@ -8836,7 +8836,7 @@ "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", "dev": true, "requires": { - "through2": "^2.0.3" + "through2": "2.0.3" } }, "tough-cookie": { @@ -8844,7 +8844,7 @@ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", "requires": { - "punycode": "^1.4.1" + "punycode": "1.4.1" } }, "tree-kill": { @@ -8864,7 +8864,7 @@ "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", "dev": true, "requires": { - "escape-string-regexp": "^1.0.2" + "escape-string-regexp": "1.0.5" } }, "tslib": { @@ -8879,18 +8879,18 @@ "integrity": "sha1-EeJrzLiK+gLdDZlWyuPUVAtfVMM=", "dev": true, "requires": { - "babel-code-frame": "^6.22.0", - "builtin-modules": "^1.1.1", - "chalk": "^2.3.0", - "commander": "^2.12.1", - "diff": "^3.2.0", - "glob": "^7.1.1", - "js-yaml": "^3.7.0", - "minimatch": "^3.0.4", - "resolve": "^1.3.2", - "semver": "^5.3.0", - "tslib": "^1.8.0", - "tsutils": "^2.12.1" + "babel-code-frame": "6.26.0", + "builtin-modules": "1.1.1", + "chalk": "2.4.1", + "commander": "2.15.1", + "diff": "3.5.0", + "glob": "7.1.2", + "js-yaml": "3.11.0", + "minimatch": "3.0.4", + "resolve": "1.7.1", + "semver": "5.5.0", + "tslib": "1.9.1", + "tsutils": "2.27.1" }, "dependencies": { "ansi-styles": { @@ -8899,7 +8899,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "1.9.1" } }, "chalk": { @@ -8908,9 +8908,9 @@ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.4.0" } }, "has-flag": { @@ -8925,7 +8925,7 @@ "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } @@ -8953,7 +8953,7 @@ "integrity": "sha1-AWAXNymzvxOGKN0UoVN+AIUdgUo=", "dev": true, "requires": { - "tslib": "^1.7.1" + "tslib": "1.9.0" } } } @@ -8964,7 +8964,7 @@ "integrity": "sha512-5AnfTGlfpUzpRHLmoojPBKFTTmbjnwgdaTHMdllausa4GBPya5u36i9ddrTX4PhetGZvd4JUYIpAmgHqVnsctg==", "dev": true, "requires": { - "tsutils": "^2.12.1" + "tsutils": "2.27.1" } }, "tsutils": { @@ -8973,7 +8973,7 @@ "integrity": "sha512-AE/7uzp32MmaHvNNFES85hhUDHFdFZp6OAiZcd6y4ZKKIg6orJTm8keYWBhIhrJQH3a4LzNKat7ZPXZt5aTf6w==", "dev": true, "requires": { - "tslib": "^1.8.1" + "tslib": "1.9.1" } }, "tunnel-agent": { @@ -8981,7 +8981,7 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "requires": { - "safe-buffer": "^5.0.1" + "safe-buffer": "5.1.2" } }, "tweetnacl": { @@ -8996,7 +8996,7 @@ "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", "dev": true, "requires": { - "prelude-ls": "~1.1.2" + "prelude-ls": "1.1.2" } }, "type-detect": { @@ -9011,9 +9011,9 @@ "integrity": "sha512-DtRNLb7x8yCTv/KHlwes+NI+aGb4Vl1iPC63Hhtcvk1DpxSAZzKWQv0RQFY0jX2Uqj0SDBNl8Na4e6MV6TNDgw==", "dev": true, "requires": { - "circular-json": "^0.3.1", - "lodash": "^4.17.4", - "postinstall-build": "^5.0.1" + "circular-json": "0.3.3", + "lodash": "4.17.5", + "postinstall-build": "5.0.1" } }, "typescript": { @@ -9033,8 +9033,8 @@ "integrity": "sha512-A16UqkHtkQOF340cf21LJXchcftyBTPqNOAmP1J8Plu2m3Q8o+2fAYwgFjLXMKP6ooSPjDoOS6z8j9q+1nEnXg==", "dev": true, "requires": { - "commandpost": "^1.0.0", - "editorconfig": "^0.15.0" + "commandpost": "1.3.0", + "editorconfig": "0.15.0" }, "dependencies": { "editorconfig": { @@ -9043,12 +9043,12 @@ "integrity": "sha512-j7JBoj/bpNzvoTQylfRZSc85MlLNKWQiq5y6gwKhmqD2h1eZ+tH4AXbkhEJD468gjDna/XMx2YtSkCxBRX9OGg==", "dev": true, "requires": { - "@types/commander": "^2.11.0", - "@types/semver": "^5.4.0", - "commander": "^2.11.0", - "lru-cache": "^4.1.1", - "semver": "^5.4.1", - "sigmund": "^1.0.1" + "@types/commander": "2.12.2", + "@types/semver": "5.5.0", + "commander": "2.15.1", + "lru-cache": "4.1.3", + "semver": "5.5.0", + "sigmund": "1.0.1" } }, "lru-cache": { @@ -9057,8 +9057,8 @@ "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", "dev": true, "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "pseudomap": "1.0.2", + "yallist": "2.1.2" } } } @@ -9070,9 +9070,9 @@ "dev": true, "optional": true, "requires": { - "source-map": "~0.5.1", - "uglify-to-browserify": "~1.0.0", - "yargs": "~3.10.0" + "source-map": "0.5.7", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" } }, "uglify-to-browserify": { @@ -9093,8 +9093,8 @@ "integrity": "sha512-izD3jxT8xkzwtXRUZjtmRwKnZoeECrfZ8ra/ketwOcusbZEp4mjULMnJOCfTDZBgGQAAY1AJ/IgxcwkavcX9Og==", "dev": true, "requires": { - "buffer": "^3.0.1", - "through": "^2.3.6" + "buffer": "3.6.0", + "through": "2.3.8" } }, "unc-path-regex": { @@ -9120,10 +9120,10 @@ "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", "dev": true, "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^0.4.3" + "arr-union": "3.1.0", + "get-value": "2.0.6", + "is-extendable": "0.1.1", + "set-value": "0.4.3" }, "dependencies": { "extend-shallow": { @@ -9132,7 +9132,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } }, "set-value": { @@ -9141,10 +9141,10 @@ "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.1", - "to-object-path": "^0.3.0" + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "to-object-path": "0.3.0" } } } @@ -9166,8 +9166,8 @@ "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", "dev": true, "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" + "has-value": "0.3.1", + "isobject": "3.0.1" }, "dependencies": { "has-value": { @@ -9176,9 +9176,9 @@ "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", "dev": true, "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" + "get-value": "2.0.6", + "has-values": "0.1.4", + "isobject": "2.1.0" }, "dependencies": { "isobject": { @@ -9223,8 +9223,8 @@ "integrity": "sha512-ERuGxDiQ6Xw/agN4tuoCRbmwRuZP0cJ1lJxJubXr5Q/5cDa78+Dc4wfvtxzhzhkm5VvmW6Mf8EVj9SPGN4l8Lg==", "dev": true, "requires": { - "querystringify": "^2.0.0", - "requires-port": "^1.0.0" + "querystringify": "2.0.0", + "requires-port": "1.0.0" } }, "url-parse-lax": { @@ -9233,7 +9233,7 @@ "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", "dev": true, "requires": { - "prepend-http": "^2.0.0" + "prepend-http": "2.0.0" } }, "url-to-options": { @@ -9254,7 +9254,7 @@ "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", "dev": true, "requires": { - "kind-of": "^6.0.2" + "kind-of": "6.0.2" } }, "user-home": { @@ -9280,7 +9280,7 @@ "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", "dev": true, "requires": { - "user-home": "^1.1.1" + "user-home": "1.1.1" } }, "vali-date": { @@ -9295,8 +9295,8 @@ "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", "dev": true, "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "spdx-correct": "3.0.0", + "spdx-expression-parse": "3.0.0" } }, "validator": { @@ -9316,9 +9316,9 @@ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "requires": { - "assert-plus": "^1.0.0", + "assert-plus": "1.0.0", "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" + "extsprintf": "1.3.0" } }, "vinyl": { @@ -9327,8 +9327,8 @@ "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", "dev": true, "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", + "clone": "1.0.4", + "clone-stats": "0.0.1", "replace-ext": "0.0.1" } }, @@ -9338,12 +9338,12 @@ "integrity": "sha1-p+v1/779obfRjRQPyweyI++2dRo=", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.3.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0", - "strip-bom-stream": "^2.0.0", - "vinyl": "^1.1.0" + "graceful-fs": "4.1.11", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "strip-bom": "2.0.0", + "strip-bom-stream": "2.0.0", + "vinyl": "1.2.0" }, "dependencies": { "pify": { @@ -9358,7 +9358,7 @@ "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, "requires": { - "is-utf8": "^0.2.0" + "is-utf8": "0.2.1" } }, "vinyl": { @@ -9367,8 +9367,8 @@ "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", + "clone": "1.0.4", + "clone-stats": "0.0.1", "replace-ext": "0.0.1" } } @@ -9380,14 +9380,14 @@ "integrity": "sha1-mmhRzhysHBzqX+hsCTHWIMLPqeY=", "dev": true, "requires": { - "defaults": "^1.0.0", - "glob-stream": "^3.1.5", - "glob-watcher": "^0.0.6", - "graceful-fs": "^3.0.0", - "mkdirp": "^0.5.0", - "strip-bom": "^1.0.0", - "through2": "^0.6.1", - "vinyl": "^0.4.0" + "defaults": "1.0.3", + "glob-stream": "3.1.18", + "glob-watcher": "0.0.6", + "graceful-fs": "3.0.11", + "mkdirp": "0.5.1", + "strip-bom": "1.0.0", + "through2": "0.6.5", + "vinyl": "0.4.6" }, "dependencies": { "clone": { @@ -9402,7 +9402,7 @@ "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", "dev": true, "requires": { - "natives": "^1.1.0" + "natives": "1.1.4" } }, "isarray": { @@ -9417,10 +9417,10 @@ "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", + "core-util-is": "1.0.2", + "inherits": "2.0.3", "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "string_decoder": "0.10.31" } }, "through2": { @@ -9429,8 +9429,8 @@ "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, "requires": { - "readable-stream": ">=1.0.33-1 <1.1.0-0", - "xtend": ">=4.0.0 <4.1.0-0" + "readable-stream": "1.0.34", + "xtend": "4.0.1" } }, "vinyl": { @@ -9439,8 +9439,8 @@ "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", "dev": true, "requires": { - "clone": "^0.2.0", - "clone-stats": "^0.0.1" + "clone": "0.2.0", + "clone-stats": "0.0.1" } } } @@ -9451,8 +9451,8 @@ "integrity": "sha1-YrU6E1YQqJbpjKlr7jqH8Aio54A=", "dev": true, "requires": { - "through2": "^2.0.3", - "vinyl": "^0.4.3" + "through2": "2.0.3", + "vinyl": "0.4.6" }, "dependencies": { "clone": { @@ -9467,8 +9467,8 @@ "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", "dev": true, "requires": { - "clone": "^0.2.0", - "clone-stats": "^0.0.1" + "clone": "0.2.0", + "clone-stats": "0.0.1" } } } @@ -9479,13 +9479,13 @@ "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", "dev": true, "requires": { - "append-buffer": "^1.0.2", - "convert-source-map": "^1.5.0", - "graceful-fs": "^4.1.6", - "normalize-path": "^2.1.1", - "now-and-later": "^2.0.0", - "remove-bom-buffer": "^3.0.0", - "vinyl": "^2.0.0" + "append-buffer": "1.0.2", + "convert-source-map": "1.5.1", + "graceful-fs": "4.1.11", + "normalize-path": "2.1.1", + "now-and-later": "2.0.0", + "remove-bom-buffer": "3.0.0", + "vinyl": "2.1.0" }, "dependencies": { "clone": { @@ -9512,12 +9512,12 @@ "integrity": "sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=", "dev": true, "requires": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" + "clone": "2.1.1", + "clone-buffer": "1.0.0", + "clone-stats": "1.0.0", + "cloneable-readable": "1.1.2", + "remove-trailing-separator": "1.1.0", + "replace-ext": "1.0.0" } } } @@ -9528,20 +9528,20 @@ "integrity": "sha512-SyDw4qFwZ+WthZX7RWp71PNiWLF7VhpM65j2oryY/6jtSORd8qH6J8vclwWZJ6Jvu0EH7JamO2RWNfBfsMR9Zw==", "dev": true, "requires": { - "glob": "^7.1.2", - "gulp-chmod": "^2.0.0", - "gulp-filter": "^5.0.1", + "glob": "7.1.2", + "gulp-chmod": "2.0.0", + "gulp-filter": "5.1.0", "gulp-gunzip": "1.0.0", - "gulp-remote-src-vscode": "^0.5.0", - "gulp-symdest": "^1.1.0", - "gulp-untar": "^0.0.7", - "gulp-vinyl-zip": "^2.1.0", - "mocha": "^4.0.1", - "request": "^2.83.0", - "semver": "^5.4.1", - "source-map-support": "^0.5.0", - "url-parse": "^1.1.9", - "vinyl-source-stream": "^1.1.0" + "gulp-remote-src-vscode": "0.5.0", + "gulp-symdest": "1.1.0", + "gulp-untar": "0.0.7", + "gulp-vinyl-zip": "2.1.0", + "mocha": "4.1.0", + "request": "2.85.0", + "semver": "5.5.0", + "source-map-support": "0.5.6", + "url-parse": "1.4.0", + "vinyl-source-stream": "1.1.2" }, "dependencies": { "browser-stdout": { @@ -9607,7 +9607,7 @@ "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", "dev": true, "requires": { - "has-flag": "^2.0.0" + "has-flag": "2.0.0" } } } @@ -9661,7 +9661,7 @@ "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-4.3.0.tgz", "integrity": "sha512-vDpsmYfYpfuyDKZ46pCJEFBOCNHepRBSmlBGA0fczEbYghYm059BiFo3SmT4MK1r8NvYrFEem4k5TYNW3wommg==", "requires": { - "vscode-languageserver-protocol": "^3.9.0" + "vscode-languageserver-protocol": "3.9.0" } }, "vscode-languageserver": { @@ -9669,8 +9669,8 @@ "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-4.3.0.tgz", "integrity": "sha512-4dTpnyTB6Q0HmMhxaG60rrpQthbTBlMtFX5cwJpPxcPzLZIFDWB3msR6TxGCzWpdYF11REIJihWByobpGkljdQ==", "requires": { - "vscode-languageserver-protocol": "^3.9.0", - "vscode-uri": "^1.0.3" + "vscode-languageserver-protocol": "3.9.0", + "vscode-uri": "1.0.5" }, "dependencies": { "vscode-uri": { @@ -9685,8 +9685,8 @@ "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.9.0.tgz", "integrity": "sha512-i1sG5iU88Mocc7egTeh6dAow/yRWpPK5PLJaxsWsKiA+dspq1Yzr/R1bNLPc+6P/ab010lXhzdUHQY0CuIUyDw==", "requires": { - "vscode-jsonrpc": "^3.6.2", - "vscode-languageserver-types": "^3.9.0" + "vscode-jsonrpc": "3.6.2", + "vscode-languageserver-types": "3.9.0" } }, "vscode-languageserver-types": { @@ -9711,7 +9711,7 @@ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { - "isexe": "^2.0.0" + "isexe": "2.0.0" } }, "window-size": { @@ -9748,8 +9748,8 @@ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", "requires": { - "sax": ">=0.6.0", - "xmlbuilder": "~9.0.1" + "sax": "1.2.4", + "xmlbuilder": "9.0.7" } }, "xmlbuilder": { @@ -9776,9 +9776,9 @@ "dev": true, "optional": true, "requires": { - "camelcase": "^1.0.2", - "cliui": "^2.1.0", - "decamelize": "^1.0.0", + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", "window-size": "0.1.0" }, "dependencies": { @@ -9797,8 +9797,8 @@ "integrity": "sha1-qBmB6nCleUYTOIPwKcWCGok1mn8=", "dev": true, "requires": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.0.1" + "buffer-crc32": "0.2.13", + "fd-slicer": "1.0.1" } }, "yazl": { @@ -9807,7 +9807,7 @@ "integrity": "sha1-7CblzIfVYBud+EMtvdPNLlFzoHE=", "dev": true, "requires": { - "buffer-crc32": "~0.2.3" + "buffer-crc32": "0.2.13" } }, "zone.js": { From 2591ad32c2baa08fa05b3904cf9736494e415111 Mon Sep 17 00:00:00 2001 From: Aman Agarwal Date: Thu, 30 Aug 2018 15:18:29 -0700 Subject: [PATCH 427/433] oops, mistake made in merge --- src/client/debugger/Common/Contracts.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/debugger/Common/Contracts.ts b/src/client/debugger/Common/Contracts.ts index adfdd0fee083..2445a1cffebb 100644 --- a/src/client/debugger/Common/Contracts.ts +++ b/src/client/debugger/Common/Contracts.ts @@ -25,14 +25,14 @@ export class TelemetryEvent extends OutputEvent { } } -export const DjangoApp = 'DJANGO'; export const VALID_DEBUG_OPTIONS = [ 'WaitOnAbnormalExit', 'WaitOnNormalExit', 'RedirectOutput', 'DebugStdLib', 'BreakOnSystemExitZero', - 'Django', + 'DjangoDebugging', + 'Django' ]; export enum DebugFlags { From 843a807166b473b7a797383e9b0c5dfea59e7cb8 Mon Sep 17 00:00:00 2001 From: Aman Agarwal Date: Thu, 30 Aug 2018 15:18:29 -0700 Subject: [PATCH 428/433] oops, mistake made in merge --- src/client/debugger/Main.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/debugger/Main.ts b/src/client/debugger/Main.ts index 7b3a9a3ab3b1..eb7904555598 100644 --- a/src/client/debugger/Main.ts +++ b/src/client/debugger/Main.ts @@ -335,12 +335,12 @@ export class PythonDebugger extends LoggingDebugSession { this.launchArgs.debugOptions.indexOf(DebugOptions.Django) >= 0) { isDjangoFile = filePath.toUpperCase().endsWith(".HTML"); } - if (this.attachArgs != null && + if (this.attachArgs !== null && Array.isArray(this.attachArgs.debugOptions) && this.attachArgs.debugOptions.indexOf(DebugOptions.DjangoDebugging) >= 0) { isDjangoFile = filePath.toUpperCase().endsWith(".HTML"); } - if (this.attachArgs != null && + if (this.attachArgs !== null && Array.isArray(this.attachArgs.debugOptions) && this.attachArgs.debugOptions.indexOf(DebugOptions.DjangoDebugging) >= 0) { isDjangoFile = filePath.toUpperCase().endsWith(".HTML"); From 652ff6ccd6d4e63fb214e3ed17102c380969adf5 Mon Sep 17 00:00:00 2001 From: Aman Agarwal Date: Thu, 13 Sep 2018 13:21:49 -0700 Subject: [PATCH 429/433] only send one launch/attach response --- src/client/debugger/Main.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client/debugger/Main.ts b/src/client/debugger/Main.ts index eb7904555598..c2c8cb28039b 100644 --- a/src/client/debugger/Main.ts +++ b/src/client/debugger/Main.ts @@ -166,6 +166,7 @@ export class PythonDebugger extends LoggingDebugSession { private onPythonProcessLoaded(pyThread?: IPythonThread) { if (this.entryResponse) { this.sendResponse(this.entryResponse); + this.entryResponse = undefined; } this.debuggerLoadedPromiseResolve(); if (this.launchArgs && !this.launchArgs.console) { From bcb0699fa2e9ed38fa7b52964276a070f9777e79 Mon Sep 17 00:00:00 2001 From: Aman Agarwal Date: Mon, 17 Sep 2018 16:36:03 -0700 Subject: [PATCH 430/433] fixed nit with single attach/launch response only --- src/client/debugger/Main.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/client/debugger/Main.ts b/src/client/debugger/Main.ts index c2c8cb28039b..50e221042f1f 100644 --- a/src/client/debugger/Main.ts +++ b/src/client/debugger/Main.ts @@ -333,17 +333,12 @@ export class PythonDebugger extends LoggingDebugSession { let isDjangoFile = false; if (this.launchArgs && Array.isArray(this.launchArgs.debugOptions) && - this.launchArgs.debugOptions.indexOf(DebugOptions.Django) >= 0) { + (this.launchArgs.debugOptions.indexOf(DebugOptions.Django) >= 0 || this.launchArgs.debugOptions.indexOf(DebugOptions.DjangoDebugging) >= 0)) { isDjangoFile = filePath.toUpperCase().endsWith(".HTML"); } - if (this.attachArgs !== null && + if (this.attachArgs && Array.isArray(this.attachArgs.debugOptions) && - this.attachArgs.debugOptions.indexOf(DebugOptions.DjangoDebugging) >= 0) { - isDjangoFile = filePath.toUpperCase().endsWith(".HTML"); - } - if (this.attachArgs !== null && - Array.isArray(this.attachArgs.debugOptions) && - this.attachArgs.debugOptions.indexOf(DebugOptions.DjangoDebugging) >= 0) { + (this.attachArgs.debugOptions.indexOf(DebugOptions.Django) >= 0 || this.attachArgs.debugOptions.indexOf(DebugOptions.DjangoDebugging) >= 0)) { isDjangoFile = filePath.toUpperCase().endsWith(".HTML"); } From 786d0ed1362bac84e8946a92a1fce5321803ee33 Mon Sep 17 00:00:00 2001 From: Aman Agarwal Date: Thu, 4 Oct 2018 16:40:53 -0700 Subject: [PATCH 431/433] Skip LazyImporter Modules --- pythonFiles/PythonTools/ptvsd/attach_server.py | 2 +- .../PythonTools/ptvsd/visualstudio_py_debugger.py | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/pythonFiles/PythonTools/ptvsd/attach_server.py b/pythonFiles/PythonTools/ptvsd/attach_server.py index 48dbda79b434..2330f9c512e3 100644 --- a/pythonFiles/PythonTools/ptvsd/attach_server.py +++ b/pythonFiles/PythonTools/ptvsd/attach_server.py @@ -328,7 +328,7 @@ def set_trace(): def enable_attach_ui(): global _attach_enabled, _ui_attach_options, _ui_attach_enabled if not _attach_enabled: - enable_attach(None, ('0.0.0.0', 0)) + enable_attach(None, ('127.0.0.1', 0)) if not _ui_attach_enabled: _ui_attach_enabled = debugger_ui_enable_attach() diff --git a/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py b/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py index 0d523ed64cd6..84982e0b38b7 100644 --- a/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py +++ b/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py @@ -2364,7 +2364,14 @@ def _excepthook(exc_type, exc_value, exc_tb): for mod_value in list(sys.modules.values()): try: - filename = getattr(mod_value, '__file__', None) + lazyModule = False + try: + lazyModule = mod_value.__class__.__name__ == 'LazyImporter' + except: + lazyModule = False + filename = None + if not lazyModule: + filename = getattr(mod_value, '__file__', None) if filename is not None: try: fullpath = path.abspath(filename) From 22def4d183a97721ec6d1a82071c322cf21112bf Mon Sep 17 00:00:00 2001 From: Aman Agarwal Date: Fri, 5 Oct 2018 11:24:33 -0700 Subject: [PATCH 432/433] Reverted ip address from 127.0.0.1 back to 0.0.0.0 for tupperware containers --- pythonFiles/PythonTools/ptvsd/attach_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pythonFiles/PythonTools/ptvsd/attach_server.py b/pythonFiles/PythonTools/ptvsd/attach_server.py index 2330f9c512e3..48dbda79b434 100644 --- a/pythonFiles/PythonTools/ptvsd/attach_server.py +++ b/pythonFiles/PythonTools/ptvsd/attach_server.py @@ -328,7 +328,7 @@ def set_trace(): def enable_attach_ui(): global _attach_enabled, _ui_attach_options, _ui_attach_enabled if not _attach_enabled: - enable_attach(None, ('127.0.0.1', 0)) + enable_attach(None, ('0.0.0.0', 0)) if not _ui_attach_enabled: _ui_attach_enabled = debugger_ui_enable_attach() From bb2178cd41a2d7662488bf46296f44caebd6e8a0 Mon Sep 17 00:00:00 2001 From: Aman Agarwal Date: Tue, 9 Oct 2018 16:30:40 -0700 Subject: [PATCH 433/433] Synced Launch and Attach Codepaths --- pythonFiles/PythonTools/ptvsd/__main__.py | 2 +- .../ptvsd/visualstudio_py_debugger.py | 16 ++++++-- .../PythonTools/visualstudio_ipython_repl.py | 10 ++--- .../PythonTools/visualstudio_py_debugger.py | 40 +++++++++++++------ .../PythonTools/visualstudio_py_launcher.py | 2 +- .../visualstudio_py_launcher_nodebug.py | 2 +- .../PythonTools/visualstudio_py_repl.py | 16 ++++---- pythonFiles/completionServer.py | 2 +- 8 files changed, 56 insertions(+), 34 deletions(-) diff --git a/pythonFiles/PythonTools/ptvsd/__main__.py b/pythonFiles/PythonTools/ptvsd/__main__.py index 8fc5004aa43a..2837de743528 100644 --- a/pythonFiles/PythonTools/ptvsd/__main__.py +++ b/pythonFiles/PythonTools/ptvsd/__main__.py @@ -54,4 +54,4 @@ DONT_DEBUG.append(os.path.normcase(__file__)) sys.argv = script_argv -exec_file(script_argv[0], {'__name__': '__main__'}) \ No newline at end of file +exec_file(script_argv[0], {'__name__': '__main__'}) diff --git a/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py b/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py index 84982e0b38b7..12aa0b09f2ed 100644 --- a/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py +++ b/pythonFiles/PythonTools/ptvsd/visualstudio_py_debugger.py @@ -2194,7 +2194,8 @@ def report_execution_error(exc_text, execution_id): def report_execution_exception(execution_id, exc_info): try: - exc_text = str(exc_info[1]) + exc_type, exc_value, traceback = exc_info + exc_text = '{}: {}'.format(get_exception_name(exc_type), exc_value) except: exc_text = 'An exception was thrown' @@ -2605,9 +2606,14 @@ def parse_debug_options(s): # Accept current Process id to pass back to debugger def debug(file, port_num, debug_id, debug_options, currentPid, run_as = 'script'): # remove us from modules so there's no trace of us - sys.modules['$visualstudio_py_debugger'] = sys.modules['visualstudio_py_debugger'] - __name__ = '$visualstudio_py_debugger' - del sys.modules['visualstudio_py_debugger'] + if sys.modules.has_key('visualstudio_py_debugger'): + sys.modules['$visualstudio_py_debugger'] = sys.modules['visualstudio_py_debugger'] + __name__ = '$visualstudio_py_debugger' + del sys.modules['visualstudio_py_debugger'] + elif sys.modules.has_key('ptvsd.visualstudio_py_debugger'): + sys.modules['$ptvsd.visualstudio_py_debugger'] = sys.modules['ptvsd.visualstudio_py_debugger'] + __name__ = '$ptvsd.visualstudio_py_debugger' + del sys.modules['ptvsd.visualstudio_py_debugger'] wait_on_normal_exit = 'WaitOnNormalExit' in debug_options @@ -2631,6 +2637,8 @@ def debug(file, port_num, debug_id, debug_options, currentPid, run_as = 'script' elif run_as == 'code': exec_code(file, '', globals_obj) else: + # fix sys.path to be the script file dir + sys.path[0] = '' exec_file(file, globals_obj) finally: sys.settrace(None) diff --git a/pythonFiles/PythonTools/visualstudio_ipython_repl.py b/pythonFiles/PythonTools/visualstudio_ipython_repl.py index 14f42362a8f8..0e2b8ca056ff 100644 --- a/pythonFiles/PythonTools/visualstudio_ipython_repl.py +++ b/pythonFiles/PythonTools/visualstudio_ipython_repl.py @@ -21,8 +21,8 @@ import re import sys -from visualstudio_py_repl import BasicReplBackend, ReplBackend, UnsupportedReplException, _command_line_to_args_list -from visualstudio_py_util import to_bytes +from ptvsd.visualstudio_py_repl import BasicReplBackend, ReplBackend, UnsupportedReplException, _command_line_to_args_list +from ptvsd.visualstudio_py_util import to_bytes try: import thread except: @@ -401,7 +401,7 @@ def init_debugger(self): def __visualstudio_debugger_init(): import sys sys.path.append(''' + repr(path.dirname(__file__)) + ''') - import visualstudio_py_debugger + import ptvsd.visualstudio_py_debugger as visualstudio_py_debugger new_thread = visualstudio_py_debugger.new_thread() sys.settrace(new_thread.trace_func) visualstudio_py_debugger.intercept_threads(True) @@ -413,7 +413,7 @@ def __visualstudio_debugger_init(): def attach_process(self, port, debugger_id): self.run_command(''' def __visualstudio_debugger_attach(): - import visualstudio_py_debugger + import ptvsd.visualstudio_py_debugger as visual_studio_py_debugger def do_detach(): visualstudio_py_debugger.DETACH_CALLBACKS.remove(do_detach) @@ -427,4 +427,4 @@ def do_detach(): class IPythonBackendWithoutPyLab(IPythonBackend): def get_extra_arguments(self): - return [] \ No newline at end of file + return [] diff --git a/pythonFiles/PythonTools/visualstudio_py_debugger.py b/pythonFiles/PythonTools/visualstudio_py_debugger.py index 6454a5d840ec..b2db85fcae17 100644 --- a/pythonFiles/PythonTools/visualstudio_py_debugger.py +++ b/pythonFiles/PythonTools/visualstudio_py_debugger.py @@ -47,12 +47,12 @@ try: # In the local attach scenario, visualstudio_py_util is injected into globals() # by PyDebugAttach before loading this module, and cannot be imported. - _vspu = visualstudio_py_util + _vspu = ptvsd.visualstudio_py_util except: try: - import visualstudio_py_util as _vspu - except ImportError: import ptvsd.visualstudio_py_util as _vspu + except ImportError: + import visualstudio_py_util as _vspu to_bytes = _vspu.to_bytes exec_file = _vspu.exec_file @@ -69,15 +69,17 @@ try: # In the local attach scenario, visualstudio_py_repl is injected into globals() # by PyDebugAttach before loading this module, and cannot be imported. - _vspr = visualstudio_py_repl + _vspr = ptvsd.visualstudio_py_repl except: try: - import visualstudio_py_repl as _vspr - except ImportError: import ptvsd.visualstudio_py_repl as _vspr + except ImportError: + import visualstudio_py_repl as _vspr + try: import stackless + stackless.tasklet # work-around lazy on-demand importers except ImportError: stackless = None @@ -2361,9 +2363,16 @@ def _excepthook(exc_type, exc_value, exc_tb): global debugger_thread_id debugger_thread_id = _start_new_thread(DebuggerLoop(conn).loop, ()) - for mod_name, mod_value in sys.modules.items(): + for mod_value in list(sys.modules.values()): try: - filename = getattr(mod_value, '__file__', None) + lazyModule = False + try: + lazyModule = mod_value.__class__.__name__ == 'LazyImporter' + except: + lazyModule = False + filename = None + if not lazyModule: + filename = getattr(mod_value, '__file__', None) if filename is not None: try: fullpath = path.abspath(filename) @@ -2372,7 +2381,7 @@ def _excepthook(exc_type, exc_value, exc_tb): else: MODULES.append((filename, Module(fullpath))) except: - traceback.print_exc() + traceback.print_exc() if report: THREADS_LOCK.acquire() @@ -2597,9 +2606,14 @@ def parse_debug_options(s): # Accept current Process id to pass back to debugger def debug(file, port_num, debug_id, debug_options, currentPid, run_as = 'script'): # remove us from modules so there's no trace of us - sys.modules['$visualstudio_py_debugger'] = sys.modules['visualstudio_py_debugger'] - __name__ = '$visualstudio_py_debugger' - del sys.modules['visualstudio_py_debugger'] + if sys.modules.has_key('visualstudio_py_debugger'): + sys.modules['$visualstudio_py_debugger'] = sys.modules['visualstudio_py_debugger'] + __name__ = '$visualstudio_py_debugger' + del sys.modules['visualstudio_py_debugger'] + elif sys.modules.has_key('ptvsd.visualstudio_py_debugger'): + sys.modules['$ptvsd.visualstudio_py_debugger'] = sys.modules['ptvsd.visualstudio_py_debugger'] + __name__ = '$ptvsd.visualstudio_py_debugger' + del sys.modules['ptvsd.visualstudio_py_debugger'] wait_on_normal_exit = 'WaitOnNormalExit' in debug_options @@ -2753,4 +2767,4 @@ def _get_template_line(frame): return _offset_to_line_number(_read_file(file_name), source[1][0]) except: return None -## End modification by Don Jayamanne \ No newline at end of file +## End modification by Don Jayamanne diff --git a/pythonFiles/PythonTools/visualstudio_py_launcher.py b/pythonFiles/PythonTools/visualstudio_py_launcher.py index 9e202ff2064e..38f7a0bd53ac 100644 --- a/pythonFiles/PythonTools/visualstudio_py_launcher.py +++ b/pythonFiles/PythonTools/visualstudio_py_launcher.py @@ -29,7 +29,7 @@ def launch(): import sys import traceback try: - import visualstudio_py_debugger as vspd + import ptvsd.visualstudio_py_debugger as vspd except: traceback.print_exc() print(''' diff --git a/pythonFiles/PythonTools/visualstudio_py_launcher_nodebug.py b/pythonFiles/PythonTools/visualstudio_py_launcher_nodebug.py index 7285d6d8558b..0ada07cba53a 100644 --- a/pythonFiles/PythonTools/visualstudio_py_launcher_nodebug.py +++ b/pythonFiles/PythonTools/visualstudio_py_launcher_nodebug.py @@ -9,7 +9,7 @@ import time import socket try: - import visualstudio_py_util as _vspu + import ptvsd.visualstudio_py_util as _vspu except: traceback.print_exc() print("""Internal error detected. Please copy the above traceback and report at diff --git a/pythonFiles/PythonTools/visualstudio_py_repl.py b/pythonFiles/PythonTools/visualstudio_py_repl.py index ff84f9115dac..dac8a2efc3f4 100644 --- a/pythonFiles/PythonTools/visualstudio_py_repl.py +++ b/pythonFiles/PythonTools/visualstudio_py_repl.py @@ -49,12 +49,12 @@ try: # In the local attach scenario, visualstudio_py_util is injected into globals() # by PyDebugAttach before loading this module, and cannot be imported. - _vspu = visualstudio_py_util + _vspu = ptvsd.visualstudio_py_util except: try: - import visualstudio_py_util as _vspu - except ImportError: import ptvsd.visualstudio_py_util as _vspu + except ImportError: + import visualstudio_py_util as _vspu to_bytes = _vspu.to_bytes read_bytes = _vspu.read_bytes read_int = _vspu.read_int @@ -350,7 +350,7 @@ def _cmd_excx(self): self.execute_file_ex(filetype, filename, args) def _cmd_debug_attach(self): - import visualstudio_py_debugger + import ptvsd.visualstudio_py_debugger as visualstudio_py_debugger port = read_int(self.conn) id = read_string(self.conn) debug_options = visualstudio_py_debugger.parse_debug_options(read_string(self.conn)) @@ -384,7 +384,7 @@ def on_debugger_detach(self): def init_debugger(self): from os import path sys.path.append(path.dirname(__file__)) - import visualstudio_py_debugger + import ptvsd.visualstudio_py_debugger as visualstudio_py_debugger visualstudio_py_debugger.DONT_DEBUG.append(path.normcase(__file__)) new_thread = visualstudio_py_debugger.new_thread() sys.settrace(new_thread.trace_func) @@ -1005,13 +1005,13 @@ def flush(self): sys.stdout.flush() def do_detach(self): - import visualstudio_py_debugger + import ptvsd.visualstudio_py_debugger as visualstudio_py_debugger visualstudio_py_debugger.DETACH_CALLBACKS.remove(self.do_detach) self.on_debugger_detach() def attach_process(self, port, debugger_id, debug_options): def execute_attach_process_work_item(): - import visualstudio_py_debugger + import ptvsd.visualstudio_py_debugger as visualstudio_py_debugger visualstudio_py_debugger.DETACH_CALLBACKS.append(self.do_detach) visualstudio_py_debugger.attach_process(port, debugger_id, debug_options, report=True, block=True) @@ -1388,4 +1388,4 @@ def _run_repl(): _debug_write(traceback.format_exc()) _debug_write('exiting') input() - raise \ No newline at end of file + raise diff --git a/pythonFiles/completionServer.py b/pythonFiles/completionServer.py index 9c380e493f73..d4defc59fe82 100644 --- a/pythonFiles/completionServer.py +++ b/pythonFiles/completionServer.py @@ -28,7 +28,7 @@ # http://pydoc.net/Python/magni/1.4.0/magni.tests.ipynb_examples/ # http://www.xavierdupre.fr/app/pyquickhelper/helpsphinx/_modules/pyquickhelper/ipythonhelper/notebook_runner.html -import visualstudio_py_util as _vspu +import ptvsd.visualstudio_py_util as _vspu to_bytes = _vspu.to_bytes read_bytes = _vspu.read_bytes