From d93ef08af5f7e0894a4ea349a09edeea42e2d921 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Thu, 5 Aug 2021 11:19:49 -0700 Subject: [PATCH 01/10] Update version and change log. (#16868) --- CHANGELOG.md | 6 +++--- package-lock.json | 16 ++++++++-------- package.json | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54911f022d27..3a94c15e88f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,17 @@ # Changelog -## 2021.8.0-rc (2 August 2021) +## 2021.8.0 (5 August 2021) ### Enhancements -1. Add new getting started page using VS Code's API +1. Add new getting started page using VS Code's API to replace our custom start page. ([#16678](https://github.com/Microsoft/vscode-python/issues/16678)) 1. Replace deprecated vscode-test with @vscode/test-electron for CI. (thanks [iChenLei](https://github.com/iChenLei)) ([#16765](https://github.com/Microsoft/vscode-python/issues/16765)) ### Code Health -1. Sort Settings Alphabetically (thanks [bfarahdel](https://github.com/bfarahdel)) +1. Sort Settings Alphabetically. (thanks [bfarahdel](https://github.com/bfarahdel)) ([#8406](https://github.com/Microsoft/vscode-python/issues/8406)) 1. Changed default language server to `Pylance` for extension development. (thanks [jasleen101010](https://github.com/jasleen101010)) ([#13007](https://github.com/Microsoft/vscode-python/issues/13007)) diff --git a/package-lock.json b/package-lock.json index 035af737a92d..57919a88245f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "python", - "version": "2021.8.0-rc", + "version": "2021.8.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -18034,9 +18034,9 @@ } }, "minizlib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.0.tgz", - "integrity": "sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "dev": true, "requires": { "minipass": "^3.0.0", @@ -18138,15 +18138,15 @@ } }, "tar": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.2.tgz", - "integrity": "sha512-Glo3jkRtPcvpDlAs/0+hozav78yoXKFr+c4wgw62NNMO3oo4AaJdCo21Uu7lcwr55h39W2XD1LMERc64wtbItg==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.6.tgz", + "integrity": "sha512-oaWyu5dQbHaYcyZCTfyPpC+VmI62/OM2RTUYavTk1MDr1cwW5Boi3baeYQKiZbY2uSQJGr+iMOzb/JFxLrft+g==", "dev": true, "requires": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^3.0.0", - "minizlib": "^2.1.0", + "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } diff --git a/package.json b/package.json index 1c2aa8165386..f857cd4676ca 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "IntelliSense (Pylance), Linting, Debugging (multi-threaded, remote), Jupyter Notebooks, code formatting, refactoring, unit tests, and more.", - "version": "2021.8.0-rc", + "version": "2021.8.0", "featureFlags": { "usingNewInterpreterStorage": true }, From 4f207551e647b9df9bf56bc36588a52bd71b0417 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 6 Aug 2021 08:53:38 -0700 Subject: [PATCH 02/10] Deprecate inExperiment API in favor of inExperimentSync (#16848) * Deprecate inExperiment API in favour of inExperimentSync * Ensure experiments are activated before they are used * News entry --- news/2 Fixes/16768.md | 1 + src/client/common/experiments/service.ts | 25 +-- src/client/extension.ts | 6 +- src/client/extensionActivation.ts | 2 - .../common/experiments/service.unit.test.ts | 170 ------------------ 5 files changed, 7 insertions(+), 197 deletions(-) create mode 100644 news/2 Fixes/16768.md diff --git a/news/2 Fixes/16768.md b/news/2 Fixes/16768.md new file mode 100644 index 000000000000..9d5177cbef3c --- /dev/null +++ b/news/2 Fixes/16768.md @@ -0,0 +1 @@ +Fix random delay before several actions. diff --git a/src/client/common/experiments/service.ts b/src/client/common/experiments/service.ts index 3aadce900074..e2ed57082317 100644 --- a/src/client/common/experiments/service.ts +++ b/src/client/common/experiments/service.ts @@ -88,30 +88,7 @@ export class ExperimentService implements IExperimentService { } public async inExperiment(experiment: string): Promise { - if (!this.experimentationService) { - return false; - } - - // Currently the service doesn't support opting in and out of experiments. - // so we need to perform these checks manually. - if (this._optOutFrom.includes('All') || this._optOutFrom.includes(experiment)) { - return false; - } - - if (this._optInto.includes('All') || this._optInto.includes(experiment)) { - // Check if the user was already in the experiment server-side. We need to do - // this to ensure the experiment service is ready and internal states are fully - // synced with the experiment server. - await this.experimentationService.getTreatmentVariableAsync(EXP_CONFIG_ID, experiment, true); - return true; - } - - const treatmentVariable = await this.experimentationService.getTreatmentVariableAsync( - EXP_CONFIG_ID, - experiment, - true, - ); - return treatmentVariable !== undefined; + return this.inExperimentSync(experiment); } public inExperimentSync(experiment: string): boolean { diff --git a/src/client/extension.ts b/src/client/extension.ts index bfe3d9ab5b62..c49acb1b42d3 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -30,7 +30,7 @@ import { ProgressLocation, ProgressOptions, window } from 'vscode'; import { buildApi, IExtensionApi } from './api'; import { IApplicationShell } from './common/application/types'; import { traceError } from './common/logger'; -import { IAsyncDisposableRegistry, IExtensionContext } from './common/types'; +import { IAsyncDisposableRegistry, IExperimentService, IExtensionContext } from './common/types'; import { createDeferred } from './common/utils/async'; import { Common } from './common/utils/localize'; import { activateComponents } from './extensionActivation'; @@ -103,6 +103,10 @@ async function activateUnsafe( // Note standard utils especially experiment and platform code are fundamental to the extension // and should be available before we activate anything else.Hence register them first. initializeStandard(ext); + // We need to activate experiments before initializing components as objects are created or not created based on experiments. + const experimentService = activatedServiceContainer.get(IExperimentService); + // This guarantees that all experiment information has loaded & all telemetry will contain experiment info. + await experimentService.activate(); const components = await initializeComponents(ext); // Then we finish activating. diff --git a/src/client/extensionActivation.ts b/src/client/extensionActivation.ts index 56aa7b112d70..d9b8561da6d9 100644 --- a/src/client/extensionActivation.ts +++ b/src/client/extensionActivation.ts @@ -123,8 +123,6 @@ async function activateLegacy(ext: ExtensionState): Promise { tensorBoardRegisterTypes(serviceManager); const experimentService = serviceContainer.get(IExperimentService); - // This guarantees that all experiment information has loaded & all telemetry will contain experiment info. - await experimentService.activate(); const workspaceService = serviceContainer.get(IWorkspaceService); const extensions = serviceContainer.get(IExtensions); diff --git a/src/test/common/experiments/service.unit.test.ts b/src/test/common/experiments/service.unit.test.ts index 293be48d79eb..2585bbd0afbe 100644 --- a/src/test/common/experiments/service.unit.test.ts +++ b/src/test/common/experiments/service.unit.test.ts @@ -155,176 +155,6 @@ suite('Experimentation service', () => { }); }); - suite('In-experiment check', () => { - const experiment = 'Test Experiment - experiment'; - let telemetryEvents: { eventName: string; properties: Record }[] = []; - let getTreatmentVariableAsyncStub: sinon.SinonStub; - let sendTelemetryEventStub: sinon.SinonStub; - - setup(() => { - sendTelemetryEventStub = sinon - .stub(Telemetry, 'sendTelemetryEvent') - .callsFake((eventName: string, _, properties: Record) => { - const telemetry = { eventName, properties }; - telemetryEvents.push(telemetry); - }); - - getTreatmentVariableAsyncStub = sinon.stub().returns(Promise.resolve(true)); - sinon.stub(tasClient, 'getExperimentationService').returns(({ - getTreatmentVariableAsync: getTreatmentVariableAsyncStub, - } as unknown) as tasClient.IExperimentationService); - - configureApplicationEnvironment('stable', extensionVersion); - }); - - teardown(() => { - telemetryEvents = []; - }); - - test('If the opt-in and opt-out arrays are empty, return the value from the experimentation framework for a given experiment', async () => { - configureSettings(true, [], []); - - const experimentService = new ExperimentService( - instance(workspaceService), - instance(appEnvironment), - globalMemento, - outputChannel, - ); - const result = await experimentService.inExperiment(experiment); - - assert.isTrue(result); - sinon.assert.notCalled(sendTelemetryEventStub); - sinon.assert.calledOnce(getTreatmentVariableAsyncStub); - }); - - test('If the experiment setting is disabled, inExperiment should return false', async () => { - configureSettings(false, [], []); - - const experimentService = new ExperimentService( - instance(workspaceService), - instance(appEnvironment), - globalMemento, - outputChannel, - ); - const result = await experimentService.inExperiment(experiment); - - assert.isFalse(result); - sinon.assert.notCalled(sendTelemetryEventStub); - sinon.assert.notCalled(getTreatmentVariableAsyncStub); - }); - - test('If the opt-in setting contains "All", inExperiment should return true', async () => { - configureSettings(true, ['All'], []); - - const experimentService = new ExperimentService( - instance(workspaceService), - instance(appEnvironment), - globalMemento, - outputChannel, - ); - const result = await experimentService.inExperiment(experiment); - - assert.isTrue(result); - assert.strictEqual(telemetryEvents.length, 0); - }); - - test('If the opt-in setting contains `All`, inExperiment should check the value cached by the experiment service', async () => { - configureSettings(true, ['All'], []); - - const experimentService = new ExperimentService( - instance(workspaceService), - instance(appEnvironment), - globalMemento, - outputChannel, - ); - const result = await experimentService.inExperiment(experiment); - - assert.isTrue(result); - sinon.assert.notCalled(sendTelemetryEventStub); - sinon.assert.calledOnce(getTreatmentVariableAsyncStub); - }); - - test('If the opt-in setting contains `All` and the experiment setting is disabled, inExperiment should return false', async () => { - configureSettings(false, ['All'], []); - - const experimentService = new ExperimentService( - instance(workspaceService), - instance(appEnvironment), - globalMemento, - outputChannel, - ); - const result = await experimentService.inExperiment(experiment); - - assert.isFalse(result); - sinon.assert.notCalled(sendTelemetryEventStub); - sinon.assert.notCalled(getTreatmentVariableAsyncStub); - }); - - test('If the opt-in setting contains the experiment name, inExperiment should return true', async () => { - configureSettings(true, [experiment], []); - - const experimentService = new ExperimentService( - instance(workspaceService), - instance(appEnvironment), - globalMemento, - outputChannel, - ); - const result = await experimentService.inExperiment(experiment); - - assert.isTrue(result); - assert.strictEqual(telemetryEvents.length, 0); - sinon.assert.calledOnce(getTreatmentVariableAsyncStub); - }); - - test('If the opt-out setting contains "All", inExperiment should return false', async () => { - configureSettings(true, [], ['All']); - - const experimentService = new ExperimentService( - instance(workspaceService), - instance(appEnvironment), - globalMemento, - outputChannel, - ); - const result = await experimentService.inExperiment(experiment); - - assert.isFalse(result); - sinon.assert.notCalled(sendTelemetryEventStub); - sinon.assert.notCalled(getTreatmentVariableAsyncStub); - }); - - test('If the opt-out setting contains "All" and the experiment setting is enabled, inExperiment should return false', async () => { - configureSettings(true, [], ['All']); - - const experimentService = new ExperimentService( - instance(workspaceService), - instance(appEnvironment), - globalMemento, - outputChannel, - ); - const result = await experimentService.inExperiment(experiment); - - assert.isFalse(result); - sinon.assert.notCalled(sendTelemetryEventStub); - sinon.assert.notCalled(getTreatmentVariableAsyncStub); - }); - - test('If the opt-out setting contains the experiment name, inExperiment should return false', async () => { - configureSettings(true, [], [experiment]); - - const experimentService = new ExperimentService( - instance(workspaceService), - instance(appEnvironment), - globalMemento, - outputChannel, - ); - const result = await experimentService.inExperiment(experiment); - - assert.isFalse(result); - assert.strictEqual(telemetryEvents.length, 0); - sinon.assert.notCalled(getTreatmentVariableAsyncStub); - }); - }); - suite('In-experiment-sync check', () => { const experiment = 'Test Experiment - experiment'; let telemetryEvents: { eventName: string; properties: Record }[] = []; From 06fb964dfd0aefc955a38ad478140412379484dd Mon Sep 17 00:00:00 2001 From: Nikolay Kondratyev <4085884+kondratyev-nv@users.noreply.github.com> Date: Fri, 6 Aug 2021 17:38:54 +0100 Subject: [PATCH 03/10] Fix order of default unittest arguments (#16880) * Fix order of default unittest arguments * Add news entry --- news/2 Fixes/16882.md | 2 ++ package.json | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 news/2 Fixes/16882.md diff --git a/news/2 Fixes/16882.md b/news/2 Fixes/16882.md new file mode 100644 index 000000000000..8ba3c1178b3b --- /dev/null +++ b/news/2 Fixes/16882.md @@ -0,0 +1,2 @@ +Fix the order of default unittest arguments. +(thanks [Nikolay Kondratyev](https://github.com/kondratyev-nv/)) diff --git a/package.json b/package.json index f857cd4676ca..8f98089873d6 100644 --- a/package.json +++ b/package.json @@ -1338,11 +1338,11 @@ }, "python.testing.unittestArgs": { "default": [ - "*test*.py", - "-p", - "-s", "-v", - "." + "-s", + ".", + "-p", + "*test*.py" ], "description": "Arguments passed in. Each argument is a separate item in the array.", "items": { From 956c01f4a5babc611167d3d4d60fe0b0b9f30a03 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Fri, 6 Aug 2021 10:02:29 -0700 Subject: [PATCH 04/10] Update version and change log --- CHANGELOG.md | 65 +++++++++++++++++++++++++++++++++++++++++++ news/2 Fixes/16768.md | 1 - news/2 Fixes/16882.md | 2 -- package-lock.json | 2 +- package.json | 2 +- 5 files changed, 67 insertions(+), 5 deletions(-) delete mode 100644 news/2 Fixes/16768.md delete mode 100644 news/2 Fixes/16882.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a94c15e88f4..5ae3e1f485ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,70 @@ # Changelog +## 2021.8.1 (6 August 2021) + +### Fixes + +1. Fix random delay before running python code. + ([#16768](https://github.com/Microsoft/vscode-python/issues/16768)) +1. Fix the order of default unittest arguments. + (thanks [Nikolay Kondratyev](https://github.com/kondratyev-nv/)) + ([#16882](https://github.com/Microsoft/vscode-python/issues/16882)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [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: + [bandit](https://pypi.org/project/bandit/), + [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! + ## 2021.8.0 (5 August 2021) ### Enhancements diff --git a/news/2 Fixes/16768.md b/news/2 Fixes/16768.md deleted file mode 100644 index 9d5177cbef3c..000000000000 --- a/news/2 Fixes/16768.md +++ /dev/null @@ -1 +0,0 @@ -Fix random delay before several actions. diff --git a/news/2 Fixes/16882.md b/news/2 Fixes/16882.md deleted file mode 100644 index 8ba3c1178b3b..000000000000 --- a/news/2 Fixes/16882.md +++ /dev/null @@ -1,2 +0,0 @@ -Fix the order of default unittest arguments. -(thanks [Nikolay Kondratyev](https://github.com/kondratyev-nv/)) diff --git a/package-lock.json b/package-lock.json index 57919a88245f..fc0cb795e77a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "python", - "version": "2021.8.0", + "version": "2021.8.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 8f98089873d6..eaba820e05bb 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "IntelliSense (Pylance), Linting, Debugging (multi-threaded, remote), Jupyter Notebooks, code formatting, refactoring, unit tests, and more.", - "version": "2021.8.0", + "version": "2021.8.1", "featureFlags": { "usingNewInterpreterStorage": true }, From c50532c9bdd1d03d2ca41294ae75e667304bd7ef Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Fri, 13 Aug 2021 10:29:10 -0700 Subject: [PATCH 05/10] Add webpack build for browser entrypoint (#16912) --- .gitignore | 1 + .../webpack.extension.browser.config.js | 74 +++++++++++++++++++ gulpfile.js | 3 + news/1 Enhancements/16869.md | 1 + news/1 Enhancements/16870.md | 1 + package.json | 1 + src/client/browser/extension.ts | 61 +++++++++++++++ tsconfig.browser.json | 6 ++ typings/webworker.fix.d.ts | 8 ++ 9 files changed, 156 insertions(+) create mode 100644 build/webpack/webpack.extension.browser.config.js create mode 100644 news/1 Enhancements/16869.md create mode 100644 news/1 Enhancements/16870.md create mode 100644 src/client/browser/extension.ts create mode 100644 tsconfig.browser.json create mode 100644 typings/webworker.fix.d.ts diff --git a/.gitignore b/.gitignore index a343e9929c81..eb8b02d00b17 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ debugpy*.log pydevd*.log nodeLanguageServer/** nodeLanguageServer.*/** +dist/** diff --git a/build/webpack/webpack.extension.browser.config.js b/build/webpack/webpack.extension.browser.config.js new file mode 100644 index 000000000000..91e00eb7845a --- /dev/null +++ b/build/webpack/webpack.extension.browser.config.js @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// @ts-check + +'use strict'; + +const path = require('path'); + +const packageRoot = path.resolve(__dirname, '..', '..'); +const outDir = path.resolve(packageRoot, 'dist'); + +/** @type {(env: any, argv: { mode: 'production' | 'development' | 'none' }) => import('webpack').Configuration} */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const nodeConfig = (_, { mode }) => ({ + context: packageRoot, + entry: { + extension: './src/client/browser/extension.ts', + }, + target: 'webworker', + output: { + filename: '[name].browser.js', + path: outDir, + libraryTarget: 'commonjs2', + devtoolModuleFilenameTemplate: '../../[resource-path]', + }, + devtool: 'source-map', + // stats: { + // all: false, + // errors: true, + // warnings: true, + // }, + resolve: { + extensions: ['.ts', '.js'], + }, + externals: { + vscode: 'commonjs vscode', + + // These dependencies are ignored because we don't use them, and App Insights has try-catch protecting their loading if they don't exist + // See: https://github.com/microsoft/vscode-extension-telemetry/issues/41#issuecomment-598852991 + 'applicationinsights-native-metrics': 'commonjs applicationinsights-native-metrics', + '@opentelemetry/tracing': 'commonjs @opentelemetry/tracing', + }, + module: { + rules: [ + { + test: /\.ts$/, + loader: 'ts-loader', + options: { + configFile: 'tsconfig.browser.json', + }, + }, + { + test: /\.node$/, + loader: 'node-loader', + }, + ], + }, + // optimization: { + // usedExports: true, + // splitChunks: { + // cacheGroups: { + // defaultVendors: { + // name: 'vendor', + // test: /[\\/]node_modules[\\/]/, + // chunks: 'all', + // priority: -10, + // }, + // }, + // }, + // }, +}); + +module.exports = nodeConfig; diff --git a/gulpfile.js b/gulpfile.js index 3b5717479855..5978e8c47d40 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -75,6 +75,7 @@ gulp.task('webpack', async () => { await buildWebPackForDevOrProduction('./build/webpack/webpack.extension.dependencies.config.js', 'production'); await buildWebPackForDevOrProduction('./build/webpack/webpack.startPage-ui-viewers.config.js', 'production'); await buildWebPackForDevOrProduction('./build/webpack/webpack.extension.config.js', 'extension'); + await buildWebPackForDevOrProduction('./build/webpack/webpack.extension.browser.config.js', 'browser'); }); gulp.task('addExtensionPackDependencies', async () => { @@ -199,6 +200,8 @@ function getAllowedWarningsForWebPack(buildConfig) { 'WARNING in ./node_modules/diagnostic-channel-publishers/dist/src/azure-coretracing.pub.js', 'WARNING in ./node_modules/applicationinsights/out/AutoCollection/NativePerformance.js', ]; + case 'browser': + return []; default: throw new Error('Unknown WebPack Configuration'); } diff --git a/news/1 Enhancements/16869.md b/news/1 Enhancements/16869.md new file mode 100644 index 000000000000..ad850baf77dc --- /dev/null +++ b/news/1 Enhancements/16869.md @@ -0,0 +1 @@ +Add a basic web extension bundle. diff --git a/news/1 Enhancements/16870.md b/news/1 Enhancements/16870.md new file mode 100644 index 000000000000..f3fcc9ff7fc4 --- /dev/null +++ b/news/1 Enhancements/16870.md @@ -0,0 +1 @@ +Add basic Pylance support to the web extension. diff --git a/package.json b/package.json index eaba820e05bb..b106edf0879e 100644 --- a/package.json +++ b/package.json @@ -102,6 +102,7 @@ "workspaceContains:app.py" ], "main": "./out/client/extension", + "browser": "./dist/extension.browser.js", "contributes": { "walkthroughs": [ { diff --git a/src/client/browser/extension.ts b/src/client/browser/extension.ts new file mode 100644 index 000000000000..4164bc7f3e7f --- /dev/null +++ b/src/client/browser/extension.ts @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import * as vscode from 'vscode'; +import { LanguageClientOptions } from 'vscode-languageclient'; +import { LanguageClient } from 'vscode-languageclient/browser'; +import { ILSExtensionApi } from '../activation/node/languageServerFolderService'; +import { PYLANCE_EXTENSION_ID } from '../common/constants'; + +interface BrowserConfig { + distUrl: string; // URL to Pylance's dist folder. +} + +export async function activate(context: vscode.ExtensionContext): Promise { + // Run in a promise and return early so that VS Code can go activate Pylance. + runPylance(context); +} + +async function runPylance(context: vscode.ExtensionContext): Promise { + const pylanceExtension = vscode.extensions.getExtension(PYLANCE_EXTENSION_ID); + const pylanceApi = await pylanceExtension?.activate(); + if (!pylanceApi?.languageServerFolder) { + throw new Error('Could not find Pylance extension'); + } + + const { path: distUrl } = await pylanceApi.languageServerFolder(); + + try { + const worker = new Worker(`${distUrl}/browser.server.bundle.js`); + + // Pass the configuration as the first message to the worker so it can + // have info like the URL of the dist folder early enough. + // + // This is the same method used by the TS worker: + // https://github.com/microsoft/vscode/blob/90aa979bb75a795fd8c33d38aee263ea655270d0/extensions/typescript-language-features/src/tsServer/serverProcess.browser.ts#L55 + const config: BrowserConfig = { + distUrl, + }; + worker.postMessage(config); + + const clientOptions: LanguageClientOptions = { + // Register the server for python source files. + documentSelector: [ + { + language: 'python', + }, + ], + synchronize: { + // Synchronize the setting section to the server. + configurationSection: ['python'], + }, + }; + + const languageClient = new LanguageClient('python', 'Python Language Server', clientOptions, worker); + const disposable = languageClient.start(); + + context.subscriptions.push(disposable); + } catch (e) { + console.log(e); + } +} diff --git a/tsconfig.browser.json b/tsconfig.browser.json new file mode 100644 index 000000000000..d4aae7e98208 --- /dev/null +++ b/tsconfig.browser.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "include": [ + "./src/client/browser" + ] +} diff --git a/typings/webworker.fix.d.ts b/typings/webworker.fix.d.ts new file mode 100644 index 000000000000..80b53fb5b2e3 --- /dev/null +++ b/typings/webworker.fix.d.ts @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// Fake interfaces that are required for web workers to work around +// tsconfig's DOM and WebWorker lib options being mutally exclusive. +// https://github.com/microsoft/TypeScript/issues/20595 + +interface DedicatedWorkerGlobalScope {} From 896d7fc798ee199919cb51fb33136c39823de409 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Wed, 18 Aug 2021 10:38:53 -0700 Subject: [PATCH 06/10] Get telemetry client and LS middleware working in the browser (#17010) --- .vscode/settings.json | 2 + build/webpack/webpack.extension.config.js | 10 +- gulpfile.js | 6 +- news/3 Code Health/16871.md | 1 + news/3 Code Health/16872.md | 1 + package-lock.json | 87 +--- package.json | 2 +- .../activation/languageClientMiddleware.ts | 417 +---------------- .../languageClientMiddlewareBase.ts | 425 ++++++++++++++++++ src/client/browser/extension.ts | 144 +++++- src/client/telemetry/index.ts | 4 +- 11 files changed, 607 insertions(+), 492 deletions(-) create mode 100644 news/3 Code Health/16871.md create mode 100644 news/3 Code Health/16872.md create mode 100644 src/client/activation/languageClientMiddlewareBase.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index de42fdbe8eb1..0c37edaa8f23 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,6 +2,7 @@ { "files.exclude": { "out": true, // set this to true to hide the "out" folder with the compiled JS files + "dist": true, "**/*.pyc": true, ".nyc_output": true, "obj": true, @@ -15,6 +16,7 @@ }, "search.exclude": { "out": true, // set this to false to include "out" folder in search results + "dist": true, "**/node_modules": true, "coverage": true, "languageServer*/**": true, diff --git a/build/webpack/webpack.extension.config.js b/build/webpack/webpack.extension.config.js index 1a936313f113..fe56c70bf15f 100644 --- a/build/webpack/webpack.extension.config.js +++ b/build/webpack/webpack.extension.config.js @@ -53,7 +53,15 @@ const config = { }, ], }, - externals: ['vscode', 'commonjs', ...existingModulesInOutDir], + externals: [ + 'vscode', + 'commonjs', + ...existingModulesInOutDir, + // These dependencies are ignored because we don't use them, and App Insights has try-catch protecting their loading if they don't exist + // See: https://github.com/microsoft/vscode-extension-telemetry/issues/41#issuecomment-598852991 + 'applicationinsights-native-metrics', + '@opentelemetry/tracing', + ], plugins: [...common.getDefaultPlugins('extension')], resolve: { alias: { diff --git a/gulpfile.js b/gulpfile.js index 5978e8c47d40..bd9a4e0b79ee 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -201,7 +201,11 @@ function getAllowedWarningsForWebPack(buildConfig) { 'WARNING in ./node_modules/applicationinsights/out/AutoCollection/NativePerformance.js', ]; case 'browser': - return []; + return [ + 'WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).', + 'WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.', + 'WARNING in webpack performance recommendations:', + ]; default: throw new Error('Unknown WebPack Configuration'); } diff --git a/news/3 Code Health/16871.md b/news/3 Code Health/16871.md new file mode 100644 index 000000000000..6e6eef269d55 --- /dev/null +++ b/news/3 Code Health/16871.md @@ -0,0 +1 @@ +Update telemetry client to support browser, plumb to Pylance. diff --git a/news/3 Code Health/16872.md b/news/3 Code Health/16872.md new file mode 100644 index 000000000000..7ba6cba6c200 --- /dev/null +++ b/news/3 Code Health/16872.md @@ -0,0 +1 @@ +Refactor language server middleware to work in the browser. diff --git a/package-lock.json b/package-lock.json index fc0cb795e77a..7683c2b0c567 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1612,17 +1612,6 @@ "default-require-extensions": "^3.0.0" } }, - "applicationinsights": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/applicationinsights/-/applicationinsights-1.7.4.tgz", - "integrity": "sha512-XFLsNlcanpjFhHNvVWEfcm6hr7lu9znnb6Le1Lk5RE03YUV9X2B2n2MfM4kJZRrUdV+C0hdHxvWyv+vWoLfY7A==", - "requires": { - "cls-hooked": "^4.2.2", - "continuation-local-storage": "^3.2.1", - "diagnostic-channel": "0.2.0", - "diagnostic-channel-publishers": "^0.3.3" - } - }, "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", @@ -2306,29 +2295,12 @@ "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", "dev": true }, - "async-hook-jl": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/async-hook-jl/-/async-hook-jl-1.7.6.tgz", - "integrity": "sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg==", - "requires": { - "stack-chain": "^1.3.7" - } - }, "async-limiter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", "dev": true }, - "async-listener": { - "version": "0.6.10", - "resolved": "https://registry.npmjs.org/async-listener/-/async-listener-0.6.10.tgz", - "integrity": "sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw==", - "requires": { - "semver": "^5.3.0", - "shimmer": "^1.1.0" - } - }, "async-settle": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", @@ -4007,16 +3979,6 @@ } } }, - "cls-hooked": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/cls-hooked/-/cls-hooked-4.2.2.tgz", - "integrity": "sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw==", - "requires": { - "async-hook-jl": "^1.7.6", - "emitter-listener": "^1.0.1", - "semver": "^5.4.1" - } - }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -4323,15 +4285,6 @@ "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", "dev": true }, - "continuation-local-storage": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz", - "integrity": "sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA==", - "requires": { - "async-listener": "^0.6.0", - "emitter-listener": "^1.1.1" - } - }, "convert-source-map": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", @@ -5435,19 +5388,6 @@ "debug": "^2.6.0" } }, - "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.3.4", - "resolved": "https://registry.npmjs.org/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.3.4.tgz", - "integrity": "sha512-SZ1zMfFiEabf4Qx0Og9V1gMsRoqz3O+5ENkVcNOfI+SMJ3QhQsdEoKX99r0zvreagXot2parPxmrwwUM/ja8ug==" - }, "diagnostics": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", @@ -5725,14 +5665,6 @@ } } }, - "emitter-listener": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz", - "integrity": "sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==", - "requires": { - "shimmer": "^1.2.0" - } - }, "emoji-regex": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", @@ -16491,11 +16423,6 @@ } } }, - "shimmer": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", - "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" - }, "shortid": { "version": "2.2.14", "resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.14.tgz", @@ -17055,11 +16982,6 @@ "figgy-pudding": "^3.5.1" } }, - "stack-chain": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/stack-chain/-/stack-chain-1.3.7.tgz", - "integrity": "sha1-0ZLJ/06moiyUxN1FkXHj8AzqEoU=" - }, "stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -19424,12 +19346,9 @@ "integrity": "sha512-+OMm11R1bGYbpIJ5eQIkwoDGFF4GvBz3Ztl6/VM+/RNNb2Gjk2c0Ku+oMmfhlTmTlPCpgHBsH4JqVCbUYhu5bA==" }, "vscode-extension-telemetry": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/vscode-extension-telemetry/-/vscode-extension-telemetry-0.1.4.tgz", - "integrity": "sha512-9U2pUZ/YwZBfA8CkBrHwMxjnq9Ab+ng8daJWJzEQ6CAxlZyRhmck23bx2lqqpEwGWJCiuceQy4k0Me6llEB4zw==", - "requires": { - "applicationinsights": "1.7.4" - } + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/vscode-extension-telemetry/-/vscode-extension-telemetry-0.2.8.tgz", + "integrity": "sha512-Vf52im5qzORRD2K5Ryp8PXo31YXVcJAYRSDDZGegWlt0OATOd83DYabS1U/WIq9nR5g80UQKH3+BsenhpQHUaA==" }, "vscode-jsonrpc": { "version": "6.0.0", diff --git a/package.json b/package.json index b106edf0879e..9616145d5436 100644 --- a/package.json +++ b/package.json @@ -2265,7 +2265,7 @@ "untildify": "^3.0.2", "vscode-debugadapter": "^1.28.0", "vscode-debugprotocol": "^1.28.0", - "vscode-extension-telemetry": "0.1.4", + "vscode-extension-telemetry": "0.2.8", "vscode-jsonrpc": "6.0.0", "vscode-languageclient": "7.0.0", "vscode-languageserver": "7.0.0", diff --git a/src/client/activation/languageClientMiddleware.ts b/src/client/activation/languageClientMiddleware.ts index 72cc91d311af..e7a79c99f2fe 100644 --- a/src/client/activation/languageClientMiddleware.ts +++ b/src/client/activation/languageClientMiddleware.ts @@ -1,137 +1,38 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import * as path from 'path'; -import { - CancellationToken, - CodeAction, - CodeLens, - Command, - CompletionItem, - CompletionList, - Declaration as VDeclaration, - Definition, - DefinitionLink, - Diagnostic, - DocumentHighlight, - DocumentLink, - DocumentSymbol, - Location, - ProviderResult, - Range, - SymbolInformation, - TextEdit, - Uri, - WorkspaceEdit, -} from 'vscode'; -import { - ConfigurationParams, - ConfigurationRequest, - HandleDiagnosticsSignature, - LanguageClient, - Middleware, - ResponseError, -} from 'vscode-languageclient/node'; -import { IJupyterExtensionDependencyManager, IVSCodeNotebook } from '../common/application/types'; -import { HiddenFilePrefix, PYTHON_LANGUAGE } from '../common/constants'; +import { LanguageClient } from 'vscode-languageclient/node'; +import { IJupyterExtensionDependencyManager, IVSCodeNotebook } from '../common/application/types'; +import { PYTHON_LANGUAGE } from '../common/constants'; import { IFileSystem } from '../common/platform/types'; -import { IConfigurationService, IDisposableRegistry, IExtensions } from '../common/types'; -import { isThenable } from '../common/utils/async'; -import { StopWatch } from '../common/utils/stopWatch'; -import { IEnvironmentVariablesProvider } from '../common/variables/types'; +import { IDisposableRegistry, IExtensions } from '../common/types'; import { IServiceContainer } from '../ioc/types'; import { NotebookMiddlewareAddon } from '../jupyter/languageserver/notebookMiddlewareAddon'; import { sendTelemetryEvent } from '../telemetry'; -import { EventName } from '../telemetry/constants'; -import { LanguageServerType } from './types'; - -// Only send 100 events per hour. -const globalDebounce = 1000 * 60 * 60; -const globalLimit = 100; - -// For calls that are more likely to happen during a session (hover, completion, document symbols). -const debounceFrequentCall = 1000 * 60 * 5; - -// For calls that are less likely to happen during a session (go-to-def, workspace symbols). -const debounceRareCall = 1000 * 60; - -export class LanguageClientMiddleware implements Middleware { - // These are public so that the captureTelemetryForLSPMethod decorator can access them. - public readonly eventName: EventName | undefined; - public readonly lastCaptured = new Map(); - public nextWindow: number = 0; - public eventCount: number = 0; - - public workspace = { - configuration: async ( - params: ConfigurationParams, - token: CancellationToken, - next: ConfigurationRequest.HandlerSignature, - ) => { - const configService = this.serviceContainer.get(IConfigurationService); - const envService = this.serviceContainer.get(IEnvironmentVariablesProvider); - let settings = next(params, token); - if (isThenable(settings)) { - settings = await settings; - } - if (settings instanceof ResponseError) { - return settings; - } - - for (const [i, item] of params.items.entries()) { - if (item.section === 'python') { - const uri = item.scopeUri ? Uri.parse(item.scopeUri) : undefined; - // For backwards compatibility, set python.pythonPath to the configured - // value as though it were in the user's settings.json file. - settings[i].pythonPath = configService.getSettings(uri).pythonPath; - - const env = await envService.getEnvironmentVariables(uri); - const envPYTHONPATH = env.PYTHONPATH; - if (envPYTHONPATH) { - settings[i]._envPYTHONPATH = envPYTHONPATH; - } - } - } - - return settings; - }, - }; - private notebookAddon: NotebookMiddlewareAddon | undefined; - - private connected = false; // Default to not forwarding to VS code. +import { LanguageClientMiddlewareBase } from './languageClientMiddlewareBase'; +import { LanguageServerType } from './types'; +export class LanguageClientMiddleware extends LanguageClientMiddlewareBase { public constructor( - readonly serviceContainer: IServiceContainer, + serviceContainer: IServiceContainer, serverType: LanguageServerType, getClient: () => LanguageClient | undefined, - public readonly serverVersion?: string, + serverVersion?: string, ) { - this.handleDiagnostics = this.handleDiagnostics.bind(this); // VS Code calls function without context. - this.didOpen = this.didOpen.bind(this); - this.didSave = this.didSave.bind(this); - this.didChange = this.didChange.bind(this); - this.didClose = this.didClose.bind(this); - this.willSave = this.willSave.bind(this); - this.willSaveWaitUntil = this.willSaveWaitUntil.bind(this); + super(serviceContainer, serverType, sendTelemetryEvent, serverVersion); - if (serverType === LanguageServerType.Microsoft) { - this.eventName = EventName.PYTHON_LANGUAGE_SERVER_REQUEST; - } else if (serverType === LanguageServerType.Node) { - this.eventName = EventName.LANGUAGE_SERVER_REQUEST; - } else if (serverType === LanguageServerType.JediLSP) { - this.eventName = EventName.JEDI_LANGUAGE_SERVER_REQUEST; - } else { + if (serverType === LanguageServerType.None || serverType === LanguageServerType.Jedi) { return; } - const jupyterDependencyManager = this.serviceContainer.get( + const jupyterDependencyManager = serviceContainer.get( IJupyterExtensionDependencyManager, ); - const notebookApi = this.serviceContainer.get(IVSCodeNotebook); - const disposables = this.serviceContainer.get(IDisposableRegistry) || []; - const extensions = this.serviceContainer.get(IExtensions); - const fileSystem = this.serviceContainer.get(IFileSystem); + const notebookApi = serviceContainer.get(IVSCodeNotebook); + const disposables = serviceContainer.get(IDisposableRegistry) || []; + const extensions = serviceContainer.get(IExtensions); + const fileSystem = serviceContainer.get(IFileSystem); // Enable notebook support if jupyter support is installed if (jupyterDependencyManager && jupyterDependencyManager.isJupyterExtensionInstalled) { @@ -161,290 +62,4 @@ export class LanguageClientMiddleware implements Middleware { }), ); } - - public connect() { - this.connected = true; - } - - public disconnect() { - this.connected = false; - } - - public didChange() { - if (this.connected) { - return this.callNext('didChange', arguments); - } - } - - public didOpen() { - // Special case, open and close happen before we connect. - return this.callNext('didOpen', arguments); - } - - public didClose() { - // Special case, open and close happen before we connect. - return this.callNext('didClose', arguments); - } - - public didSave() { - if (this.connected) { - return this.callNext('didSave', arguments); - } - } - - public willSave() { - if (this.connected) { - return this.callNext('willSave', arguments); - } - } - - public willSaveWaitUntil() { - if (this.connected) { - return this.callNext('willSaveWaitUntil', arguments); - } - } - - @captureTelemetryForLSPMethod( - 'textDocument/completion', - debounceFrequentCall, - LanguageClientMiddleware.completionLengthMeasure, - ) - public provideCompletionItem() { - if (this.connected) { - return this.callNext('provideCompletionItem', arguments); - } - } - - private static completionLengthMeasure( - _obj: LanguageClientMiddleware, - result: CompletionItem[] | CompletionList, - ): Record { - const resultLength = Array.isArray(result) ? result.length : result.items.length; - return { resultLength }; - } - - @captureTelemetryForLSPMethod('textDocument/hover', debounceFrequentCall) - public provideHover() { - if (this.connected) { - return this.callNext('provideHover', arguments); - } - } - - public handleDiagnostics(uri: Uri, _diagnostics: Diagnostic[], _next: HandleDiagnosticsSignature) { - if (this.connected) { - // Skip sending if this is a special file. - const filePath = uri.fsPath; - const baseName = filePath ? path.basename(filePath) : undefined; - if (!baseName || !baseName.startsWith(HiddenFilePrefix)) { - return this.callNext('handleDiagnostics', arguments); - } - } - } - - @captureTelemetryForLSPMethod('completionItem/resolve', debounceFrequentCall) - public resolveCompletionItem(): ProviderResult { - if (this.connected) { - return this.callNext('resolveCompletionItem', arguments); - } - } - - @captureTelemetryForLSPMethod('textDocument/signatureHelp', debounceFrequentCall) - public provideSignatureHelp() { - if (this.connected) { - return this.callNext('provideSignatureHelp', arguments); - } - } - - @captureTelemetryForLSPMethod('textDocument/definition', debounceRareCall) - public provideDefinition(): ProviderResult { - if (this.connected) { - return this.callNext('provideDefinition', arguments); - } - } - - @captureTelemetryForLSPMethod('textDocument/references', debounceRareCall) - public provideReferences(): ProviderResult { - if (this.connected) { - return this.callNext('provideReferences', arguments); - } - } - - public provideDocumentHighlights(): ProviderResult { - if (this.connected) { - return this.callNext('provideDocumentHighlights', arguments); - } - } - - @captureTelemetryForLSPMethod('textDocument/documentSymbol', debounceFrequentCall) - public provideDocumentSymbols(): ProviderResult { - if (this.connected) { - return this.callNext('provideDocumentSymbols', arguments); - } - } - - @captureTelemetryForLSPMethod('workspace/symbol', debounceRareCall) - public provideWorkspaceSymbols(): ProviderResult { - if (this.connected) { - return this.callNext('provideWorkspaceSymbols', arguments); - } - } - - @captureTelemetryForLSPMethod('textDocument/codeAction', debounceFrequentCall) - public provideCodeActions(): ProviderResult<(Command | CodeAction)[]> { - if (this.connected) { - return this.callNext('provideCodeActions', arguments); - } - } - - @captureTelemetryForLSPMethod('textDocument/codeLens', debounceFrequentCall) - public provideCodeLenses(): ProviderResult { - if (this.connected) { - return this.callNext('provideCodeLenses', arguments); - } - } - - @captureTelemetryForLSPMethod('codeLens/resolve', debounceFrequentCall) - public resolveCodeLens(): ProviderResult { - if (this.connected) { - return this.callNext('resolveCodeLens', arguments); - } - } - - public provideDocumentFormattingEdits(): ProviderResult { - if (this.connected) { - return this.callNext('provideDocumentFormattingEdits', arguments); - } - } - - public provideDocumentRangeFormattingEdits(): ProviderResult { - if (this.connected) { - return this.callNext('provideDocumentRangeFormattingEdits', arguments); - } - } - - public provideOnTypeFormattingEdits(): ProviderResult { - if (this.connected) { - return this.callNext('provideOnTypeFormattingEdits', arguments); - } - } - - @captureTelemetryForLSPMethod('textDocument/rename', debounceRareCall) - public provideRenameEdits(): ProviderResult { - if (this.connected) { - return this.callNext('provideRenameEdits', arguments); - } - } - - @captureTelemetryForLSPMethod('textDocument/prepareRename', debounceRareCall) - public prepareRename(): ProviderResult< - | Range - | { - range: Range; - placeholder: string; - } - > { - if (this.connected) { - return this.callNext('prepareRename', arguments); - } - } - - public provideDocumentLinks(): ProviderResult { - if (this.connected) { - return this.callNext('provideDocumentLinks', arguments); - } - } - - public resolveDocumentLink(): ProviderResult { - if (this.connected) { - return this.callNext('resolveDocumentLink', arguments); - } - } - - @captureTelemetryForLSPMethod('textDocument/declaration', debounceRareCall) - public provideDeclaration(): ProviderResult { - if (this.connected) { - return this.callNext('provideDeclaration', arguments); - } - } - - private callNext(funcName: keyof NotebookMiddlewareAddon, args: IArguments) { - // This function uses the last argument to call the 'next' item. If we're allowing notebook - // middleware, it calls into the notebook middleware first. - if (this.notebookAddon) { - // It would be nice to use args.callee, but not supported in strict mode - - return (this.notebookAddon as any)[funcName](...args); - } else { - return args[args.length - 1](...args); - } - } -} - -function captureTelemetryForLSPMethod( - method: string, - debounceMilliseconds: number, - lazyMeasures?: (this_: any, result: any) => Record, -) { - return function (_target: Object, _propertyKey: string, descriptor: TypedPropertyDescriptor) { - const originalMethod = descriptor.value; - - descriptor.value = function (this: LanguageClientMiddleware, ...args: any[]) { - const eventName = this.eventName; - if (!eventName) { - return originalMethod.apply(this, args); - } - - const now = Date.now(); - - if (now > this.nextWindow) { - // Past the end of the last window, reset. - this.nextWindow = now + globalDebounce; - this.eventCount = 0; - } else if (this.eventCount >= globalLimit) { - // Sent too many events in this window, don't send. - return originalMethod.apply(this, args); - } - - const lastCapture = this.lastCaptured.get(method); - if (lastCapture && now - lastCapture < debounceMilliseconds) { - return originalMethod.apply(this, args); - } - - this.lastCaptured.set(method, now); - this.eventCount += 1; - - // Replace all slashes in the method name so it doesn't get scrubbed by vscode-extension-telemetry. - const formattedMethod = method.replace(/\//g, '.'); - - const properties = { - lsVersion: this.serverVersion || 'unknown', - method: formattedMethod, - }; - - const stopWatch = new StopWatch(); - const sendTelemetry = (result: any) => { - let measures: number | Record = stopWatch.elapsedTime; - if (lazyMeasures) { - measures = { - duration: measures, - ...lazyMeasures(this, result), - }; - } - sendTelemetryEvent(eventName, measures, properties); - return result; - }; - - let result = originalMethod.apply(this, args); - - if (isThenable(result)) { - return result.then(sendTelemetry); - } - - sendTelemetry(result); - - return result; - }; - - return descriptor; - }; } diff --git a/src/client/activation/languageClientMiddlewareBase.ts b/src/client/activation/languageClientMiddlewareBase.ts new file mode 100644 index 000000000000..6a6949659c12 --- /dev/null +++ b/src/client/activation/languageClientMiddlewareBase.ts @@ -0,0 +1,425 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +import * as path from 'path'; +import { + CancellationToken, + CodeAction, + CodeLens, + Command, + CompletionItem, + CompletionList, + Declaration as VDeclaration, + Definition, + DefinitionLink, + Diagnostic, + DocumentHighlight, + DocumentLink, + DocumentSymbol, + Location, + ProviderResult, + Range, + SymbolInformation, + TextEdit, + Uri, + WorkspaceEdit, +} from 'vscode'; +import { + ConfigurationParams, + ConfigurationRequest, + HandleDiagnosticsSignature, + Middleware, + ResponseError, +} from 'vscode-languageclient'; + +import type { NotebookMiddlewareAddon } from '../jupyter/languageserver/notebookMiddlewareAddon'; +import { HiddenFilePrefix } from '../common/constants'; +import { IConfigurationService } from '../common/types'; +import { isThenable } from '../common/utils/async'; +import { StopWatch } from '../common/utils/stopWatch'; +import { IEnvironmentVariablesProvider } from '../common/variables/types'; +import { IServiceContainer } from '../ioc/types'; +import { EventName } from '../telemetry/constants'; +import { LanguageServerType } from './types'; + +// Only send 100 events per hour. +const globalDebounce = 1000 * 60 * 60; +const globalLimit = 100; + +// For calls that are more likely to happen during a session (hover, completion, document symbols). +const debounceFrequentCall = 1000 * 60 * 5; + +// For calls that are less likely to happen during a session (go-to-def, workspace symbols). +const debounceRareCall = 1000 * 60; + +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/* eslint-disable prefer-rest-params */ +/* eslint-disable consistent-return */ +/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +interface SendTelemetryEventFunc { + (eventName: EventName, measuresOrDurationMs?: Record | number, properties?: any, ex?: Error): void; +} + +export class LanguageClientMiddlewareBase implements Middleware { + // These are public so that the captureTelemetryForLSPMethod decorator can access them. + public readonly eventName: EventName | undefined; + + public readonly lastCaptured = new Map(); + + public nextWindow = 0; + + public eventCount = 0; + + public workspace = { + configuration: async ( + params: ConfigurationParams, + token: CancellationToken, + next: ConfigurationRequest.HandlerSignature, + ) => { + if (!this.serviceContainer) { + return next(params, token); + } + + const configService = this.serviceContainer.get(IConfigurationService); + const envService = this.serviceContainer.get(IEnvironmentVariablesProvider); + + let settings = next(params, token); + if (isThenable(settings)) { + settings = await settings; + } + if (settings instanceof ResponseError) { + return settings; + } + + for (const [i, item] of params.items.entries()) { + if (item.section === 'python') { + const uri = item.scopeUri ? Uri.parse(item.scopeUri) : undefined; + // For backwards compatibility, set python.pythonPath to the configured + // value as though it were in the user's settings.json file. + settings[i].pythonPath = configService.getSettings(uri).pythonPath; + + const env = await envService.getEnvironmentVariables(uri); + const envPYTHONPATH = env.PYTHONPATH; + if (envPYTHONPATH) { + settings[i]._envPYTHONPATH = envPYTHONPATH; + } + } + } + + return settings; + }, + }; + + protected notebookAddon: NotebookMiddlewareAddon | undefined; + + private connected = false; // Default to not forwarding to VS code. + + public constructor( + readonly serviceContainer: IServiceContainer | undefined, + serverType: LanguageServerType, + public readonly sendTelemetryEventFunc: SendTelemetryEventFunc, + public readonly serverVersion?: string, + ) { + this.handleDiagnostics = this.handleDiagnostics.bind(this); // VS Code calls function without context. + this.didOpen = this.didOpen.bind(this); + this.didSave = this.didSave.bind(this); + this.didChange = this.didChange.bind(this); + this.didClose = this.didClose.bind(this); + this.willSave = this.willSave.bind(this); + this.willSaveWaitUntil = this.willSaveWaitUntil.bind(this); + + if (serverType === LanguageServerType.Microsoft) { + this.eventName = EventName.PYTHON_LANGUAGE_SERVER_REQUEST; + } else if (serverType === LanguageServerType.Node) { + this.eventName = EventName.LANGUAGE_SERVER_REQUEST; + } else if (serverType === LanguageServerType.JediLSP) { + this.eventName = EventName.JEDI_LANGUAGE_SERVER_REQUEST; + } + } + + public connect() { + this.connected = true; + } + + public disconnect() { + this.connected = false; + } + + public didChange() { + if (this.connected) { + return this.callNext('didChange', arguments); + } + } + + public didOpen() { + // Special case, open and close happen before we connect. + return this.callNext('didOpen', arguments); + } + + public didClose() { + // Special case, open and close happen before we connect. + return this.callNext('didClose', arguments); + } + + public didSave() { + if (this.connected) { + return this.callNext('didSave', arguments); + } + } + + public willSave() { + if (this.connected) { + return this.callNext('willSave', arguments); + } + } + + public willSaveWaitUntil() { + if (this.connected) { + return this.callNext('willSaveWaitUntil', arguments); + } + } + + @captureTelemetryForLSPMethod( + 'textDocument/completion', + debounceFrequentCall, + LanguageClientMiddlewareBase.completionLengthMeasure, + ) + public provideCompletionItem() { + if (this.connected) { + return this.callNext('provideCompletionItem', arguments); + } + } + + private static completionLengthMeasure( + _obj: LanguageClientMiddlewareBase, + result: CompletionItem[] | CompletionList, + ): Record { + const resultLength = Array.isArray(result) ? result.length : result.items.length; + return { resultLength }; + } + + @captureTelemetryForLSPMethod('textDocument/hover', debounceFrequentCall) + public provideHover() { + if (this.connected) { + return this.callNext('provideHover', arguments); + } + } + + public handleDiagnostics(uri: Uri, _diagnostics: Diagnostic[], _next: HandleDiagnosticsSignature) { + if (this.connected) { + // Skip sending if this is a special file. + const filePath = uri.fsPath; + const baseName = filePath ? path.basename(filePath) : undefined; + if (!baseName || !baseName.startsWith(HiddenFilePrefix)) { + return this.callNext('handleDiagnostics', arguments); + } + } + } + + @captureTelemetryForLSPMethod('completionItem/resolve', debounceFrequentCall) + public resolveCompletionItem(): ProviderResult { + if (this.connected) { + return this.callNext('resolveCompletionItem', arguments); + } + } + + @captureTelemetryForLSPMethod('textDocument/signatureHelp', debounceFrequentCall) + public provideSignatureHelp() { + if (this.connected) { + return this.callNext('provideSignatureHelp', arguments); + } + } + + @captureTelemetryForLSPMethod('textDocument/definition', debounceRareCall) + public provideDefinition(): ProviderResult { + if (this.connected) { + return this.callNext('provideDefinition', arguments); + } + } + + @captureTelemetryForLSPMethod('textDocument/references', debounceRareCall) + public provideReferences(): ProviderResult { + if (this.connected) { + return this.callNext('provideReferences', arguments); + } + } + + public provideDocumentHighlights(): ProviderResult { + if (this.connected) { + return this.callNext('provideDocumentHighlights', arguments); + } + } + + @captureTelemetryForLSPMethod('textDocument/documentSymbol', debounceFrequentCall) + public provideDocumentSymbols(): ProviderResult { + if (this.connected) { + return this.callNext('provideDocumentSymbols', arguments); + } + } + + @captureTelemetryForLSPMethod('workspace/symbol', debounceRareCall) + public provideWorkspaceSymbols(): ProviderResult { + if (this.connected) { + return this.callNext('provideWorkspaceSymbols', arguments); + } + } + + @captureTelemetryForLSPMethod('textDocument/codeAction', debounceFrequentCall) + public provideCodeActions(): ProviderResult<(Command | CodeAction)[]> { + if (this.connected) { + return this.callNext('provideCodeActions', arguments); + } + } + + @captureTelemetryForLSPMethod('textDocument/codeLens', debounceFrequentCall) + public provideCodeLenses(): ProviderResult { + if (this.connected) { + return this.callNext('provideCodeLenses', arguments); + } + } + + @captureTelemetryForLSPMethod('codeLens/resolve', debounceFrequentCall) + public resolveCodeLens(): ProviderResult { + if (this.connected) { + return this.callNext('resolveCodeLens', arguments); + } + } + + public provideDocumentFormattingEdits(): ProviderResult { + if (this.connected) { + return this.callNext('provideDocumentFormattingEdits', arguments); + } + } + + public provideDocumentRangeFormattingEdits(): ProviderResult { + if (this.connected) { + return this.callNext('provideDocumentRangeFormattingEdits', arguments); + } + } + + public provideOnTypeFormattingEdits(): ProviderResult { + if (this.connected) { + return this.callNext('provideOnTypeFormattingEdits', arguments); + } + } + + @captureTelemetryForLSPMethod('textDocument/rename', debounceRareCall) + public provideRenameEdits(): ProviderResult { + if (this.connected) { + return this.callNext('provideRenameEdits', arguments); + } + } + + @captureTelemetryForLSPMethod('textDocument/prepareRename', debounceRareCall) + public prepareRename(): ProviderResult< + | Range + | { + range: Range; + placeholder: string; + } + > { + if (this.connected) { + return this.callNext('prepareRename', arguments); + } + } + + public provideDocumentLinks(): ProviderResult { + if (this.connected) { + return this.callNext('provideDocumentLinks', arguments); + } + } + + public resolveDocumentLink(): ProviderResult { + if (this.connected) { + return this.callNext('resolveDocumentLink', arguments); + } + } + + @captureTelemetryForLSPMethod('textDocument/declaration', debounceRareCall) + public provideDeclaration(): ProviderResult { + if (this.connected) { + return this.callNext('provideDeclaration', arguments); + } + } + + private callNext(funcName: keyof NotebookMiddlewareAddon, args: IArguments) { + // This function uses the last argument to call the 'next' item. If we're allowing notebook + // middleware, it calls into the notebook middleware first. + if (this.notebookAddon) { + // It would be nice to use args.callee, but not supported in strict mode + + return (this.notebookAddon as any)[funcName](...args); + } + return args[args.length - 1](...args); + } +} + +function captureTelemetryForLSPMethod( + method: string, + debounceMilliseconds: number, + lazyMeasures?: (this_: any, result: any) => Record, +) { + return function (_target: Object, _propertyKey: string, descriptor: TypedPropertyDescriptor) { + const originalMethod = descriptor.value; + + descriptor.value = function (this: LanguageClientMiddlewareBase, ...args: any[]) { + const { eventName } = this; + if (!eventName) { + return originalMethod.apply(this, args); + } + + const now = Date.now(); + + if (now > this.nextWindow) { + // Past the end of the last window, reset. + this.nextWindow = now + globalDebounce; + this.eventCount = 0; + } else if (this.eventCount >= globalLimit) { + // Sent too many events in this window, don't send. + return originalMethod.apply(this, args); + } + + const lastCapture = this.lastCaptured.get(method); + if (lastCapture && now - lastCapture < debounceMilliseconds) { + return originalMethod.apply(this, args); + } + + this.lastCaptured.set(method, now); + this.eventCount += 1; + + // Replace all slashes in the method name so it doesn't get scrubbed by vscode-extension-telemetry. + const formattedMethod = method.replace(/\//g, '.'); + + const properties = { + lsVersion: this.serverVersion || 'unknown', + method: formattedMethod, + }; + + const stopWatch = new StopWatch(); + const sendTelemetry = (result: any) => { + let measures: number | Record = stopWatch.elapsedTime; + if (lazyMeasures) { + measures = { + duration: measures, + ...lazyMeasures(this, result), + }; + } + this.sendTelemetryEventFunc(eventName, measures, properties); + return result; + }; + + const result = originalMethod.apply(this, args); + + if (isThenable(result)) { + return result.then(sendTelemetry); + } + + sendTelemetry(result); + + return result; + }; + + return descriptor; + }; +} diff --git a/src/client/browser/extension.ts b/src/client/browser/extension.ts index 4164bc7f3e7f..4b27ad2a0f7e 100644 --- a/src/client/browser/extension.ts +++ b/src/client/browser/extension.ts @@ -2,10 +2,14 @@ // Licensed under the MIT License. import * as vscode from 'vscode'; -import { LanguageClientOptions } from 'vscode-languageclient'; +import TelemetryReporter from 'vscode-extension-telemetry'; +import { LanguageClientOptions, State } from 'vscode-languageclient'; import { LanguageClient } from 'vscode-languageclient/browser'; +import { LanguageClientMiddlewareBase } from '../activation/languageClientMiddlewareBase'; import { ILSExtensionApi } from '../activation/node/languageServerFolderService'; -import { PYLANCE_EXTENSION_ID } from '../common/constants'; +import { LanguageServerType } from '../activation/types'; +import { AppinsightsKey, PVSC_EXTENSION_ID, PYLANCE_EXTENSION_ID } from '../common/constants'; +import { EventName } from '../telemetry/constants'; interface BrowserConfig { distUrl: string; // URL to Pylance's dist folder. @@ -23,7 +27,7 @@ async function runPylance(context: vscode.ExtensionContext): Promise { throw new Error('Could not find Pylance extension'); } - const { path: distUrl } = await pylanceApi.languageServerFolder(); + const { path: distUrl, version } = await pylanceApi.languageServerFolder(); try { const worker = new Worker(`${distUrl}/browser.server.bundle.js`); @@ -38,6 +42,14 @@ async function runPylance(context: vscode.ExtensionContext): Promise { }; worker.postMessage(config); + const middleware = new LanguageClientMiddlewareBase( + undefined, + LanguageServerType.Node, + sendTelemetryEventBrowser, + version, + ); + middleware.connect(); + const clientOptions: LanguageClientOptions = { // Register the server for python source files. documentSelector: [ @@ -49,9 +61,41 @@ async function runPylance(context: vscode.ExtensionContext): Promise { // Synchronize the setting section to the server. configurationSection: ['python'], }, + middleware, }; const languageClient = new LanguageClient('python', 'Python Language Server', clientOptions, worker); + + languageClient.onDidChangeState((e) => { + // The client's on* methods must be called after the client has started, but if called too + // late the server may have already sent a message (which leads to failures). Register + // these on the state change to running to ensure they are ready soon enough. + if (e.newState !== State.Running) { + return; + } + + context.subscriptions.push( + vscode.commands.registerCommand('python.viewLanguageServerOutput', () => + languageClient.outputChannel.show(), + ), + ); + + languageClient.onTelemetry((telemetryEvent) => { + const eventName = telemetryEvent.EventName || EventName.LANGUAGE_SERVER_TELEMETRY; + const formattedProperties = { + ...telemetryEvent.Properties, + // Replace all slashes in the method name so it doesn't get scrubbed by vscode-extension-telemetry. + method: telemetryEvent.Properties.method?.replace(/\//g, '.'), + }; + sendTelemetryEventBrowser( + eventName, + telemetryEvent.Measurements, + formattedProperties, + telemetryEvent.Exception, + ); + }); + }); + const disposable = languageClient.start(); context.subscriptions.push(disposable); @@ -59,3 +103,97 @@ async function runPylance(context: vscode.ExtensionContext): Promise { console.log(e); } } + +// Duplicate code from telemetry/index.ts to avoid pulling in winston, +// which doesn't support the browser. + +let telemetryReporter: TelemetryReporter | undefined; +function getTelemetryReporter() { + if (telemetryReporter) { + return telemetryReporter; + } + const extensionId = PVSC_EXTENSION_ID; + + // eslint-disable-next-line global-require + const { extensions } = require('vscode') as typeof import('vscode'); + const extension = extensions.getExtension(extensionId)!; + const extensionVersion = extension.packageJSON.version; + + // eslint-disable-next-line global-require + const Reporter = require('vscode-extension-telemetry').default as typeof TelemetryReporter; + telemetryReporter = new Reporter(extensionId, extensionVersion, AppinsightsKey, true); + + return telemetryReporter; +} + +function sendTelemetryEventBrowser( + eventName: EventName, + measuresOrDurationMs?: Record | number, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + properties?: any, + ex?: Error, +): void { + const reporter = getTelemetryReporter(); + const measures = + typeof measuresOrDurationMs === 'number' + ? { duration: measuresOrDurationMs } + : measuresOrDurationMs || undefined; + const customProperties: Record = {}; + const eventNameSent = eventName as string; + + if (properties) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const data = properties as any; + Object.getOwnPropertyNames(data).forEach((prop) => { + if (data[prop] === undefined || data[prop] === null) { + return; + } + try { + // If there are any errors in serializing one property, ignore that and move on. + // Else nothing will be sent. + switch (typeof data[prop]) { + case 'string': + customProperties[prop] = data[prop]; + break; + case 'object': + customProperties[prop] = 'object'; + break; + default: + customProperties[prop] = data[prop].toString(); + break; + } + } catch (exception) { + console.error(`Failed to serialize ${prop} for ${eventName}`, exception); + } + }); + } + + // Add shared properties to telemetry props (we may overwrite existing ones). + // Removed in the browser; there's no setSharedProperty. + // Object.assign(customProperties, sharedProperties); + + if (ex) { + const errorProps = { + errorName: ex.name, + errorMessage: ex.message, + errorStack: ex.stack ?? '', + }; + Object.assign(customProperties, errorProps); + + // To avoid hardcoding the names and forgetting to update later. + const errorPropNames = Object.getOwnPropertyNames(errorProps); + // TODO: remove this "as any" once the upstream lib is fixed. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (reporter.sendTelemetryErrorEvent as any)(eventNameSent, customProperties, measures, errorPropNames); + } else { + reporter.sendTelemetryEvent(eventNameSent, customProperties, measures); + } + + if (process.env && process.env.VSC_PYTHON_LOG_TELEMETRY) { + console.error( + `Telemetry Event : ${eventNameSent} Measures: ${JSON.stringify(measures)} Props: ${JSON.stringify( + customProperties, + )} `, + ); + } +} diff --git a/src/client/telemetry/index.ts b/src/client/telemetry/index.ts index db98680a919e..5c6a52e428b3 100644 --- a/src/client/telemetry/index.ts +++ b/src/client/telemetry/index.ts @@ -159,7 +159,9 @@ export function sendTelemetryEvent

Date: Wed, 18 Aug 2021 12:45:19 -0700 Subject: [PATCH 07/10] Add typings/types to browser tsconfig --- tsconfig.browser.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tsconfig.browser.json b/tsconfig.browser.json index d4aae7e98208..96fde2389f6f 100644 --- a/tsconfig.browser.json +++ b/tsconfig.browser.json @@ -1,6 +1,8 @@ { "extends": "./tsconfig.json", "include": [ - "./src/client/browser" + "./src/client/browser", + "./typings", + "./types", ] } From c6dbff8ce08c1ebfd4f498bd196ac453c2a06310 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Thu, 19 Aug 2021 10:11:01 -0700 Subject: [PATCH 08/10] Wait for pylance instead of failing in the browser (#17033) (#17036) --- src/client/browser/extension.ts | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/client/browser/extension.ts b/src/client/browser/extension.ts index 4b27ad2a0f7e..94e86603565a 100644 --- a/src/client/browser/extension.ts +++ b/src/client/browser/extension.ts @@ -17,13 +17,28 @@ interface BrowserConfig { export async function activate(context: vscode.ExtensionContext): Promise { // Run in a promise and return early so that VS Code can go activate Pylance. - runPylance(context); -} -async function runPylance(context: vscode.ExtensionContext): Promise { const pylanceExtension = vscode.extensions.getExtension(PYLANCE_EXTENSION_ID); - const pylanceApi = await pylanceExtension?.activate(); - if (!pylanceApi?.languageServerFolder) { + if (pylanceExtension) { + runPylance(context, pylanceExtension); + return; + } + + const changeDisposable = vscode.extensions.onDidChange(() => { + const newPylanceExtension = vscode.extensions.getExtension(PYLANCE_EXTENSION_ID); + if (newPylanceExtension) { + changeDisposable.dispose(); + runPylance(context, newPylanceExtension); + } + }); +} + +async function runPylance( + context: vscode.ExtensionContext, + pylanceExtension: vscode.Extension, +): Promise { + const pylanceApi = await pylanceExtension.activate(); + if (!pylanceApi.languageServerFolder) { throw new Error('Could not find Pylance extension'); } From e1d1f8aad6f460917ebf4e92c0aa0473254085e8 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Thu, 19 Aug 2021 10:11:29 -0700 Subject: [PATCH 09/10] Update version number and change log for point release (#17023) * Update versions * Update change log. * Update CHANGELOG date --- CHANGELOG.md | 71 ++++++++++++++++++++++++++++++++++++ news/1 Enhancements/16869.md | 1 - news/1 Enhancements/16870.md | 1 - news/3 Code Health/16871.md | 1 - news/3 Code Health/16872.md | 1 - package-lock.json | 2 +- package.json | 2 +- 7 files changed, 73 insertions(+), 6 deletions(-) delete mode 100644 news/1 Enhancements/16869.md delete mode 100644 news/1 Enhancements/16870.md delete mode 100644 news/3 Code Health/16871.md delete mode 100644 news/3 Code Health/16872.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ae3e1f485ec..1492fd8e63d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,76 @@ # Changelog +## 2021.8.2 (19 August 2021) + +### Enhancements + +1. Add a basic web extension bundle. + ([#16869](https://github.com/Microsoft/vscode-python/issues/16869)) +1. Add basic Pylance support to the web extension. + ([#16870](https://github.com/Microsoft/vscode-python/issues/16870)) + +### Code Health + +1. Update telemetry client to support browser, plumb to Pylance. + ([#16871](https://github.com/Microsoft/vscode-python/issues/16871)) +1. Refactor language server middleware to work in the browser. + ([#16872](https://github.com/Microsoft/vscode-python/issues/16872)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [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: + [bandit](https://pypi.org/project/bandit/), + [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! + ## 2021.8.1 (6 August 2021) ### Fixes diff --git a/news/1 Enhancements/16869.md b/news/1 Enhancements/16869.md deleted file mode 100644 index ad850baf77dc..000000000000 --- a/news/1 Enhancements/16869.md +++ /dev/null @@ -1 +0,0 @@ -Add a basic web extension bundle. diff --git a/news/1 Enhancements/16870.md b/news/1 Enhancements/16870.md deleted file mode 100644 index f3fcc9ff7fc4..000000000000 --- a/news/1 Enhancements/16870.md +++ /dev/null @@ -1 +0,0 @@ -Add basic Pylance support to the web extension. diff --git a/news/3 Code Health/16871.md b/news/3 Code Health/16871.md deleted file mode 100644 index 6e6eef269d55..000000000000 --- a/news/3 Code Health/16871.md +++ /dev/null @@ -1 +0,0 @@ -Update telemetry client to support browser, plumb to Pylance. diff --git a/news/3 Code Health/16872.md b/news/3 Code Health/16872.md deleted file mode 100644 index 7ba6cba6c200..000000000000 --- a/news/3 Code Health/16872.md +++ /dev/null @@ -1 +0,0 @@ -Refactor language server middleware to work in the browser. diff --git a/package-lock.json b/package-lock.json index 7683c2b0c567..009a6677f545 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "python", - "version": "2021.8.1", + "version": "2021.8.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 9616145d5436..3a97db15ec67 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "IntelliSense (Pylance), Linting, Debugging (multi-threaded, remote), Jupyter Notebooks, code formatting, refactoring, unit tests, and more.", - "version": "2021.8.1", + "version": "2021.8.2", "featureFlags": { "usingNewInterpreterStorage": true }, From 9c21f7c48cb4f69fee4c0b0c406128492c7e4e90 Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Mon, 23 Aug 2021 11:31:20 -0700 Subject: [PATCH 10/10] Update version and change log for point release. (#17074) * Update `vsce` to latest. (#17051) * Update vscode-extension-telemetry to 0.2.9 (#17071) * Update vscode-extension-telemetry to 0.2.9 * Remove todo * Remove other todo * Exact * Update version and change log for point release. Co-authored-by: Jake Bailey <5341706+jakebailey@users.noreply.github.com> --- CHANGELOG.md | 62 +++++++++++++++++++++++++++++++++ package-lock.json | 53 +++++++++++++++------------- package.json | 6 ++-- src/client/browser/extension.ts | 4 +-- src/client/telemetry/index.ts | 4 +-- 5 files changed, 95 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1492fd8e63d8..c99dfd6135b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,67 @@ # Changelog +## 2021.8.3 (23 August 2021) + +### Fixes + +1. Update `vsce` to latest to fix metadata in VSIX for web extension. + ([#17049](https://github.com/Microsoft/vscode-python/issues/17049)) + +### Thanks + +Thanks to the following projects which we fully rely on to provide some of +our features: + +- [debugpy](https://pypi.org/project/debugpy/) +- [isort](https://pypi.org/project/isort/) +- [jedi](https://pypi.org/project/jedi/) + and [parso](https://pypi.org/project/parso/) +- [jedi-language-server](https://pypi.org/project/jedi-language-server/) +- [Microsoft Python Language Server](https://github.com/microsoft/python-language-server) +- [Pylance](https://github.com/microsoft/pylance-release) +- [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: + [bandit](https://pypi.org/project/bandit/), + [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! + ## 2021.8.2 (19 August 2021) ### Enhancements diff --git a/package-lock.json b/package-lock.json index 009a6677f545..feb976f9791f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "python", - "version": "2021.8.2", + "version": "2021.8.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2394,9 +2394,9 @@ "dev": true }, "azure-devops-node-api": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-10.2.2.tgz", - "integrity": "sha512-4TVv2X7oNStT0vLaEfExmy3J4/CzfuXolEcQl/BRUmvGySqKStTG2O55/hUQ0kM7UJlZBLgniM0SBq4d/WkKow==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-11.0.1.tgz", + "integrity": "sha512-YMdjAw9l5p/6leiyIloxj3k7VIvYThKjvqgiQn88r3nhT93ENwsoDS3A83CyJ4uTWzCZ5f5jCi6c27rTU5Pz+A==", "dev": true, "requires": { "tunnel": "0.0.6", @@ -12119,9 +12119,9 @@ "dev": true }, "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, "mv": { @@ -18630,9 +18630,9 @@ } }, "typed-rest-client": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.4.tgz", - "integrity": "sha512-MyfKKYzk3I6/QQp6e1T50py4qg+c+9BzOEl2rBmQIpStwNUoqQ73An+Tkfy9YuV7O+o2mpVVJpe+fH//POZkbg==", + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.5.tgz", + "integrity": "sha512-952/Aegu3lTqUAI1anbDLbewojnF/gh8at9iy1CIrfS1h/+MtNjB1Y9z6ZF5n2kZd+97em56lZ9uu7Zz3y/pwg==", "dev": true, "requires": { "qs": "^6.9.1", @@ -18648,12 +18648,6 @@ "requires": { "side-channel": "^1.0.4" } - }, - "underscore": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", - "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==", - "dev": true } } }, @@ -19287,14 +19281,14 @@ "dev": true }, "vsce": { - "version": "1.88.0", - "resolved": "https://registry.npmjs.org/vsce/-/vsce-1.88.0.tgz", - "integrity": "sha512-FS5ou3G+WRnPPr/tWVs8b/jVzeDacgZHy/y7/QQW7maSPFEAmRt2bFGUJtJVEUDLBqtDm/3VGMJ7D31cF2U1tw==", + "version": "1.96.1", + "resolved": "https://registry.npmjs.org/vsce/-/vsce-1.96.1.tgz", + "integrity": "sha512-KnEVqjfc1dXrpZsbJ8J7B9VQ7GAAx8o5RqBNk42Srv1KF9+e2/aXchQHe9QZxeUs/FiliHoMGpGvnHTXwKIT2A==", "dev": true, "requires": { - "azure-devops-node-api": "^10.2.2", + "azure-devops-node-api": "^11.0.1", "chalk": "^2.4.2", - "cheerio": "^1.0.0-rc.1", + "cheerio": "^1.0.0-rc.9", "commander": "^6.1.0", "denodeify": "^1.2.1", "glob": "^7.0.6", @@ -19307,7 +19301,7 @@ "parse-semver": "^1.1.1", "read": "^1.0.7", "semver": "^5.1.0", - "tmp": "0.0.29", + "tmp": "^0.2.1", "typed-rest-client": "^1.8.4", "url-join": "^1.1.0", "yauzl": "^2.3.1", @@ -19319,6 +19313,15 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", "dev": true + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } } } }, @@ -19346,9 +19349,9 @@ "integrity": "sha512-+OMm11R1bGYbpIJ5eQIkwoDGFF4GvBz3Ztl6/VM+/RNNb2Gjk2c0Ku+oMmfhlTmTlPCpgHBsH4JqVCbUYhu5bA==" }, "vscode-extension-telemetry": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/vscode-extension-telemetry/-/vscode-extension-telemetry-0.2.8.tgz", - "integrity": "sha512-Vf52im5qzORRD2K5Ryp8PXo31YXVcJAYRSDDZGegWlt0OATOd83DYabS1U/WIq9nR5g80UQKH3+BsenhpQHUaA==" + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/vscode-extension-telemetry/-/vscode-extension-telemetry-0.2.9.tgz", + "integrity": "sha512-JUHHikEG47iLF4bc5lTFHHO1l+e1RLlU5/mtwRifd2qgWz9Vc8eBA9/xbxMudO2skSICVE16VTyi/9KEFQ3vxw==" }, "vscode-jsonrpc": { "version": "6.0.0", diff --git a/package.json b/package.json index 3a97db15ec67..d42e6adfbf6c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "python", "displayName": "Python", "description": "IntelliSense (Pylance), Linting, Debugging (multi-threaded, remote), Jupyter Notebooks, code formatting, refactoring, unit tests, and more.", - "version": "2021.8.2", + "version": "2021.8.3", "featureFlags": { "usingNewInterpreterStorage": true }, @@ -2265,7 +2265,7 @@ "untildify": "^3.0.2", "vscode-debugadapter": "^1.28.0", "vscode-debugprotocol": "^1.28.0", - "vscode-extension-telemetry": "0.2.8", + "vscode-extension-telemetry": "0.2.9", "vscode-jsonrpc": "6.0.0", "vscode-languageclient": "7.0.0", "vscode-languageserver": "7.0.0", @@ -2437,7 +2437,7 @@ "url-loader": "^1.1.2", "uuid": "^3.3.2", "vinyl-fs": "^3.0.3", - "vsce": "^1.59.0", + "vsce": "^1.96.1", "vscode-debugadapter-testsupport": "^1.27.0", "webpack": "^4.33.0", "webpack-bundle-analyzer": "^3.6.0", diff --git a/src/client/browser/extension.ts b/src/client/browser/extension.ts index 94e86603565a..284bf1300ada 100644 --- a/src/client/browser/extension.ts +++ b/src/client/browser/extension.ts @@ -197,9 +197,7 @@ function sendTelemetryEventBrowser( // To avoid hardcoding the names and forgetting to update later. const errorPropNames = Object.getOwnPropertyNames(errorProps); - // TODO: remove this "as any" once the upstream lib is fixed. - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (reporter.sendTelemetryErrorEvent as any)(eventNameSent, customProperties, measures, errorPropNames); + reporter.sendTelemetryErrorEvent(eventNameSent, customProperties, measures, errorPropNames); } else { reporter.sendTelemetryEvent(eventNameSent, customProperties, measures); } diff --git a/src/client/telemetry/index.ts b/src/client/telemetry/index.ts index 5c6a52e428b3..db98680a919e 100644 --- a/src/client/telemetry/index.ts +++ b/src/client/telemetry/index.ts @@ -159,9 +159,7 @@ export function sendTelemetryEvent