From e0b5fb9b5c79e3a17f3a66317b7ed7048e48a088 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 5 Aug 2022 03:07:31 +1000 Subject: [PATCH] Port changes from main branch to remove diagnostic feature (#11052) * Reduce list of reserved files for which we warn uses of potentially interfering with kernel startup (#11044) * Reduced reserved files that interfere with kernels * Misc * Remove warning about overriding python files * Restore commented files --- CHANGELOG.md | 3 - news/2 Fixes/11040.md | 1 + package.json | 26 -- src/platform/interpreter/constants.ts | 187 +------------ .../interpreter/reservedNamedProvider.node.ts | 16 +- .../reservedFileNameDiagnostics.node.ts | 211 --------------- .../diagnostics/serviceRegistry.node.ts | 14 - src/standalone/serviceRegistry.node.ts | 3 - .../reservedNamedProvider.node.unit.test.ts | 35 +-- ...ervedFileNameDiagnostics.node.unit.test.ts | 247 ------------------ 10 files changed, 14 insertions(+), 729 deletions(-) create mode 100644 news/2 Fixes/11040.md delete mode 100644 src/standalone/diagnostics/reservedFileNameDiagnostics.node.ts delete mode 100644 src/standalone/diagnostics/serviceRegistry.node.ts delete mode 100644 src/test/standalone/diagnostics/reservedFileNameDiagnostics.node.unit.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index e5aca6ed211..b1b3388181b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,6 @@ ### Enhancements -1. Warn users when a Python file could override an existing Python package (there by interfering with the kernels. [More info](https://aka.ms/JupyterKernelStartFailureOverrideReservedName)). - This feature could be turned off via the setting `"jupyter.diagnostics.reservedPythonNames.enabled": false`. - ([#7538](https://github.com/Microsoft/vscode-jupyter/issues/7538)) 1. DataFrame viewer enabled on the web. ([#9665](https://github.com/Microsoft/vscode-jupyter/issues/9665)) 1. The Variable Viewer now shows strings wrapped in single quotes. diff --git a/news/2 Fixes/11040.md b/news/2 Fixes/11040.md new file mode 100644 index 00000000000..066b95c327c --- /dev/null +++ b/news/2 Fixes/11040.md @@ -0,0 +1 @@ +Reduced set of file names that are treated as reserved names that could interfere with the startup of `Python Kernels`. diff --git a/package.json b/package.json index f599d1057b5..5aa0a93c0bd 100644 --- a/package.json +++ b/package.json @@ -2042,32 +2042,6 @@ "tags": [ "experimental" ] - }, - "jupyter.diagnostics.reservedPythonNames.enabled": { - "type": "boolean", - "default": true, - "markdownDescription": "%jupyter.configuration.jupyter.diagnostics.reservedPythonNames.enabled.markdownDescription%", - "scope": "machine", - "tags": [ - "diagnostics" - ] - }, - "jupyter.diagnostics.reservedPythonNames.exclude": { - "type": "array", - "default": [ - "**/site-packages/**", - "**/lib/python/**", - "**/lib64/python/**" - ], - "items": { - "type": "string" - }, - "uniqueItems": true, - "markdownDescription": "%jupyter.configuration.jupyter.diagnostics.reservedPythonNames.exclude.markdownDescription%", - "scope": "machine", - "tags": [ - "diagnostics" - ] } } }, diff --git a/src/platform/interpreter/constants.ts b/src/platform/interpreter/constants.ts index cc172792123..cfbcd5c7410 100644 --- a/src/platform/interpreter/constants.ts +++ b/src/platform/interpreter/constants.ts @@ -6,116 +6,18 @@ export const JupyterKernelStartFailureOverrideReservedName = // Sum of __builtins__ and stdlib_list('3.9') (stdlib_list is a separate python package). // This has been generated and hard-coded to prevent unnecessarily launching the Python process. +// And then check if we can import these as modules. +// E.g. `ArithmeticError` is an allowed file/folder name, as that cannot be imported, hence the assumption is that +// creating a file named `ArithmeticError.py` will not interfere with the startup of the kernel (tested and validated). +// This way we only focus on files that we know can be imported by Python. +// Unfortunately we don't know what the full list of files is, as users could have multiple packages installed, that could in turn pull in different dependencies +// that in turn import these built-in modules. export const BuiltInModules = [ - 'ArithmeticError', - 'AssertionError', - 'AttributeError', - 'BaseException', - 'BlockingIOError', - 'BrokenPipeError', - 'BufferError', - 'BytesWarning', - 'ChildProcessError', - 'ConnectionAbortedError', - 'ConnectionError', - 'ConnectionRefusedError', - 'ConnectionResetError', - 'DeprecationWarning', - 'EOFError', - 'Ellipsis', - 'EnvironmentError', - 'Exception', - 'False', - 'FileExistsError', - 'FileNotFoundError', - 'FloatingPointError', - 'FutureWarning', - 'GeneratorExit', - 'IOError', - 'ImportError', - 'ImportWarning', - 'IndentationError', - 'IndexError', - 'InterruptedError', - 'IsADirectoryError', - 'KeyError', - 'KeyboardInterrupt', - 'LookupError', - 'MemoryError', - 'ModuleNotFoundError', - 'NameError', - 'None', - 'NotADirectoryError', - 'NotImplemented', - 'NotImplementedError', - 'OSError', - 'OverflowError', - 'PendingDeprecationWarning', - 'PermissionError', - 'ProcessLookupError', - 'RecursionError', - 'ReferenceError', - 'ResourceWarning', - 'RuntimeError', - 'RuntimeWarning', - 'StopAsyncIteration', - 'StopIteration', - 'SyntaxError', - 'SyntaxWarning', - 'SystemError', - 'SystemExit', - 'TabError', - 'TimeoutError', - 'True', - 'TypeError', - 'UnboundLocalError', - 'UnicodeDecodeError', - 'UnicodeEncodeError', - 'UnicodeError', - 'UnicodeTranslateError', - 'UnicodeWarning', - 'UserWarning', - 'ValueError', - 'Warning', - 'ZeroDivisionError', - '_', - '__build_class__', - '__debug__', - '__doc__', - '__future__', - '__import__', - '__loader__', - '__main__', - '__name__', - '__package__', - '__spec__', - '_aix_support', - '_bootlocale', - '_bootsubprocess', - '_collections_abc', - '_compat_pickle', - '_compression', - '_markupbase', - '_osx_support', - '_py_abc', - '_pydecimal', - '_pyio', - '_sitebuiltins', - '_strptime', - '_sysconfigdata_x86_64_conda_cos6_linux_gnu', - '_sysconfigdata_x86_64_conda_linux_gnu', - '_thread', - '_threading_local', - '_weakrefset', 'abc', - 'abs', 'aifc', - 'all', 'antigravity', - 'any', 'argparse', 'array', - 'ascii', 'ast', 'asynchat', 'asyncio', @@ -124,24 +26,16 @@ export const BuiltInModules = [ 'audioop', 'base64', 'bdb', - 'bin', 'binascii', 'binhex', 'bisect', - 'bool', - 'breakpoint', 'builtins', - 'bytearray', - 'bytes', 'bz2', 'cProfile', 'calendar', - 'callable', 'cgi', 'cgitb', - 'chr', 'chunk', - 'classmethod', 'cmath', 'cmd', 'code', @@ -149,17 +43,13 @@ export const BuiltInModules = [ 'codeop', 'collections', 'colorsys', - 'compile', 'compileall', - 'complex', 'concurrent', 'configparser', 'contextlib', 'contextvars', 'copy', 'copyreg', - 'copyright', - 'credits', 'crypt', 'csv', 'ctypes', @@ -168,112 +58,72 @@ export const BuiltInModules = [ 'datetime', 'dbm', 'decimal', - 'delattr', - 'dict', 'difflib', - 'dir', 'dis', 'distutils', - 'divmod', 'doctest', 'email', 'encodings', 'ensurepip', 'enum', - 'enumerate', 'errno', - 'eval', - 'exec', - 'exit', 'faulthandler', 'fcntl', 'filecmp', 'fileinput', - 'filter', - 'float', 'fnmatch', - 'format', 'formatter', 'fractions', - 'frozenset', 'ftplib', 'functools', 'gc', 'genericpath', - 'getattr', 'getopt', 'getpass', 'gettext', 'glob', - 'globals', 'graphlib', 'grp', 'gzip', - 'hasattr', - 'hash', 'hashlib', 'heapq', - 'help', - 'hex', 'hmac', 'html', 'http', - 'id', 'idlelib', 'imaplib', 'imghdr', 'imp', 'importlib', - 'input', 'inspect', - 'int', 'io', 'ipaddress', - 'isinstance', - 'issubclass', - 'iter', 'itertools', 'json', 'keyword', - 'len', 'lib2to3', - 'license', 'linecache', - 'list', 'locale', - 'locals', 'logging', 'lzma', 'mailbox', 'mailcap', - 'map', 'marshal', 'math', - 'max', - 'memoryview', 'mimetypes', - 'min', 'mmap', 'modulefinder', - 'msilib', - 'msvcrt', 'multiprocessing', 'netrc', - 'next', 'nis', 'nntplib', 'ntpath', 'nturl2path', 'numbers', - 'object', - 'oct', 'opcode', - 'open', 'operator', 'optparse', - 'ord', 'os', - 'ossaudiodev', 'parser', 'pathlib', 'pdb', @@ -286,11 +136,8 @@ export const BuiltInModules = [ 'poplib', 'posix', 'posixpath', - 'pow', 'pprint', - 'print', 'profile', - 'property', 'pstats', 'pty', 'pwd', @@ -299,54 +146,40 @@ export const BuiltInModules = [ 'pydoc', 'pydoc_data', 'queue', - 'quit', 'quopri', 'random', - 'range', 're', 'readline', - 'repr', 'reprlib', 'resource', - 'reversed', 'rlcompleter', - 'round', 'runpy', 'sched', 'secrets', 'select', 'selectors', - 'set', - 'setattr', 'shelve', 'shlex', 'shutil', 'signal', 'site', - 'slice', 'smtpd', 'smtplib', 'sndhdr', 'socket', 'socketserver', - 'sorted', - 'spwd', 'sqlite3', 'sre_compile', 'sre_constants', 'sre_parse', 'ssl', 'stat', - 'staticmethod', 'statistics', - 'str', 'string', 'stringprep', 'struct', 'subprocess', - 'sum', 'sunau', - 'super', 'symbol', 'symtable', 'sys', @@ -357,7 +190,6 @@ export const BuiltInModules = [ 'telnetlib', 'tempfile', 'termios', - 'test', 'textwrap', 'this', 'threading', @@ -370,10 +202,8 @@ export const BuiltInModules = [ 'traceback', 'tracemalloc', 'tty', - 'tuple', 'turtle', 'turtledemo', - 'type', 'types', 'typing', 'unicodedata', @@ -381,19 +211,14 @@ export const BuiltInModules = [ 'urllib', 'uu', 'uuid', - 'vars', - 'venv', 'warnings', 'wave', 'weakref', 'webbrowser', - 'winreg', - 'winsound', 'wsgiref', 'xdrlib', 'xml', 'xmlrpc', - 'zip', 'zipapp', 'zipfile', 'zipimport', diff --git a/src/platform/interpreter/reservedNamedProvider.node.ts b/src/platform/interpreter/reservedNamedProvider.node.ts index 85cf9706efd..255d102e0c3 100644 --- a/src/platform/interpreter/reservedNamedProvider.node.ts +++ b/src/platform/interpreter/reservedNamedProvider.node.ts @@ -5,11 +5,10 @@ import { inject, injectable, named } from 'inversify'; import { ConfigurationTarget, Memento, Uri } from 'vscode'; import { IMemento, GLOBAL_MEMENTO, IDisposable, IDisposableRegistry } from '../../platform/common/types'; import { BuiltInModules } from './constants'; -import { noop } from '../../platform/common/utils/misc'; import { IWorkspaceService } from '../../platform/common/application/types'; import { IPlatformService } from '../../platform/common/platform/types'; import { disposeAllDisposables } from '../../platform/common/helpers'; -import { IInterpreterPackages, IReservedPythonNamedProvider } from './types'; +import { IReservedPythonNamedProvider } from './types'; import * as minimatch from 'minimatch'; import { IFileSystemNode } from '../common/platform/types.node'; import * as path from '../../platform/vscode-path/resources'; @@ -26,7 +25,6 @@ export class ReservedNamedProvider implements IReservedPythonNamedProvider { private pendingUpdate = Promise.resolve(); private readonly disposables: IDisposable[] = []; constructor( - @inject(IInterpreterPackages) private readonly packages: IInterpreterPackages, @inject(IMemento) @named(GLOBAL_MEMENTO) private cache: Memento, @inject(IWorkspaceService) private workspace: IWorkspaceService, @inject(IPlatformService) private platform: IPlatformService, @@ -104,17 +102,7 @@ export class ReservedNamedProvider implements IReservedPythonNamedProvider { const baseName = path.basename(uri, path.extname(uri)).toLowerCase(); // If its a __init__.py, get name of parent folder (as its a module). const possibleModule = baseName === '__init__' ? path.basename(path.dirname(uri)).toLowerCase() : baseName; - if (this.cachedModules.has(possibleModule)) { - return true; - } - - const packages = new Set(await this.packages.listPackages(uri)); - const previousCount = this.cachedModules.size; - packages.forEach((item) => this.cachedModules.add(item.toLowerCase())); - if (previousCount < this.cachedModules.size) { - this.cache.update(PYTHON_PACKAGES_MEMENTO_KEY, Array.from(this.cachedModules)).then(noop, noop); - } - return packages.has(possibleModule); + return this.cachedModules.has(possibleModule); } public async addToIgnoreList(uri: Uri) { await this.pendingUpdate; diff --git a/src/standalone/diagnostics/reservedFileNameDiagnostics.node.ts b/src/standalone/diagnostics/reservedFileNameDiagnostics.node.ts deleted file mode 100644 index dc5e18ff2ec..00000000000 --- a/src/standalone/diagnostics/reservedFileNameDiagnostics.node.ts +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { inject, injectable } from 'inversify'; -import { - CancellationToken, - CodeAction, - CodeActionContext, - CodeActionKind, - CodeActionProvider, - commands, - ConfigurationTarget, - Diagnostic, - DiagnosticCollection, - DiagnosticSeverity, - EventEmitter, - FileDecoration, - FileDecorationProvider, - languages, - Range, - TextDocument, - TextEditor, - ThemeColor, - Uri, - window, - workspace -} from 'vscode'; -import { IDisposable } from '@fluentui/react'; -import { IExtensionSingleActivationService } from '../../platform/activation/types'; -import { PYTHON_LANGUAGE } from '../../platform/common/constants'; -import { disposeAllDisposables } from '../../platform/common/helpers'; -import { Common, DataScience } from '../../platform/common/utils/localize'; -import * as path from '../../platform/vscode-path/path'; -import { IFileSystem } from '../../platform/common/platform/types'; -import { IWorkspaceService } from '../../platform/common/application/types'; -import { IReservedPythonNamedProvider } from '../../platform/interpreter/types'; -import { JupyterKernelStartFailureOverrideReservedName } from '../../platform/interpreter/constants'; -import { swallowExceptions } from '../../platform/common/utils/decorators'; - -export const enabledSettingName = 'diagnostics.reservedPythonNames.enabled'; - -/** - * Generates errors when reserved names are used for files in the workspace - */ -@injectable() -export class ReservedFileNamesDiagnosticProvider - implements IExtensionSingleActivationService, CodeActionProvider, FileDecorationProvider -{ - private readonly disposables: IDisposable[] = []; - private readonly diagnosticCollection: DiagnosticCollection; - private readonly _onDidChangeFileDecorations = new EventEmitter(); - onDidChangeFileDecorations = this._onDidChangeFileDecorations.event; - private enabled: boolean; - constructor( - @inject(IReservedPythonNamedProvider) private readonly reservedNameProvider: IReservedPythonNamedProvider, - @inject(IFileSystem) private readonly fileSystem: IFileSystem, - @inject(IWorkspaceService) private workspace: IWorkspaceService - ) { - this.diagnosticCollection = languages.createDiagnosticCollection( - DataScience.reservedPythonFileNamesDiagnosticCollectionName() - ); - } - public dispose() { - disposeAllDisposables(this.disposables); - this._onDidChangeFileDecorations.dispose(); - this.diagnosticCollection.dispose(); - } - public async activate(): Promise { - this.disposables.push(languages.registerCodeActionsProvider(PYTHON_LANGUAGE, this)); - this.disposables.push(window.registerFileDecorationProvider(this)); - this.enabled = this.workspace.getConfiguration('jupyter').get(enabledSettingName, true); - this.workspace.onDidChangeConfiguration( - (e) => { - if (e.affectsConfiguration(`jupyter.${enabledSettingName}`)) { - this.enabled = this.workspace.getConfiguration('jupyter').get(enabledSettingName, true); - if (this.enabled) { - window.visibleTextEditors.forEach((editor) => this.provideDiagnosticsForEditor(editor)); - } else { - this.diagnosticCollection.clear(); - } - this._onDidChangeFileDecorations.fire(undefined); - } - }, - this, - this.disposables - ); - - window.onDidChangeActiveTextEditor(this.provideDiagnosticsForEditor, this, this.disposables); - workspace.onDidCloseTextDocument((e) => this.diagnosticCollection.delete(e.uri), this, this.disposables); - window.visibleTextEditors.forEach((editor) => this.provideDiagnosticsForEditor(editor)); - this.disposables.push( - commands.registerCommand( - 'jupyter.ignoreReservedFileNamesDiagnostic', - async (uri: Uri) => { - await this.reservedNameProvider.addToIgnoreList(uri); - this._onDidChangeFileDecorations.fire(uri); - this.diagnosticCollection.forEach((item) => { - if (this.fileSystem.arePathsSame(item, uri)) { - this.diagnosticCollection.delete(item); - } - }); - }, - this - ) - ); - this.disposables.push( - commands.registerCommand( - 'jupyter.disableReservedFileNamesDiagnostic', - async () => this.disableDiagnostics(), - this - ) - ); - } - - public async provideCodeActions( - document: TextDocument, - _range: Range, - context: CodeActionContext - ): Promise { - if (!this.enabled) { - return []; - } - const ourDiagnostic = context.diagnostics.filter( - (item) => item.message === DataScience.pythonFileOverridesPythonPackage() - ); - if (ourDiagnostic.length === 0) { - return []; - } - - const name = path.basename(document.fileName, path.extname(document.fileName)); - const codeActionDisable = new CodeAction( - DataScience.alwaysIgnoreWarningsAboutOverridingPythonPackages(), - CodeActionKind.QuickFix - ); - codeActionDisable.command = { - command: 'jupyter.disableReservedFileNamesDiagnostic', - arguments: [document.uri], - title: codeActionDisable.title - }; - const codeActionIgnore = new CodeAction( - DataScience.ignoreWarningAboutOverridingPythonPackage().format(name), - CodeActionKind.QuickFix - ); - codeActionIgnore.command = { - command: 'jupyter.ignoreReservedFileNamesDiagnostic', - arguments: [document.uri], - title: codeActionIgnore.title - }; - codeActionIgnore.isPreferred = true; - - const codeActions = [codeActionDisable, codeActionIgnore]; - codeActions.forEach((action) => (action.diagnostics = ourDiagnostic)); - return codeActions; - } - - public async provideFileDecoration(uri: Uri, _token: CancellationToken): Promise { - if (!this.enabled || !uri.fsPath.toLowerCase().endsWith('.py')) { - return; - } - const ourDiagnostic = this.diagnosticCollection.get(uri); - if (ourDiagnostic && ourDiagnostic.length > 0) { - return new FileDecoration('!', ourDiagnostic[0].message, new ThemeColor('editorWarning.foreground')); - } - - if (await this.reservedNameProvider.isReserved(uri)) { - const diagnostic = new Diagnostic( - new Range(0, 0, 0, 0), - DataScience.pythonFileOverridesPythonPackage(), - DiagnosticSeverity.Warning - ); - diagnostic.code = { - target: Uri.parse(JupyterKernelStartFailureOverrideReservedName), - value: Common.learnMore() - }; - diagnostic.source = Common.jupyter(); - this.diagnosticCollection.set(uri, [diagnostic]); - return new FileDecoration('!', diagnostic.message, new ThemeColor('editorWarning.foreground')); - } - } - - private async disableDiagnostics() { - this.enabled = false; - const jupyterConfig = this.workspace.getConfiguration('jupyter'); - await jupyterConfig.update(enabledSettingName, false, ConfigurationTarget.Global); - this.diagnosticCollection.clear(); - this._onDidChangeFileDecorations.fire(undefined); - } - - @swallowExceptions() - private async provideDiagnosticsForEditor(editor?: TextEditor) { - if (!this.enabled || !editor || editor.document.languageId !== PYTHON_LANGUAGE) { - return; - } - if (await this.reservedNameProvider.isReserved(editor.document.uri)) { - const lastLine = editor.document.lineCount; - const diagnostic = new Diagnostic( - new Range(0, 0, lastLine, editor.document.lineAt(lastLine - 1).range.end.character), - DataScience.pythonFileOverridesPythonPackage(), - DiagnosticSeverity.Warning - ); - diagnostic.code = { - target: Uri.parse(JupyterKernelStartFailureOverrideReservedName), - value: Common.learnMore() - }; - diagnostic.source = Common.jupyter(); - this.diagnosticCollection.delete(editor.document.uri); - this.diagnosticCollection.set(editor.document.uri, [diagnostic]); - this._onDidChangeFileDecorations.fire(editor.document.uri); - } - } -} diff --git a/src/standalone/diagnostics/serviceRegistry.node.ts b/src/standalone/diagnostics/serviceRegistry.node.ts deleted file mode 100644 index af347394878..00000000000 --- a/src/standalone/diagnostics/serviceRegistry.node.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -'use strict'; - -import { IExtensionSyncActivationService } from '../../platform/activation/types'; -import { IServiceManager } from '../../platform/ioc/types'; -import { ReservedFileNamesDiagnosticProvider } from './reservedFileNameDiagnostics.node'; - -export function registerTypes(serviceManager: IServiceManager) { - serviceManager.addSingleton( - IExtensionSyncActivationService, - ReservedFileNamesDiagnosticProvider - ); -} diff --git a/src/standalone/serviceRegistry.node.ts b/src/standalone/serviceRegistry.node.ts index a0c6238c6ae..31ca38a2256 100644 --- a/src/standalone/serviceRegistry.node.ts +++ b/src/standalone/serviceRegistry.node.ts @@ -27,7 +27,6 @@ import { DataScienceSurveyBanner, ISurveyBanner } from './survey/dataScienceSurv import { IExtensionContext } from '../platform/common/types'; import { registerTypes as registerDevToolTypes } from './devTools/serviceRegistry'; import { registerTypes as registerIntellisenseTypes } from './intellisense/serviceRegistry.node'; -import { registerTypes as registerDiagnosticTypes } from './diagnostics/serviceRegistry.node'; import { PythonExtensionRestartNotification } from './notification/pythonExtensionRestartNotification'; export function registerTypes(context: IExtensionContext, serviceManager: IServiceManager, isDevMode: boolean) { @@ -80,8 +79,6 @@ export function registerTypes(context: IExtensionContext, serviceManager: IServi PythonExtensionRestartNotification ); - registerDiagnosticTypes(serviceManager); - // Intellisense registerIntellisenseTypes(serviceManager, isDevMode); diff --git a/src/test/platform/interpreter/reservedNamedProvider.node.unit.test.ts b/src/test/platform/interpreter/reservedNamedProvider.node.unit.test.ts index 071de0fb1c3..85da8afbd25 100644 --- a/src/test/platform/interpreter/reservedNamedProvider.node.unit.test.ts +++ b/src/test/platform/interpreter/reservedNamedProvider.node.unit.test.ts @@ -19,13 +19,10 @@ import { IFileSystemNode } from '../../../platform/common/platform/types.node'; import { IDisposable } from '../../../platform/common/types'; import { ignoreListSettingName, ReservedNamedProvider } from '../../../platform/interpreter/reservedNamedProvider.node'; import * as path from '../../../platform/vscode-path/path'; -import { BuiltInModules } from '../../../platform/interpreter/constants'; -import { IInterpreterPackages } from '../../../platform/interpreter/types'; suite('Reserved Names Provider', () => { const disposables: IDisposable[] = []; let reservedNamedProvider: ReservedNamedProvider; - let interpreterPackages: IInterpreterPackages; let memento: Memento; let workspace: IWorkspaceService; let platform: IPlatformService; @@ -34,7 +31,6 @@ suite('Reserved Names Provider', () => { let settingsChanged: EventEmitter; const defaultIgnoreList = ['**/site-packages/**', '**/lib/python/**', '**/lib64/python/**']; setup(() => { - interpreterPackages = mock(); memento = mock(); workspace = mock(); platform = mock(); @@ -44,7 +40,6 @@ suite('Reserved Names Provider', () => { when(workspace.getConfiguration('jupyter')).thenReturn(instance(workspaceConfig)); when(workspaceConfig.get(ignoreListSettingName, anything())).thenReturn(defaultIgnoreList); when(memento.get(anything(), anything())).thenCall((_, defaultValue) => defaultValue as any); - when(interpreterPackages.listPackages(anything())).thenResolve(BuiltInModules.map((m) => m.toLowerCase())); settingsChanged = new EventEmitter(); disposables.push(settingsChanged); when(workspace.onDidChangeConfiguration).thenReturn(settingsChanged.event); @@ -55,7 +50,6 @@ suite('Reserved Names Provider', () => { }); function createProvider() { reservedNamedProvider = new ReservedNamedProvider( - instance(interpreterPackages), instance(memento), instance(workspace), instance(platform), @@ -65,7 +59,7 @@ suite('Reserved Names Provider', () => { } test('Returns valid Uris of files and folders that can override builtins', async () => { const cwd = Uri.joinPath(Uri.file('users'), 'username', 'folder', 'projectDir'); - const cwdFiles = ['one.py', 'xml.py', 'two.py', 'os.py', 'random.py', 'sample.py']; + const cwdFiles = ['one.py', 'xml.py', 'two.py', 'urllib.py', 'random.py', 'sample.py']; const initFiles = [ `xml${path.sep}__init__.py`, `overrideThirdPartyModule${path.sep}__init__.py`, @@ -77,45 +71,26 @@ suite('Reserved Names Provider', () => { // Assume that a module named `overrideThirdPartyModule` has been installed into python, then // python will return that as an installed item as well. let listPackagesCallCount = 0; - when(interpreterPackages.listPackages(anything())).thenCall(() => { - listPackagesCallCount += 1; - return BuiltInModules.concat(['overrideThirdPartyModule']).map((m) => m.toLowerCase()); - }); const uris = await reservedNamedProvider.getUriOverridingReservedPythonNames(cwd); assert.deepEqual( uris.map((uri) => uri.uri.fsPath).sort(), - [ - 'xml.py', - 'os.py', - 'random.py', - `overrideThirdPartyModule${path.sep}__init__.py`, - `xml${path.sep}__init__.py` - ] + ['xml.py', 'urllib.py', 'random.py', `xml${path.sep}__init__.py`] .map((file) => Uri.joinPath(cwd, file).fsPath) .sort() ); // Also verify we don't call into Python API for the same Python files that we know are overriding builtins. const initialCallCountIntoPythonApi = listPackagesCallCount; - when(fs.searchLocal('*.py', cwd.fsPath, true)).thenResolve(['xml.py', 'os.py', 'random.py']); - when(fs.searchLocal('*/__init__.py', cwd.fsPath, true)).thenResolve([ - `xml${path.sep}__init__.py`, - `overrideThirdPartyModule${path.sep}__init__.py` - ]); + when(fs.searchLocal('*.py', cwd.fsPath, true)).thenResolve(['xml.py', 'urllib.py', 'random.py']); + when(fs.searchLocal('*/__init__.py', cwd.fsPath, true)).thenResolve([`xml${path.sep}__init__.py`]); const urisAgain = await reservedNamedProvider.getUriOverridingReservedPythonNames(cwd); assert.deepEqual( urisAgain.map((uri) => uri.uri.fsPath).sort(), - [ - 'xml.py', - 'os.py', - 'random.py', - `overrideThirdPartyModule${path.sep}__init__.py`, - `xml${path.sep}__init__.py` - ] + ['xml.py', 'urllib.py', 'random.py', `xml${path.sep}__init__.py`] .map((file) => Uri.joinPath(cwd, file).fsPath) .sort() ); diff --git a/src/test/standalone/diagnostics/reservedFileNameDiagnostics.node.unit.test.ts b/src/test/standalone/diagnostics/reservedFileNameDiagnostics.node.unit.test.ts deleted file mode 100644 index 350d7d0c8cf..00000000000 --- a/src/test/standalone/diagnostics/reservedFileNameDiagnostics.node.unit.test.ts +++ /dev/null @@ -1,247 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -import { assert } from 'chai'; -import { anything, instance, mock, when } from 'ts-mockito'; -import { - CancellationTokenSource, - ConfigurationChangeEvent, - Diagnostic, - DiagnosticCollection, - EventEmitter, - Range, - TextDocument, - TextEditor, - Uri, - WorkspaceConfiguration -} from 'vscode'; -import { IWorkspaceService } from '../../../platform/common/application/types'; -import { PYTHON_LANGUAGE } from '../../../platform/common/constants'; -import { disposeAllDisposables } from '../../../platform/common/helpers'; -import { IFileSystemNode } from '../../../platform/common/platform/types.node'; -import { IDisposable } from '../../../platform/common/types'; -import { ignoreListSettingName } from '../../../platform/interpreter/reservedNamedProvider.node'; -import { IReservedPythonNamedProvider } from '../../../platform/interpreter/types'; -import { ResourceMap } from '../../../platform/vscode-path/map'; -import { - enabledSettingName, - ReservedFileNamesDiagnosticProvider -} from '../../../standalone/diagnostics/reservedFileNameDiagnostics.node'; -import { sleep } from '../../core'; -import { uriEquals } from '../../datascience/helpers'; -import { mockedVSCodeNamespaces } from '../../vscode-mock'; -import { DataScience } from '../../../platform/common/utils/localize'; - -suite('Reserved Names Diagnostics Provider', () => { - const disposables: IDisposable[] = []; - let reservedNamedProvider: IReservedPythonNamedProvider; - let diagnosticProvider: ReservedFileNamesDiagnosticProvider; - let workspace: IWorkspaceService; - let fs: IFileSystemNode; - let workspaceConfig: WorkspaceConfiguration; - let settingsChanged: EventEmitter; - let onDidChangeActiveTextEditor: EventEmitter; - let onDidCloseTextDocument: EventEmitter; - const defaultIgnoreList = ['**/site-packages/**', '**/lib/python/**', '**/lib64/python/**']; - let diagnosticCollectionItems = new ResourceMap(); - let cancellationToken: CancellationTokenSource; - setup(() => { - onDidChangeActiveTextEditor = new EventEmitter(); - onDidCloseTextDocument = new EventEmitter(); - reservedNamedProvider = mock(); - workspace = mock(); - fs = mock(); - workspaceConfig = mock(); - cancellationToken = new CancellationTokenSource(); - disposables.push(cancellationToken); - when(workspace.getConfiguration('jupyter')).thenReturn(instance(workspaceConfig)); - when(workspaceConfig.get(ignoreListSettingName, anything())).thenReturn(defaultIgnoreList); - when(workspaceConfig.get(enabledSettingName, anything())).thenReturn(true); - settingsChanged = new EventEmitter(); - when(workspace.onDidChangeConfiguration).thenReturn(settingsChanged.event); - when(mockedVSCodeNamespaces.window.visibleTextEditors).thenReturn([]); - when(mockedVSCodeNamespaces.workspace.onDidCloseTextDocument).thenReturn(onDidCloseTextDocument.event); - when(mockedVSCodeNamespaces.window.onDidChangeActiveTextEditor).thenReturn(onDidChangeActiveTextEditor.event); - when(mockedVSCodeNamespaces.languages.createDiagnosticCollection(anything())).thenCall(() => { - const mockCollection = mock(); - when(mockCollection.clear()).thenCall(() => diagnosticCollectionItems.clear()); - when(mockCollection.delete(anything())).thenCall((uri) => diagnosticCollectionItems.delete(uri)); - when(mockCollection.get(anything())).thenCall((uri) => diagnosticCollectionItems.get(uri)); - when(mockCollection.has(anything())).thenCall((uri) => diagnosticCollectionItems.has(uri)); - when(mockCollection.set(anything(), anything())).thenCall((uri, value) => - diagnosticCollectionItems.set(uri, value) - ); - when(mockCollection.forEach(anything())).thenCall((cb) => diagnosticCollectionItems.forEach(cb)); - return instance(mockCollection); - }); - diagnosticProvider = new ReservedFileNamesDiagnosticProvider( - instance(reservedNamedProvider), - instance(fs), - instance(workspace) - ); - disposables.push(diagnosticProvider); - disposables.push(onDidChangeActiveTextEditor); - disposables.push(onDidCloseTextDocument); - }); - teardown(() => { - disposeAllDisposables(disposables); - diagnosticCollectionItems.clear(); - }); - test('Produces diagnostics', async () => { - const uri = Uri.file('textdocument.py'); - const textDoc = mock(); - when(textDoc.uri).thenReturn(uri); - when(textDoc.languageId).thenReturn(PYTHON_LANGUAGE); - when(textDoc.lineCount).thenReturn(10); - when(textDoc.lineAt(anything())).thenCall(() => { - return { - range: new Range(10, 0, 10, 10) - }; - }); - const textEditor = mock(); - when(textEditor.document).thenReturn(instance(textDoc)); - when(mockedVSCodeNamespaces.window.visibleTextEditors).thenReturn([instance(textEditor)]); - when(reservedNamedProvider.isReserved(uriEquals(uri))).thenResolve(true); - - await diagnosticProvider.activate(); - - await sleep(1); - - assert.strictEqual(diagnosticCollectionItems.size, 1); - const diagnostics = diagnosticCollectionItems.get(uri)!; - assert.strictEqual(diagnostics.length, 1); - assert.strictEqual(diagnostics[0].message, DataScience.pythonFileOverridesPythonPackage()); - - // Clear the diagnostics when the document is closed. - onDidCloseTextDocument.fire(instance(textDoc)); - - assert.strictEqual(diagnosticCollectionItems.size, 0); - }); - test('Test enable/disabling of feature', async () => { - when(workspaceConfig.get(enabledSettingName, anything())).thenReturn(false); - const uri = Uri.file('textdocument.py'); - const textDoc = mock(); - when(textDoc.uri).thenReturn(uri); - when(textDoc.languageId).thenReturn(PYTHON_LANGUAGE); - when(textDoc.lineCount).thenReturn(10); - when(textDoc.lineAt(anything())).thenCall(() => { - return { - range: new Range(10, 0, 10, 10) - }; - }); - const textEditor = mock(); - when(textEditor.document).thenReturn(instance(textDoc)); - when(mockedVSCodeNamespaces.window.visibleTextEditors).thenReturn([instance(textEditor)]); - when(reservedNamedProvider.isReserved(uriEquals(uri))).thenResolve(true); - - await diagnosticProvider.activate(); - - await sleep(1); - - assert.strictEqual(diagnosticCollectionItems.size, 0); - - // Upon enabling the feature, we should get the diagnostic for currently opened documents. - when(workspaceConfig.get(enabledSettingName, anything())).thenReturn(true); - settingsChanged.fire({ - affectsConfiguration: (section) => section === `jupyter.${enabledSettingName}` - }); - - await sleep(1); - - assert.strictEqual(diagnosticCollectionItems.size, 1); - const diagnostics = diagnosticCollectionItems.get(uri)!; - assert.strictEqual(diagnostics.length, 1); - assert.strictEqual(diagnostics[0].message, DataScience.pythonFileOverridesPythonPackage()); - - // Disabling the feature should clear the diagnostics. - when(workspaceConfig.get(enabledSettingName, anything())).thenReturn(false); - settingsChanged.fire({ - affectsConfiguration: (section) => section === `jupyter.${enabledSettingName}` - }); - - await sleep(1); - - assert.strictEqual(diagnosticCollectionItems.size, 0); - }); - test('Does not produce diagnostics', async () => { - const uri = Uri.file('textdocument.py'); - const textDoc = mock(); - when(textDoc.uri).thenReturn(uri); - when(textDoc.languageId).thenReturn(PYTHON_LANGUAGE); - when(textDoc.lineCount).thenReturn(10); - when(textDoc.lineAt(anything())).thenCall(() => { - return { - range: new Range(10, 0, 10, 10) - }; - }); - const textEditor = mock(); - when(textEditor.document).thenReturn(instance(textDoc)); - when(mockedVSCodeNamespaces.window.visibleTextEditors).thenReturn([instance(textEditor)]); - when(reservedNamedProvider.isReserved(uriEquals(uri))).thenResolve(false); - - await diagnosticProvider.activate(); - - await sleep(1); - - assert.strictEqual(diagnosticCollectionItems.size, 0); - }); - test('Produces diagnostics when a new document is opened', async () => { - const uri = Uri.file('textdocument.py'); - const textDoc = mock(); - when(textDoc.uri).thenReturn(uri); - when(textDoc.languageId).thenReturn(PYTHON_LANGUAGE); - when(textDoc.lineCount).thenReturn(10); - when(textDoc.lineAt(anything())).thenCall(() => { - return { - range: new Range(10, 0, 10, 10) - }; - }); - const textEditor = mock(); - when(textEditor.document).thenReturn(instance(textDoc)); - when(mockedVSCodeNamespaces.window.visibleTextEditors).thenReturn([]); - when(reservedNamedProvider.isReserved(uriEquals(uri))).thenResolve(true); - - await diagnosticProvider.activate(); - - await sleep(1); - - assert.strictEqual(diagnosticCollectionItems.size, 0); - - // Open the document. - onDidChangeActiveTextEditor.fire(instance(textEditor)); - - await sleep(1); - const diagnostics = diagnosticCollectionItems.get(uri)!; - assert.strictEqual(diagnostics.length, 1); - assert.strictEqual(diagnostics[0].message, DataScience.pythonFileOverridesPythonPackage()); - }); - test('Test providing file decorations', async () => { - await diagnosticProvider.activate(); - - when(reservedNamedProvider.isReserved(uriEquals(Uri.file('xml.py')))).thenResolve(true); - const decoration = await diagnosticProvider.provideFileDecoration(Uri.file('xml.py'), cancellationToken.token); - assert.strictEqual(decoration?.tooltip, DataScience.pythonFileOverridesPythonPackage()); - - when(reservedNamedProvider.isReserved(uriEquals(Uri.file('xml.ts')))).thenResolve(false); - assert.isUndefined(await diagnosticProvider.provideFileDecoration(Uri.file('xml.ts'), cancellationToken.token)); - - when(reservedNamedProvider.isReserved(uriEquals(Uri.file('something else.py')))).thenResolve(false); - assert.isUndefined( - await diagnosticProvider.provideFileDecoration(Uri.file('something else.py'), cancellationToken.token) - ); - - // Disable and try again. - when(workspaceConfig.get(enabledSettingName, anything())).thenReturn(false); - settingsChanged.fire({ - affectsConfiguration: (section) => section === `jupyter.${enabledSettingName}` - }); - when(reservedNamedProvider.isReserved(anything())).thenResolve(true); - - assert.isUndefined(await diagnosticProvider.provideFileDecoration(Uri.file('xml.py'), cancellationToken.token)); - assert.isUndefined(await diagnosticProvider.provideFileDecoration(Uri.file('xml.ts'), cancellationToken.token)); - assert.isUndefined( - await diagnosticProvider.provideFileDecoration(Uri.file('something else.py'), cancellationToken.token) - ); - }); -});